mirror of
https://github.com/home-assistant/core.git
synced 2025-09-20 18:39:40 +00:00
Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7c783dc1b4 | ||
![]() |
650f3413a4 | ||
![]() |
176c2f3978 | ||
![]() |
9d617d446e | ||
![]() |
ea5f621351 | ||
![]() |
b49aa9485c | ||
![]() |
22f16759e5 | ||
![]() |
8895c65dd4 | ||
![]() |
dfed834617 | ||
![]() |
7d0f5fa31a | ||
![]() |
b65c92ce91 | ||
![]() |
7c084975b7 | ||
![]() |
943cc243b5 | ||
![]() |
ba48fd6c51 | ||
![]() |
2f1d30fa85 | ||
![]() |
7a96f8a4eb | ||
![]() |
df6ee2e51f | ||
![]() |
6bacda3cee | ||
![]() |
d4b68454a3 | ||
![]() |
36b099592e | ||
![]() |
d156e95f8a | ||
![]() |
f3c7b77afa | ||
![]() |
45dbd49852 | ||
![]() |
e4f88a02f7 | ||
![]() |
32652fde48 | ||
![]() |
0dc926a131 | ||
![]() |
43486917bf | ||
![]() |
d967ecc8c6 | ||
![]() |
a7bdd2aafe | ||
![]() |
e6cc35cff2 | ||
![]() |
b7d0a21a8b | ||
![]() |
beaaa1478c | ||
![]() |
74d8569aab | ||
![]() |
dc0d27ec18 | ||
![]() |
64707d89dd | ||
![]() |
6c51ff63ad | ||
![]() |
aecc3476e8 | ||
![]() |
43a990350b | ||
![]() |
769c5c1779 | ||
![]() |
a93ae418a2 | ||
![]() |
562ccbbe25 | ||
![]() |
5f28c82837 | ||
![]() |
92f9a77770 | ||
![]() |
2430c5ea22 | ||
![]() |
6d54ed14b4 | ||
![]() |
faf73e304a | ||
![]() |
e902cfcbf0 | ||
![]() |
750bb11895 | ||
![]() |
2dcd57bf38 | ||
![]() |
26766d68aa | ||
![]() |
d26bceb110 | ||
![]() |
aa52ade5a0 | ||
![]() |
c57d697df5 | ||
![]() |
c604595f98 | ||
![]() |
1ab2d55a7a | ||
![]() |
048db9cb7d | ||
![]() |
0959777b81 | ||
![]() |
9c0a2d69ff | ||
![]() |
529b849efe | ||
![]() |
a34d06e363 | ||
![]() |
5b85776566 | ||
![]() |
68d2938c6b | ||
![]() |
5a460e609f | ||
![]() |
a88e011bba | ||
![]() |
40be42299d | ||
![]() |
7dcebbe89d | ||
![]() |
cb453d3ae1 | ||
![]() |
2effbb6604 | ||
![]() |
ba6acc286c | ||
![]() |
45c1c2acac | ||
![]() |
1962a7c7ce | ||
![]() |
8da732a89d | ||
![]() |
2697056ba9 | ||
![]() |
91e8593fb6 | ||
![]() |
9fa97473cb | ||
![]() |
29e7aa753c | ||
![]() |
af31aa7995 | ||
![]() |
e3d29429d3 | ||
![]() |
93e71ef218 | ||
![]() |
2893972c69 | ||
![]() |
13b8bc6290 | ||
![]() |
326d36d303 | ||
![]() |
287b33eef3 | ||
![]() |
3ec83dc29e | ||
![]() |
f716b7714b | ||
![]() |
b7c958719e | ||
![]() |
934ac73ae5 | ||
![]() |
bcd2ae78a0 | ||
![]() |
c00ab762d3 | ||
![]() |
1fb18580b2 | ||
![]() |
9c6351c1b3 | ||
![]() |
4eacf3f6c0 | ||
![]() |
35d2badb24 | ||
![]() |
331d5ba7ef | ||
![]() |
a2c157b5e9 | ||
![]() |
883fb8c168 | ||
![]() |
2e7ce63cbc | ||
![]() |
b925ae39cb | ||
![]() |
5c608eb1bc | ||
![]() |
3032f9280c | ||
![]() |
00284a87d0 | ||
![]() |
21917e86c9 | ||
![]() |
b6994689b1 | ||
![]() |
0c1c7d797c | ||
![]() |
9c60195780 | ||
![]() |
d8fea1c582 | ||
![]() |
84ab40c57d | ||
![]() |
aace9dce38 | ||
![]() |
f6f95d0422 | ||
![]() |
e0466d4ac8 |
@@ -161,7 +161,6 @@ homeassistant/components/goalzero/* @tkdrob
|
||||
homeassistant/components/gogogate2/* @vangorra
|
||||
homeassistant/components/google_assistant/* @home-assistant/cloud
|
||||
homeassistant/components/google_cloud/* @lufton
|
||||
homeassistant/components/google_translate/* @awarecan
|
||||
homeassistant/components/gpsd/* @fabaff
|
||||
homeassistant/components/gree/* @cmroche
|
||||
homeassistant/components/greeneye_monitor/* @jkeljo
|
||||
@@ -363,6 +362,7 @@ homeassistant/components/rmvtransport/* @cgtobi
|
||||
homeassistant/components/roku/* @ctalkington
|
||||
homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn
|
||||
homeassistant/components/roon/* @pavoni
|
||||
homeassistant/components/rpi_gpio_pwm/* @soldag
|
||||
homeassistant/components/rpi_power/* @shenxn @swetoast
|
||||
homeassistant/components/ruckus_unleashed/* @gabe565
|
||||
homeassistant/components/safe_mode/* @home-assistant/core
|
||||
|
@@ -143,8 +143,8 @@ stages:
|
||||
|
||||
version="$(homeassistantRelease)"
|
||||
|
||||
git clone https://github.com/home-assistant/hassio-version
|
||||
cd hassio-version
|
||||
git clone https://github.com/home-assistant/version
|
||||
cd version
|
||||
|
||||
dev_version="$(jq --raw-output '.homeassistant.default' dev.json)"
|
||||
beta_version="$(jq --raw-output '.homeassistant.default' beta.json)"
|
||||
|
@@ -31,7 +31,7 @@ UPDATE_TOPIC = f"{DOMAIN}_update"
|
||||
|
||||
ICON = "mdi:package-variant-closed"
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
SERVICE_ADD_TRACKING = "add_tracking"
|
||||
SERVICE_REMOVE_TRACKING = "remove_tracking"
|
||||
|
@@ -3,6 +3,6 @@
|
||||
"name": "AirVisual",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||
"requirements": ["pyairvisual==5.0.3"],
|
||||
"requirements": ["pyairvisual==5.0.4"],
|
||||
"codeowners": ["@bachya"]
|
||||
}
|
||||
|
@@ -276,7 +276,7 @@ class Alert(ToggleEntity):
|
||||
self._send_done_message = True
|
||||
|
||||
if self._message_template is not None:
|
||||
message = self._message_template.async_render()
|
||||
message = self._message_template.async_render(parse_result=False)
|
||||
else:
|
||||
message = self._name
|
||||
|
||||
@@ -291,7 +291,7 @@ class Alert(ToggleEntity):
|
||||
if self._done_message_template is None:
|
||||
return
|
||||
|
||||
message = self._done_message_template.async_render()
|
||||
message = self._done_message_template.async_render(parse_result=False)
|
||||
|
||||
await self._send_notification_message(message)
|
||||
|
||||
@@ -300,7 +300,7 @@ class Alert(ToggleEntity):
|
||||
msg_payload = {ATTR_MESSAGE: message}
|
||||
|
||||
if self._title_template is not None:
|
||||
title = self._title_template.async_render()
|
||||
title = self._title_template.async_render(parse_result=False)
|
||||
msg_payload.update({ATTR_TITLE: title})
|
||||
if self._data:
|
||||
msg_payload.update({ATTR_DATA: self._data})
|
||||
|
@@ -80,13 +80,17 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
output = {}
|
||||
if item.get(CONF_TITLE) is not None:
|
||||
if isinstance(item.get(CONF_TITLE), template.Template):
|
||||
output[ATTR_TITLE_TEXT] = item[CONF_TITLE].async_render()
|
||||
output[ATTR_TITLE_TEXT] = item[CONF_TITLE].async_render(
|
||||
parse_result=False
|
||||
)
|
||||
else:
|
||||
output[ATTR_TITLE_TEXT] = item.get(CONF_TITLE)
|
||||
|
||||
if item.get(CONF_TEXT) is not None:
|
||||
if isinstance(item.get(CONF_TEXT), template.Template):
|
||||
output[ATTR_MAIN_TEXT] = item[CONF_TEXT].async_render()
|
||||
output[ATTR_MAIN_TEXT] = item[CONF_TEXT].async_render(
|
||||
parse_result=False
|
||||
)
|
||||
else:
|
||||
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
|
||||
|
||||
@@ -97,13 +101,17 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
|
||||
if item.get(CONF_AUDIO) is not None:
|
||||
if isinstance(item.get(CONF_AUDIO), template.Template):
|
||||
output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render()
|
||||
output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render(
|
||||
parse_result=False
|
||||
)
|
||||
else:
|
||||
output[ATTR_STREAM_URL] = item.get(CONF_AUDIO)
|
||||
|
||||
if item.get(CONF_DISPLAY_URL) is not None:
|
||||
if isinstance(item.get(CONF_DISPLAY_URL), template.Template):
|
||||
output[ATTR_REDIRECTION_URL] = item[CONF_DISPLAY_URL].async_render()
|
||||
output[ATTR_REDIRECTION_URL] = item[CONF_DISPLAY_URL].async_render(
|
||||
parse_result=False
|
||||
)
|
||||
else:
|
||||
output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL)
|
||||
|
||||
|
@@ -271,7 +271,7 @@ class AlexaResponse:
|
||||
|
||||
self.reprompt = {
|
||||
"type": speech_type.value,
|
||||
key: text.async_render(self.variables),
|
||||
key: text.async_render(self.variables, parse_result=False),
|
||||
}
|
||||
|
||||
def as_dict(self):
|
||||
|
@@ -410,7 +410,7 @@ class APITemplateView(HomeAssistantView):
|
||||
try:
|
||||
data = await request.json()
|
||||
tpl = template.Template(data["template"], request.app["hass"])
|
||||
return str(tpl.async_render(data.get("variables")))
|
||||
return tpl.async_render(variables=data.get("variables"), parse_result=False)
|
||||
except (ValueError, TemplateError) as ex:
|
||||
return self.json_message(
|
||||
f"Error rendering template: {ex}", HTTP_BAD_REQUEST
|
||||
|
@@ -229,7 +229,7 @@ class ApnsNotificationService(BaseNotificationService):
|
||||
if isinstance(message, str):
|
||||
rendered_message = message
|
||||
elif isinstance(message, template_helper.Template):
|
||||
rendered_message = message.render()
|
||||
rendered_message = message.render(parse_result=False)
|
||||
else:
|
||||
rendered_message = ""
|
||||
|
||||
|
@@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
def _render(value):
|
||||
try:
|
||||
return value_template.async_render({"value": value})
|
||||
return value_template.async_render({"value": value}, parse_result=False)
|
||||
except TemplateError:
|
||||
_LOGGER.exception("Error parsing value")
|
||||
return value
|
||||
|
@@ -280,12 +280,15 @@ async def get_device(hass, host, port, username, password):
|
||||
|
||||
except axis.Unauthorized as err:
|
||||
LOGGER.warning("Connected to device at %s but not registered.", host)
|
||||
await device.vapix.close()
|
||||
raise AuthenticationRequired from err
|
||||
|
||||
except (asyncio.TimeoutError, axis.RequestError) as err:
|
||||
LOGGER.error("Error connecting to the Axis device at %s", host)
|
||||
await device.vapix.close()
|
||||
raise CannotConnect from err
|
||||
|
||||
except axis.AxisException as err:
|
||||
LOGGER.exception("Unknown Axis communication error occurred")
|
||||
await device.vapix.close()
|
||||
raise AuthenticationRequired from err
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "bmw_connected_drive",
|
||||
"name": "BMW Connected Drive",
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"requirements": ["bimmer_connected==0.7.8"],
|
||||
"requirements": ["bimmer_connected==0.7.12"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@gerard33", "@rikroe"]
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.last_update_success:
|
||||
coordinator.shutdown()
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
@@ -78,6 +78,7 @@ class CanaryCamera(CoordinatorEntity, Camera):
|
||||
def __init__(self, hass, coordinator, location_id, device, timeout, ffmpeg_args):
|
||||
"""Initialize a Canary security camera."""
|
||||
super().__init__(coordinator)
|
||||
Camera.__init__(self)
|
||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||
self._ffmpeg_arguments = ffmpeg_args
|
||||
self._location_id = location_id
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "cloudflare",
|
||||
"name": "Cloudflare",
|
||||
"documentation": "https://www.home-assistant.io/integrations/cloudflare",
|
||||
"requirements": ["pycfdns==1.1.1"],
|
||||
"requirements": ["pycfdns==1.2.1"],
|
||||
"codeowners": ["@ludeeus", "@ctalkington"],
|
||||
"config_flow": true
|
||||
}
|
||||
|
@@ -161,7 +161,7 @@ class DialogflowResponse:
|
||||
assert self.speech is None
|
||||
|
||||
if isinstance(text, template.Template):
|
||||
text = text.async_render(self.parameters)
|
||||
text = text.async_render(self.parameters, parse_result=False)
|
||||
|
||||
self.speech = text
|
||||
|
||||
|
@@ -168,7 +168,7 @@ async def async_setup_platform(
|
||||
requester = AiohttpSessionRequester(session, True)
|
||||
|
||||
# ensure event handler has been started
|
||||
with await hass.data[DLNA_DMR_DATA]["lock"]:
|
||||
async with hass.data[DLNA_DMR_DATA]["lock"]:
|
||||
server_host = config.get(CONF_LISTEN_IP)
|
||||
if server_host is None:
|
||||
server_host = get_local_ip()
|
||||
@@ -220,7 +220,7 @@ class DlnaDmrDevice(MediaPlayerEntity):
|
||||
|
||||
async def _async_on_hass_stop(self, event):
|
||||
"""Event handler on Home Assistant stop."""
|
||||
with await self.hass.data[DLNA_DMR_DATA]["lock"]:
|
||||
async with self.hass.data[DLNA_DMR_DATA]["lock"]:
|
||||
await self._device.async_unsubscribe_services()
|
||||
|
||||
async def async_update(self):
|
||||
|
@@ -320,6 +320,16 @@ class DerivativeDSMREntity(DSMREntity):
|
||||
"""Return the calculated current hourly rate."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def force_update(self):
|
||||
"""Disable force update."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Enable polling."""
|
||||
return True
|
||||
|
||||
async def async_update(self):
|
||||
"""Recalculate hourly rate if timestamp has changed.
|
||||
|
||||
|
@@ -431,7 +431,7 @@ class EvoBroker:
|
||||
return
|
||||
|
||||
if refresh:
|
||||
self.hass.helpers.event.async_call_later(1, self.async_update())
|
||||
self.hass.helpers.event.async_call_later(1, self.async_update)
|
||||
|
||||
return result
|
||||
|
||||
|
@@ -132,7 +132,10 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
elif (
|
||||
self._temp_sensor_device is None
|
||||
and "unit" in device.properties
|
||||
and "value" in device.properties
|
||||
and (
|
||||
"value" in device.properties
|
||||
or "heatingThermostatSetpoint" in device.properties
|
||||
)
|
||||
and (device.properties.unit == "C" or device.properties.unit == "F")
|
||||
):
|
||||
self._temp_sensor_device = FibaroDevice(device)
|
||||
@@ -141,6 +144,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
if (
|
||||
"setTargetLevel" in device.actions
|
||||
or "setThermostatSetpoint" in device.actions
|
||||
or "setHeatingThermostatSetpoint" in device.actions
|
||||
):
|
||||
self._target_temp_device = FibaroDevice(device)
|
||||
self._support_flags |= SUPPORT_TARGET_TEMPERATURE
|
||||
@@ -317,6 +321,8 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
"""Return the current temperature."""
|
||||
if self._temp_sensor_device:
|
||||
device = self._temp_sensor_device.fibaro_device
|
||||
if "heatingThermostatSetpoint" in device.properties:
|
||||
return float(device.properties.heatingThermostatSetpoint)
|
||||
return float(device.properties.value)
|
||||
return None
|
||||
|
||||
@@ -325,6 +331,8 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
"""Return the temperature we try to reach."""
|
||||
if self._target_temp_device:
|
||||
device = self._target_temp_device.fibaro_device
|
||||
if "heatingThermostatSetpointFuture" in device.properties:
|
||||
return float(device.properties.heatingThermostatSetpointFuture)
|
||||
return float(device.properties.targetLevel)
|
||||
return None
|
||||
|
||||
@@ -335,5 +343,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||
if temperature is not None:
|
||||
if "setThermostatSetpoint" in target.fibaro_device.actions:
|
||||
target.action("setThermostatSetpoint", self.fibaro_op_mode, temperature)
|
||||
elif "setHeatingThermostatSetpoint" in target.fibaro_device.actions:
|
||||
target.action("setHeatingThermostatSetpoint", temperature)
|
||||
else:
|
||||
target.action("setTargetLevel", temperature)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "frontend",
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": ["home-assistant-frontend==20201021.2"],
|
||||
"requirements": ["home-assistant-frontend==20201021.4"],
|
||||
"dependencies": [
|
||||
"api",
|
||||
"auth",
|
||||
|
@@ -121,7 +121,7 @@ class GenericCamera(Camera):
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
try:
|
||||
url = self._still_image_url.async_render()
|
||||
url = self._still_image_url.async_render(parse_result=False)
|
||||
except TemplateError as err:
|
||||
_LOGGER.error("Error parsing template %s: %s", self._still_image_url, err)
|
||||
return self._last_image
|
||||
@@ -178,7 +178,7 @@ class GenericCamera(Camera):
|
||||
return None
|
||||
|
||||
try:
|
||||
return self._stream_source.async_render()
|
||||
return self._stream_source.async_render(parse_result=False)
|
||||
except TemplateError as err:
|
||||
_LOGGER.error("Error parsing template %s: %s", self._stream_source, err)
|
||||
return None
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "geo_rss_events",
|
||||
"name": "GeoRSS",
|
||||
"documentation": "https://www.home-assistant.io/integrations/geo_rss_events",
|
||||
"requirements": ["georss_generic_client==0.3"],
|
||||
"requirements": ["georss_generic_client==0.4"],
|
||||
"codeowners": ["@exxamalte"]
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from georss_client import UPDATE_OK, UPDATE_OK_NO_DATA
|
||||
from georss_client.generic_feed import GenericFeed
|
||||
from georss_generic_client import GenericFeed
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "google_translate",
|
||||
"name": "Google Translate Text-to-Speech",
|
||||
"documentation": "https://www.home-assistant.io/integrations/google_translate",
|
||||
"requirements": ["gTTS-token==1.1.3"],
|
||||
"codeowners": ["@awarecan"]
|
||||
"requirements": ["gTTS-token==1.1.4"],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@@ -123,9 +123,14 @@ class GoogleProvider(Provider):
|
||||
|
||||
data = b""
|
||||
for idx, part in enumerate(message_parts):
|
||||
try:
|
||||
part_token = await self.hass.async_add_executor_job(
|
||||
token.calculate_token, part
|
||||
)
|
||||
except ValueError as err:
|
||||
# If token seed fetching fails.
|
||||
_LOGGER.warning(err)
|
||||
return None, None
|
||||
|
||||
url_param = {
|
||||
"ie": "UTF-8",
|
||||
|
@@ -3,6 +3,6 @@
|
||||
"name": "Gree Climate",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/gree",
|
||||
"requirements": ["greeclimate==0.9.0"],
|
||||
"requirements": ["greeclimate==0.9.5"],
|
||||
"codeowners": ["@cmroche"]
|
||||
}
|
@@ -2,6 +2,6 @@
|
||||
"domain": "hikvision",
|
||||
"name": "Hikvision",
|
||||
"documentation": "https://www.home-assistant.io/integrations/hikvision",
|
||||
"requirements": ["pyhik==0.2.7"],
|
||||
"requirements": ["pyhik==0.2.8"],
|
||||
"codeowners": ["@mezz64"]
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"requirements": [
|
||||
"aiohomekit==0.2.53"
|
||||
"aiohomekit==0.2.54"
|
||||
],
|
||||
"zeroconf": [
|
||||
"_hap._tcp.local."
|
||||
|
@@ -157,7 +157,9 @@ class HpIloSensor(Entity):
|
||||
ilo_data = getattr(self.hp_ilo_data.data, self._ilo_function)()
|
||||
|
||||
if self._sensor_value_template is not None:
|
||||
ilo_data = self._sensor_value_template.render(ilo_data=ilo_data)
|
||||
ilo_data = self._sensor_value_template.render(
|
||||
ilo_data=ilo_data, parse_result=False
|
||||
)
|
||||
|
||||
self._state = ilo_data
|
||||
|
||||
|
@@ -187,7 +187,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
||||
icloud_account = hass.data[DOMAIN].get(account_identifier)
|
||||
if icloud_account is None:
|
||||
for account in hass.data[DOMAIN].values():
|
||||
if account.name == account_identifier:
|
||||
if account.username == account_identifier:
|
||||
icloud_account = account
|
||||
|
||||
if icloud_account is None:
|
||||
|
@@ -183,7 +183,7 @@ class EmailContentSensor(Entity):
|
||||
ATTR_DATE: email_message["Date"],
|
||||
ATTR_BODY: EmailContentSensor.get_msg_text(email_message),
|
||||
}
|
||||
return self._value_template.render(variables)
|
||||
return self._value_template.render(variables, parse_result=False)
|
||||
|
||||
def sender_allowed(self, email_message):
|
||||
"""Check if the sender is in the allowed senders list."""
|
||||
|
@@ -26,6 +26,7 @@ from homeassistant.const import (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import event as event_helper, state as state_helper
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_values import EntityValues
|
||||
@@ -500,6 +501,7 @@ class InfluxThread(threading.Thread):
|
||||
self.shutdown = False
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener)
|
||||
|
||||
@callback
|
||||
def _event_listener(self, event):
|
||||
"""Listen for new messages on the bus and queue them for Influx."""
|
||||
item = (time.monotonic(), event)
|
||||
|
@@ -268,7 +268,7 @@ class InfluxFluxSensorData:
|
||||
"""Get the latest data by querying influx."""
|
||||
_LOGGER.debug(RENDERING_QUERY_MESSAGE, self.query)
|
||||
try:
|
||||
rendered_query = self.query.render()
|
||||
rendered_query = self.query.render(parse_result=False)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error(RENDERING_QUERY_ERROR_MESSAGE, ex)
|
||||
return
|
||||
@@ -312,7 +312,7 @@ class InfluxQLSensorData:
|
||||
"""Get the latest data with a shell command."""
|
||||
_LOGGER.debug(RENDERING_WHERE_MESSAGE, self.where)
|
||||
try:
|
||||
where_clause = self.where.render()
|
||||
where_clause = self.where.render(parse_result=False)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error(RENDERING_WHERE_ERROR_MESSAGE, ex)
|
||||
return
|
||||
|
@@ -87,13 +87,14 @@ class ScriptIntentHandler(intent.IntentHandler):
|
||||
|
||||
if speech is not None:
|
||||
response.async_set_speech(
|
||||
speech[CONF_TEXT].async_render(slots), speech[CONF_TYPE]
|
||||
speech[CONF_TEXT].async_render(slots, parse_result=False),
|
||||
speech[CONF_TYPE],
|
||||
)
|
||||
|
||||
if card is not None:
|
||||
response.async_set_card(
|
||||
card[CONF_TITLE].async_render(slots),
|
||||
card[CONF_CONTENT].async_render(slots),
|
||||
card[CONF_TITLE].async_render(slots, parse_result=False),
|
||||
card[CONF_CONTENT].async_render(slots, parse_result=False),
|
||||
card[CONF_TYPE],
|
||||
)
|
||||
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "jewish_calendar",
|
||||
"name": "Jewish Calendar",
|
||||
"documentation": "https://www.home-assistant.io/integrations/jewish_calendar",
|
||||
"requirements": ["hdate==0.9.5"],
|
||||
"requirements": ["hdate==0.9.12"],
|
||||
"codeowners": ["@tsvi"]
|
||||
}
|
||||
|
@@ -89,7 +89,7 @@ class LcnOutputsCover(LcnDevice, CoverEntity):
|
||||
self._is_opening = False
|
||||
self._is_closing = True
|
||||
state = pypck.lcn_defs.MotorStateModifier.DOWN
|
||||
self.address_connection.control_motors_outputs(state)
|
||||
self.address_connection.control_motors_outputs(state, self.reverse_time)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
@@ -106,7 +106,7 @@ class LcnOutputsCover(LcnDevice, CoverEntity):
|
||||
self._is_closing = False
|
||||
self._is_opening = False
|
||||
state = pypck.lcn_defs.MotorStateModifier.STOP
|
||||
self.address_connection.control_motors_outputs(state, self.reverse_time)
|
||||
self.address_connection.control_motors_outputs(state)
|
||||
self.async_write_ha_state()
|
||||
|
||||
def input_received(self, input_obj):
|
||||
|
@@ -144,7 +144,7 @@ async def async_setup(hass, config):
|
||||
domain = DOMAIN
|
||||
|
||||
message.hass = hass
|
||||
message = message.async_render()
|
||||
message = message.async_render(parse_result=False)
|
||||
async_log_entry(hass, name, message, domain, entity_id)
|
||||
|
||||
hass.components.frontend.async_register_built_in_panel(
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"name": "Lutron Caséta",
|
||||
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
|
||||
"requirements": [
|
||||
"pylutron-caseta==0.7.0"
|
||||
"pylutron-caseta==0.7.1"
|
||||
],
|
||||
"codeowners": [
|
||||
"@swails"
|
||||
|
@@ -385,7 +385,9 @@ class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity):
|
||||
if isinstance(self._code, str):
|
||||
alarm_code = self._code
|
||||
else:
|
||||
alarm_code = self._code.render(from_state=self._state, to_state=state)
|
||||
alarm_code = self._code.render(
|
||||
parse_result=False, from_state=self._state, to_state=state
|
||||
)
|
||||
check = not alarm_code or code == alarm_code
|
||||
if not check:
|
||||
_LOGGER.warning("Invalid code given for %s", state)
|
||||
|
@@ -406,7 +406,9 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity):
|
||||
if isinstance(self._code, str):
|
||||
alarm_code = self._code
|
||||
else:
|
||||
alarm_code = self._code.render(from_state=self._state, to_state=state)
|
||||
alarm_code = self._code.render(
|
||||
from_state=self._state, to_state=state, parse_result=False
|
||||
)
|
||||
check = not alarm_code or code == alarm_code
|
||||
if not check:
|
||||
_LOGGER.warning("Invalid code given for %s", state)
|
||||
|
@@ -21,6 +21,8 @@ DEFAULT_VOICE = "cmu-slt-hsmm"
|
||||
DEFAULT_CODEC = "WAVE_FILE"
|
||||
DEFAULT_EFFECTS = {}
|
||||
|
||||
MAP_MARYTTS_CODEC = {"WAVE_FILE": "wav", "AIFF_FILE": "aiff", "AU_FILE": "au"}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||
@@ -81,5 +83,6 @@ class MaryTTSProvider(Provider):
|
||||
effects = options[CONF_EFFECT]
|
||||
|
||||
data = self._mary.speak(message, effects)
|
||||
audiotype = MAP_MARYTTS_CODEC[self._mary.codec]
|
||||
|
||||
return self._mary.codec, data
|
||||
return audiotype, data
|
||||
|
@@ -124,7 +124,7 @@ def setup(hass, config):
|
||||
def _render_service_value(service, key):
|
||||
value = service.data[key]
|
||||
value.hass = hass
|
||||
return value.async_render()
|
||||
return value.async_render(parse_result=False)
|
||||
|
||||
def put_file(service):
|
||||
"""Upload file service."""
|
||||
|
@@ -322,7 +322,7 @@ MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend(
|
||||
MQTT_PUBLISH_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_TOPIC): valid_publish_topic,
|
||||
vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): object,
|
||||
vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): cv.string,
|
||||
vol.Exclusive(ATTR_PAYLOAD_TEMPLATE, CONF_PAYLOAD): cv.string,
|
||||
vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
|
||||
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
|
||||
@@ -567,7 +567,9 @@ async def async_setup_entry(hass, entry):
|
||||
retain: bool = call.data[ATTR_RETAIN]
|
||||
if payload_template is not None:
|
||||
try:
|
||||
payload = template.Template(payload_template, hass).async_render()
|
||||
payload = template.Template(payload_template, hass).async_render(
|
||||
parse_result=False
|
||||
)
|
||||
except template.jinja2.TemplateError as exc:
|
||||
_LOGGER.error(
|
||||
"Unable to publish to %s: rendering payload template of "
|
||||
|
@@ -338,7 +338,7 @@ class MqttAlarm(
|
||||
"""Publish via mqtt."""
|
||||
command_template = self._config[CONF_COMMAND_TEMPLATE]
|
||||
values = {"action": action, "code": code}
|
||||
payload = command_template.async_render(**values)
|
||||
payload = command_template.async_render(**values, parse_result=False)
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._config[CONF_COMMAND_TOPIC],
|
||||
|
@@ -557,7 +557,7 @@ class MqttCover(
|
||||
position = kwargs[ATTR_POSITION]
|
||||
percentage_position = position
|
||||
if set_position_template is not None:
|
||||
position = set_position_template.async_render(**kwargs)
|
||||
position = set_position_template.async_render(parse_result=False, **kwargs)
|
||||
else:
|
||||
position = self.find_in_range_from_percent(position, COVER_PAYLOAD)
|
||||
|
||||
|
@@ -252,7 +252,7 @@ class MqttLightTemplate(
|
||||
except ValueError:
|
||||
_LOGGER.warning("Invalid color value received")
|
||||
|
||||
if self._templates[CONF_COLOR_TEMP_TEMPLATE] is not None:
|
||||
if self._templates[CONF_WHITE_VALUE_TEMPLATE] is not None:
|
||||
try:
|
||||
self._white_value = int(
|
||||
self._templates[
|
||||
@@ -441,7 +441,9 @@ class MqttLightTemplate(
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topics[CONF_COMMAND_TOPIC],
|
||||
self._templates[CONF_COMMAND_ON_TEMPLATE].async_render(**values),
|
||||
self._templates[CONF_COMMAND_ON_TEMPLATE].async_render(
|
||||
parse_result=False, **values
|
||||
),
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
@@ -464,7 +466,9 @@ class MqttLightTemplate(
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topics[CONF_COMMAND_TOPIC],
|
||||
self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render(**values),
|
||||
self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render(
|
||||
parse_result=False, **values
|
||||
),
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
|
@@ -30,7 +30,11 @@ from homeassistant.helpers import (
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import api, config_flow, local_auth
|
||||
@@ -176,7 +180,7 @@ class SignalUpdateCallback(EventCallback):
|
||||
# This event triggered an update to a device that changed some
|
||||
# properties which the DeviceManager should already have received.
|
||||
# Send a signal to refresh state of all listening devices.
|
||||
dispatcher_send(self._hass, SIGNAL_NEST_UPDATE)
|
||||
async_dispatcher_send(self._hass, SIGNAL_NEST_UPDATE)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
@@ -203,7 +207,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID]
|
||||
)
|
||||
subscriber.set_update_callback(SignalUpdateCallback(hass))
|
||||
hass.loop.create_task(subscriber.start_async())
|
||||
asyncio.create_task(subscriber.start_async())
|
||||
hass.data[DOMAIN][entry.entry_id] = subscriber
|
||||
|
||||
for component in PLATFORMS:
|
||||
|
@@ -61,6 +61,10 @@ class CodeInvalid(NestAuthError):
|
||||
"""Raised when invalid authorization code."""
|
||||
|
||||
|
||||
class UnexpectedStateError(HomeAssistantError):
|
||||
"""Raised when the config flow is invoked in a 'should not happen' case."""
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class NestFlowHandler(
|
||||
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||
@@ -111,7 +115,7 @@ class NestFlowHandler(
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
flows = self.hass.data.get(DATA_FLOW_IMPL, {})
|
||||
|
||||
@@ -142,7 +146,7 @@ class NestFlowHandler(
|
||||
deliver the authentication code.
|
||||
"""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl]
|
||||
|
||||
@@ -185,7 +189,7 @@ class NestFlowHandler(
|
||||
async def async_step_import(self, info):
|
||||
"""Import existing auth from Nest."""
|
||||
if self.is_sdm_api():
|
||||
return None
|
||||
raise UnexpectedStateError("Step only supported for legacy API")
|
||||
|
||||
if self.hass.config_entries.async_entries(DOMAIN):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
@@ -13,5 +13,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the sensors."""
|
||||
if DATA_SDM not in entry.data:
|
||||
return await async_setup_legacy_entry(hass, entry, async_add_entities)
|
||||
return await async_setup_sdm_entry(hass, entry, async_add_entities)
|
||||
await async_setup_legacy_entry(hass, entry, async_add_entities)
|
||||
return
|
||||
|
||||
await async_setup_sdm_entry(hass, entry, async_add_entities)
|
||||
|
@@ -18,6 +18,13 @@ from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, SIGNAL_NEST_UPDATE
|
||||
|
||||
DEVICE_TYPE_MAP = {
|
||||
"sdm.devices.types.CAMERA": "Camera",
|
||||
"sdm.devices.types.DISPLAY": "Display",
|
||||
"sdm.devices.types.DOORBELL": "Doorbell",
|
||||
"sdm.devices.types.THERMOSTAT": "Thermostat",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_sdm_entry(
|
||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||
@@ -46,7 +53,7 @@ class SensorBase(Entity):
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def should_pool(self) -> bool:
|
||||
def should_poll(self) -> bool:
|
||||
"""Disable polling since entities have state pushed via pubsub."""
|
||||
return False
|
||||
|
||||
@@ -89,28 +96,19 @@ class SensorBase(Entity):
|
||||
# The API intentionally returns minimal information about specific
|
||||
# devices, instead relying on traits, but we can infer a generic model
|
||||
# name based on the type
|
||||
if self._device.type == "sdm.devices.types.CAMERA":
|
||||
return "Camera"
|
||||
if self._device.type == "sdm.devices.types.DISPLAY":
|
||||
return "Display"
|
||||
if self._device.type == "sdm.devices.types.DOORBELL":
|
||||
return "Doorbell"
|
||||
if self._device.type == "sdm.devices.types.THERMOSTAT":
|
||||
return "Thermostat"
|
||||
return None
|
||||
return DEVICE_TYPE_MAP.get(self._device.type)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity is added to register update signal handler."""
|
||||
|
||||
async def async_update_state():
|
||||
"""Update sensor state."""
|
||||
await self.async_update_ha_state(True)
|
||||
|
||||
# Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted
|
||||
# here to re-fresh the signals from _device. Unregister this callback
|
||||
# when the entity is removed.
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, async_update_state)
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
SIGNAL_NEST_UPDATE,
|
||||
self.async_write_ha_state,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
@@ -25,7 +25,8 @@
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||
"authorize_url_fail": "Unknown error generating an authorize url."
|
||||
"authorize_url_fail": "Unknown error generating an authorize url.",
|
||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"name": "Netatmo",
|
||||
"documentation": "https://www.home-assistant.io/integrations/netatmo",
|
||||
"requirements": [
|
||||
"pyatmo==4.1.0"
|
||||
"pyatmo==4.2.0"
|
||||
],
|
||||
"after_dependencies": [
|
||||
"cloud",
|
||||
|
@@ -45,10 +45,10 @@ class NetatmoBase(Entity):
|
||||
data_class["name"],
|
||||
signal_name,
|
||||
self.async_update_callback,
|
||||
LAT_NE=data_class["LAT_NE"],
|
||||
LON_NE=data_class["LON_NE"],
|
||||
LAT_SW=data_class["LAT_SW"],
|
||||
LON_SW=data_class["LON_SW"],
|
||||
lat_ne=data_class["lat_ne"],
|
||||
lon_ne=data_class["lon_ne"],
|
||||
lat_sw=data_class["lat_sw"],
|
||||
lon_sw=data_class["lon_sw"],
|
||||
)
|
||||
|
||||
else:
|
||||
|
@@ -202,10 +202,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
||||
PUBLICDATA_DATA_CLASS_NAME,
|
||||
signal_name,
|
||||
None,
|
||||
LAT_NE=area.lat_ne,
|
||||
LON_NE=area.lon_ne,
|
||||
LAT_SW=area.lat_sw,
|
||||
LON_SW=area.lon_sw,
|
||||
lat_ne=area.lat_ne,
|
||||
lon_ne=area.lon_ne,
|
||||
lat_sw=area.lat_sw,
|
||||
lon_sw=area.lon_sw,
|
||||
)
|
||||
for sensor_type in SUPPORTED_PUBLIC_SENSOR_TYPES:
|
||||
new_entities.append(
|
||||
@@ -473,10 +473,10 @@ class NetatmoPublicSensor(NetatmoBase):
|
||||
self._data_classes.append(
|
||||
{
|
||||
"name": PUBLICDATA_DATA_CLASS_NAME,
|
||||
"LAT_NE": area.lat_ne,
|
||||
"LON_NE": area.lon_ne,
|
||||
"LAT_SW": area.lat_sw,
|
||||
"LON_SW": area.lon_sw,
|
||||
"lat_ne": area.lat_ne,
|
||||
"lon_ne": area.lon_ne,
|
||||
"lat_sw": area.lat_sw,
|
||||
"lon_sw": area.lon_sw,
|
||||
"area_name": area.area_name,
|
||||
SIGNAL_NAME: self._signal_name,
|
||||
}
|
||||
@@ -563,10 +563,10 @@ class NetatmoPublicSensor(NetatmoBase):
|
||||
self._data_classes = [
|
||||
{
|
||||
"name": PUBLICDATA_DATA_CLASS_NAME,
|
||||
"LAT_NE": area.lat_ne,
|
||||
"LON_NE": area.lon_ne,
|
||||
"LAT_SW": area.lat_sw,
|
||||
"LON_SW": area.lon_sw,
|
||||
"lat_ne": area.lat_ne,
|
||||
"lon_ne": area.lon_ne,
|
||||
"lat_sw": area.lat_sw,
|
||||
"lon_sw": area.lon_sw,
|
||||
"area_name": area.area_name,
|
||||
SIGNAL_NAME: self._signal_name,
|
||||
}
|
||||
@@ -577,10 +577,10 @@ class NetatmoPublicSensor(NetatmoBase):
|
||||
PUBLICDATA_DATA_CLASS_NAME,
|
||||
self._signal_name,
|
||||
self.async_update_callback,
|
||||
LAT_NE=area.lat_ne,
|
||||
LON_NE=area.lon_ne,
|
||||
LAT_SW=area.lat_sw,
|
||||
LON_SW=area.lon_sw,
|
||||
lat_ne=area.lat_ne,
|
||||
lon_ne=area.lon_ne,
|
||||
lat_sw=area.lat_sw,
|
||||
lon_sw=area.lon_sw,
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@@ -133,7 +133,7 @@ class BaseNotificationService:
|
||||
|
||||
if title:
|
||||
title.hass = self.hass
|
||||
kwargs[ATTR_TITLE] = title.async_render()
|
||||
kwargs[ATTR_TITLE] = title.async_render(parse_result=False)
|
||||
|
||||
if self._registered_targets.get(service.service) is not None:
|
||||
kwargs[ATTR_TARGET] = [self._registered_targets[service.service]]
|
||||
@@ -141,7 +141,7 @@ class BaseNotificationService:
|
||||
kwargs[ATTR_TARGET] = service.data.get(ATTR_TARGET)
|
||||
|
||||
message.hass = self.hass
|
||||
kwargs[ATTR_MESSAGE] = message.async_render()
|
||||
kwargs[ATTR_MESSAGE] = message.async_render(parse_result=False)
|
||||
kwargs[ATTR_DATA] = service.data.get(ATTR_DATA)
|
||||
|
||||
await self.async_send_message(**kwargs)
|
||||
@@ -229,12 +229,12 @@ async def async_setup(hass, config):
|
||||
payload = {}
|
||||
message = service.data[ATTR_MESSAGE]
|
||||
message.hass = hass
|
||||
payload[ATTR_MESSAGE] = message.async_render()
|
||||
payload[ATTR_MESSAGE] = message.async_render(parse_result=False)
|
||||
|
||||
title = service.data.get(ATTR_TITLE)
|
||||
if title:
|
||||
title.hass = hass
|
||||
payload[ATTR_TITLE] = title.async_render()
|
||||
payload[ATTR_TITLE] = title.async_render(parse_result=False)
|
||||
|
||||
await hass.services.async_call(
|
||||
pn.DOMAIN, pn.SERVICE_CREATE, payload, blocking=True
|
||||
|
@@ -74,6 +74,14 @@ class EventManager:
|
||||
async def async_start(self) -> bool:
|
||||
"""Start polling events."""
|
||||
if await self.device.create_pullpoint_subscription():
|
||||
# Create subscription manager
|
||||
self._subscription = self.device.create_subscription_service(
|
||||
"PullPointSubscription"
|
||||
)
|
||||
|
||||
# Renew immediately
|
||||
await self.async_renew()
|
||||
|
||||
# Initialize events
|
||||
pullpoint = self.device.create_pullpoint_service()
|
||||
try:
|
||||
@@ -87,11 +95,6 @@ class EventManager:
|
||||
# Parse event initialization
|
||||
await self.async_parse_messages(response.NotificationMessage)
|
||||
|
||||
# Create subscription manager
|
||||
self._subscription = self.device.create_subscription_service(
|
||||
"PullPointSubscription"
|
||||
)
|
||||
|
||||
self.started = True
|
||||
return True
|
||||
|
||||
|
@@ -245,7 +245,9 @@ class Remote:
|
||||
"""Return device info."""
|
||||
if self._control is None:
|
||||
return None
|
||||
return await self._handle_errors(self._control.get_device_info)
|
||||
device_info = await self._handle_errors(self._control.get_device_info)
|
||||
_LOGGER.debug("Fetched device info: %s", str(device_info))
|
||||
return device_info
|
||||
|
||||
async def _handle_errors(self, func, *args):
|
||||
"""Handle errors from func, set available and reconnect if needed."""
|
||||
|
@@ -18,4 +18,7 @@ ATTR_MANUFACTURER = "manufacturer"
|
||||
ATTR_MODEL_NUMBER = "modelNumber"
|
||||
ATTR_UDN = "UDN"
|
||||
|
||||
DEFAULT_MANUFACTURER = "Panasonic"
|
||||
DEFAULT_MODEL_NUMBER = "Panasonic Viera"
|
||||
|
||||
ERROR_INVALID_PIN_CODE = "invalid_pin_code"
|
||||
|
@@ -26,6 +26,8 @@ from .const import (
|
||||
ATTR_MODEL_NUMBER,
|
||||
ATTR_REMOTE,
|
||||
ATTR_UDN,
|
||||
DEFAULT_MANUFACTURER,
|
||||
DEFAULT_MODEL_NUMBER,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
@@ -69,11 +71,11 @@ class PanasonicVieraTVEntity(MediaPlayerEntity):
|
||||
self._device_info = device_info
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of the device."""
|
||||
if self._device_info is not None:
|
||||
return self._device_info[ATTR_UDN]
|
||||
if self._device_info is None:
|
||||
return None
|
||||
return self._device_info[ATTR_UDN]
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
@@ -83,8 +85,10 @@ class PanasonicVieraTVEntity(MediaPlayerEntity):
|
||||
return {
|
||||
"name": self._name,
|
||||
"identifiers": {(DOMAIN, self._device_info[ATTR_UDN])},
|
||||
"manufacturer": self._device_info[ATTR_MANUFACTURER],
|
||||
"model": self._device_info[ATTR_MODEL_NUMBER],
|
||||
"manufacturer": self._device_info.get(
|
||||
ATTR_MANUFACTURER, DEFAULT_MANUFACTURER
|
||||
),
|
||||
"model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER),
|
||||
}
|
||||
|
||||
@property
|
||||
|
@@ -119,7 +119,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||
if title is not None:
|
||||
try:
|
||||
title.hass = hass
|
||||
title = title.async_render()
|
||||
title = title.async_render(parse_result=False)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error("Error rendering title %s: %s", title, ex)
|
||||
title = title.template
|
||||
@@ -128,7 +128,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||
|
||||
try:
|
||||
message.hass = hass
|
||||
message = message.async_render()
|
||||
message = message.async_render(parse_result=False)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error("Error rendering message %s: %s", message, ex)
|
||||
message = message.template
|
||||
|
@@ -55,7 +55,7 @@ class Pi4ioe5v9Switch(SwitchEntity):
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._pin = pin
|
||||
self._invert_logic = invert_logic
|
||||
self._state = False
|
||||
self._state = not invert_logic
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@@ -289,6 +289,8 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
async def async_step_plex_website_auth(self):
|
||||
"""Begin external auth flow on Plex website."""
|
||||
self.hass.http.register_view(PlexAuthorizationCallbackView)
|
||||
hass_url = get_url(self.hass)
|
||||
headers = {"Origin": hass_url}
|
||||
payload = {
|
||||
"X-Plex-Device-Name": X_PLEX_DEVICE_NAME,
|
||||
"X-Plex-Version": X_PLEX_VERSION,
|
||||
@@ -298,9 +300,9 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"X-Plex-Model": "Plex OAuth",
|
||||
}
|
||||
session = async_get_clientsession(self.hass)
|
||||
self.plexauth = PlexAuth(payload, session)
|
||||
self.plexauth = PlexAuth(payload, session, headers)
|
||||
await self.plexauth.initiate_auth()
|
||||
forward_url = f"{get_url(self.hass)}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
|
||||
forward_url = f"{hass_url}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
|
||||
auth_url = self.plexauth.auth_url(forward_url)
|
||||
return self.async_external_step(step_id="obtain_token", url=auth_url)
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/plex",
|
||||
"requirements": [
|
||||
"plexapi==4.1.1",
|
||||
"plexauth==0.0.5",
|
||||
"plexauth==0.0.6",
|
||||
"plexwebsocket==0.0.12"
|
||||
],
|
||||
"dependencies": ["http"],
|
||||
|
@@ -84,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
if resource_template is not None:
|
||||
resource_template.hass = hass
|
||||
resource = resource_template.render()
|
||||
resource = resource_template.async_render(parse_result=False)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
@@ -189,6 +189,6 @@ class RestBinarySensor(BinarySensorEntity):
|
||||
async def async_update(self):
|
||||
"""Get the latest data from REST API and updates the state."""
|
||||
if self._resource_template is not None:
|
||||
self.rest.set_url(self._resource_template.render())
|
||||
self.rest.set_url(self._resource_template.async_render(parse_result=False))
|
||||
|
||||
await self.rest.async_update()
|
||||
|
@@ -163,7 +163,7 @@ class RestNotificationService(BaseNotificationService):
|
||||
key: _data_template_creator(item) for key, item in value.items()
|
||||
}
|
||||
value.hass = self._hass
|
||||
return value.async_render(kwargs)
|
||||
return value.async_render(kwargs, parse_result=False)
|
||||
|
||||
data.update(_data_template_creator(self._data_template))
|
||||
|
||||
|
@@ -103,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
if resource_template is not None:
|
||||
resource_template.hass = hass
|
||||
resource = resource_template.render()
|
||||
resource = resource_template.async_render(parse_result=False)
|
||||
|
||||
if username and password:
|
||||
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
|
||||
@@ -202,7 +202,7 @@ class RestSensor(Entity):
|
||||
async def async_update(self):
|
||||
"""Get the latest data from REST API and update the state."""
|
||||
if self._resource_template is not None:
|
||||
self.rest.set_url(self._resource_template.render())
|
||||
self.rest.set_url(self._resource_template.async_render(parse_result=False))
|
||||
|
||||
await self.rest.async_update()
|
||||
|
||||
|
@@ -162,7 +162,7 @@ class RestSwitch(SwitchEntity):
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
body_on_t = self._body_on.async_render()
|
||||
body_on_t = self._body_on.async_render(parse_result=False)
|
||||
|
||||
try:
|
||||
req = await self.set_device_state(body_on_t)
|
||||
@@ -178,7 +178,7 @@ class RestSwitch(SwitchEntity):
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
body_off_t = self._body_off.async_render()
|
||||
body_off_t = self._body_off.async_render(parse_result=False)
|
||||
|
||||
try:
|
||||
req = await self.set_device_state(body_off_t)
|
||||
|
@@ -93,17 +93,22 @@ async def async_setup(hass, config):
|
||||
payload = None
|
||||
if template_payload:
|
||||
payload = bytes(
|
||||
template_payload.async_render(variables=service.data), "utf-8"
|
||||
template_payload.async_render(
|
||||
variables=service.data, parse_result=False
|
||||
),
|
||||
"utf-8",
|
||||
)
|
||||
|
||||
request_url = template_url.async_render(variables=service.data)
|
||||
request_url = template_url.async_render(
|
||||
variables=service.data, parse_result=False
|
||||
)
|
||||
|
||||
headers = None
|
||||
if template_headers:
|
||||
headers = {}
|
||||
for header_name, template_header in template_headers.items():
|
||||
headers[header_name] = template_header.async_render(
|
||||
variables=service.data
|
||||
variables=service.data, parse_result=False
|
||||
)
|
||||
|
||||
if content_type:
|
||||
|
@@ -49,6 +49,9 @@ from .const import (
|
||||
CONF_OFF_DELAY,
|
||||
CONF_REMOVE_DEVICE,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DATA_CLEANUP_CALLBACKS,
|
||||
DATA_LISTENER,
|
||||
DATA_RFXOBJECT,
|
||||
DEVICE_PACKET_TYPE_LIGHTING4,
|
||||
EVENT_RFXTRX_EVENT,
|
||||
SERVICE_SEND,
|
||||
@@ -93,8 +96,6 @@ DATA_TYPES = OrderedDict(
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_RFXOBJECT = "rfxobject"
|
||||
DATA_LISTENER = "ha_stop"
|
||||
|
||||
|
||||
def _bytearray_string(data):
|
||||
@@ -188,6 +189,8 @@ async def async_setup_entry(hass, entry: config_entries.ConfigEntry):
|
||||
"""Set up the RFXtrx component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS] = []
|
||||
|
||||
await async_setup_internal(hass, entry)
|
||||
|
||||
for domain in DOMAINS:
|
||||
@@ -212,12 +215,17 @@ async def async_unload_entry(hass, entry: config_entries.ConfigEntry):
|
||||
|
||||
hass.services.async_remove(DOMAIN, SERVICE_SEND)
|
||||
|
||||
for cleanup_callback in hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS]:
|
||||
cleanup_callback()
|
||||
|
||||
listener = hass.data[DOMAIN][DATA_LISTENER]
|
||||
listener()
|
||||
|
||||
rfx_object = hass.data[DOMAIN][DATA_RFXOBJECT]
|
||||
await hass.async_add_executor_job(rfx_object.close_connection)
|
||||
|
||||
hass.data.pop(DOMAIN)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -428,6 +436,14 @@ def get_device_id(device, data_bits=None):
|
||||
return (f"{device.packettype:x}", f"{device.subtype:x}", id_string)
|
||||
|
||||
|
||||
def connect_auto_add(hass, entry_data, callback_fun):
|
||||
"""Connect to dispatcher for automatic add."""
|
||||
if entry_data[CONF_AUTOMATIC_ADD]:
|
||||
hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS].append(
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, callback_fun)
|
||||
)
|
||||
|
||||
|
||||
class RfxtrxEntity(RestoreEntity):
|
||||
"""Represents a Rfxtrx device.
|
||||
|
||||
|
@@ -19,11 +19,10 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers import event as evt
|
||||
|
||||
from . import (
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
CONF_OFF_DELAY,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxEntity,
|
||||
connect_auto_add,
|
||||
find_possible_pt2262_device,
|
||||
get_device_id,
|
||||
get_pt2262_cmd,
|
||||
@@ -147,10 +146,7 @@ async def async_setup_entry(
|
||||
async_add_entities([sensor])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
if discovery_info[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_EVENT, binary_sensor_update
|
||||
)
|
||||
connect_auto_add(hass, discovery_info, binary_sensor_update)
|
||||
|
||||
|
||||
class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
||||
|
@@ -360,10 +360,14 @@ class OptionsFlow(config_entries.OptionsFlow):
|
||||
"""Check if device can be replaced with selected device."""
|
||||
device_data = self._get_device_data(entry_id)
|
||||
event_code = device_data[CONF_EVENT_CODE]
|
||||
|
||||
if event_code is not None:
|
||||
rfx_obj = get_rfx_object(event_code)
|
||||
if (
|
||||
rfx_obj.device.packettype == self._selected_device_object.device.packettype
|
||||
and rfx_obj.device.subtype == self._selected_device_object.device.subtype
|
||||
rfx_obj.device.packettype
|
||||
== self._selected_device_object.device.packettype
|
||||
and rfx_obj.device.subtype
|
||||
== self._selected_device_object.device.subtype
|
||||
and self._selected_device_event_code != event_code
|
||||
):
|
||||
return True
|
||||
|
@@ -33,3 +33,7 @@ SERVICE_SEND = "send"
|
||||
DEVICE_PACKET_TYPE_LIGHTING4 = 0x13
|
||||
|
||||
EVENT_RFXTRX_EVENT = "rfxtrx_event"
|
||||
|
||||
DATA_RFXOBJECT = "rfxobject"
|
||||
DATA_LISTENER = "ha_stop"
|
||||
DATA_CLEANUP_CALLBACKS = "cleanup_callbacks"
|
||||
|
@@ -6,12 +6,11 @@ from homeassistant.const import CONF_DEVICES, STATE_OPEN
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import (
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxCommandEntity,
|
||||
connect_auto_add,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
@@ -81,8 +80,7 @@ async def async_setup_entry(
|
||||
async_add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
if discovery_info[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, cover_update)
|
||||
connect_auto_add(hass, discovery_info, cover_update)
|
||||
|
||||
|
||||
class RfxtrxCover(RfxtrxCommandEntity, CoverEntity):
|
||||
|
@@ -12,12 +12,11 @@ from homeassistant.const import CONF_DEVICES, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import (
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxCommandEntity,
|
||||
connect_auto_add,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
@@ -95,8 +94,7 @@ async def async_setup_entry(
|
||||
async_add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
if discovery_info[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, light_update)
|
||||
connect_auto_add(hass, discovery_info, light_update)
|
||||
|
||||
|
||||
class RfxtrxLight(RfxtrxCommandEntity, LightEntity):
|
||||
|
@@ -20,11 +20,10 @@ from homeassistant.const import (
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import (
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
DATA_TYPES,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxEntity,
|
||||
connect_auto_add,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
@@ -127,8 +126,7 @@ async def async_setup_entry(
|
||||
async_add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
if discovery_info[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, sensor_update)
|
||||
connect_auto_add(hass, discovery_info, sensor_update)
|
||||
|
||||
|
||||
class RfxtrxSensor(RfxtrxEntity):
|
||||
|
@@ -8,13 +8,12 @@ from homeassistant.const import CONF_DEVICES, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import (
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
DOMAIN,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxCommandEntity,
|
||||
connect_auto_add,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
@@ -92,8 +91,7 @@ async def async_setup_entry(
|
||||
async_add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
if discovery_info[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, switch_update)
|
||||
connect_auto_add(hass, discovery_info, switch_update)
|
||||
|
||||
|
||||
class RfxtrxSwitch(RfxtrxCommandEntity, SwitchEntity):
|
||||
|
@@ -263,10 +263,10 @@ class RMVDepartureData:
|
||||
if not dest_found:
|
||||
continue
|
||||
|
||||
elif self._lines and journey["number"] not in self._lines:
|
||||
if self._lines and journey["number"] not in self._lines:
|
||||
continue
|
||||
|
||||
elif journey["minutes"] < self._time_offset:
|
||||
if journey["minutes"] < self._time_offset:
|
||||
continue
|
||||
|
||||
for attr in ["direction", "departure_time", "product", "minutes"]:
|
||||
|
@@ -123,9 +123,6 @@ class PwmSimpleLed(LightEntity, RestoreEntity):
|
||||
self._brightness = last_state.attributes.get(
|
||||
"brightness", DEFAULT_BRIGHTNESS
|
||||
)
|
||||
self._led.set(
|
||||
is_on=self._is_on, brightness=_from_hass_brightness(self._brightness)
|
||||
)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@@ -199,7 +196,6 @@ class PwmRgbLed(PwmSimpleLed):
|
||||
last_state = await self.async_get_last_state()
|
||||
if last_state:
|
||||
self._color = last_state.attributes.get("hs_color", DEFAULT_COLOR)
|
||||
self._led.set(color=_from_hass_color(self._color))
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "rpi_gpio_pwm",
|
||||
"name": "pigpio Daemon PWM LED",
|
||||
"documentation": "https://www.home-assistant.io/integrations/rpi_gpio_pwm",
|
||||
"requirements": ["pwmled==1.5.3"],
|
||||
"codeowners": []
|
||||
"requirements": ["pwmled==1.6.7"],
|
||||
"codeowners": ["@soldag"]
|
||||
}
|
||||
|
@@ -83,17 +83,19 @@ class RssView(HomeAssistantView):
|
||||
|
||||
response += "<rss>\n"
|
||||
if self._title is not None:
|
||||
response += " <title>%s</title>\n" % escape(self._title.async_render())
|
||||
response += " <title>%s</title>\n" % escape(
|
||||
self._title.async_render(parse_result=False)
|
||||
)
|
||||
|
||||
for item in self._items:
|
||||
response += " <item>\n"
|
||||
if "title" in item:
|
||||
response += " <title>"
|
||||
response += escape(item["title"].async_render())
|
||||
response += escape(item["title"].async_render(parse_result=False))
|
||||
response += "</title>\n"
|
||||
if "description" in item:
|
||||
response += " <description>"
|
||||
response += escape(item["description"].async_render())
|
||||
response += escape(item["description"].async_render(parse_result=False))
|
||||
response += "</description>\n"
|
||||
response += " </item>\n"
|
||||
|
||||
|
@@ -45,7 +45,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
||||
|
||||
if args_compiled:
|
||||
try:
|
||||
rendered_args = args_compiled.async_render(service.data)
|
||||
rendered_args = args_compiled.async_render(
|
||||
variables=service.data, parse_result=False
|
||||
)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.exception("Error rendering command template: %s", ex)
|
||||
return
|
||||
|
@@ -3,6 +3,7 @@ import asyncio
|
||||
from uuid import UUID
|
||||
|
||||
from simplipy import API
|
||||
from simplipy.entity import EntityTypes
|
||||
from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError
|
||||
from simplipy.websocket import (
|
||||
EVENT_CAMERA_MOTION_DETECTED,
|
||||
@@ -590,6 +591,13 @@ class SimpliSafeEntity(CoordinatorEntity):
|
||||
else:
|
||||
self._serial = system.serial
|
||||
|
||||
try:
|
||||
sensor_type = EntityTypes(
|
||||
simplisafe.initial_event_to_use[system.system_id].get("sensorType")
|
||||
)
|
||||
except ValueError:
|
||||
sensor_type = EntityTypes.unknown
|
||||
|
||||
self._attrs = {
|
||||
ATTR_LAST_EVENT_INFO: simplisafe.initial_event_to_use[system.system_id].get(
|
||||
"info"
|
||||
@@ -597,9 +605,7 @@ class SimpliSafeEntity(CoordinatorEntity):
|
||||
ATTR_LAST_EVENT_SENSOR_NAME: simplisafe.initial_event_to_use[
|
||||
system.system_id
|
||||
].get("sensorName"),
|
||||
ATTR_LAST_EVENT_SENSOR_TYPE: simplisafe.initial_event_to_use[
|
||||
system.system_id
|
||||
].get("sensorType"),
|
||||
ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type.name,
|
||||
ATTR_LAST_EVENT_TIMESTAMP: simplisafe.initial_event_to_use[
|
||||
system.system_id
|
||||
].get("eventTimestamp"),
|
||||
@@ -724,3 +730,23 @@ class SimpliSafeEntity(CoordinatorEntity):
|
||||
@callback
|
||||
def async_update_from_websocket_event(self, event):
|
||||
"""Update the entity with the provided websocket event."""
|
||||
|
||||
|
||||
class SimpliSafeBaseSensor(SimpliSafeEntity):
|
||||
"""Define a SimpliSafe base (binary) sensor."""
|
||||
|
||||
def __init__(self, simplisafe, system, sensor):
|
||||
"""Initialize."""
|
||||
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
|
||||
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
|
||||
self._device_info["model"] = sensor.type.name
|
||||
self._device_info["name"] = sensor.name
|
||||
self._sensor = sensor
|
||||
self._sensor_type_human_name = " ".join(
|
||||
[w.title() for w in self._sensor.type.name.split("_")]
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return f"{self._system.address} {self._name} {self._sensor_type_human_name}"
|
||||
|
@@ -159,7 +159,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity):
|
||||
try:
|
||||
await self._system.set_off()
|
||||
except SimplipyError as err:
|
||||
LOGGER.error('Error while disarming "%s": %s', self._system.name, err)
|
||||
LOGGER.error('Error while disarming "%s": %s', self._system.system_id, err)
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
@@ -172,7 +172,9 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity):
|
||||
try:
|
||||
await self._system.set_home()
|
||||
except SimplipyError as err:
|
||||
LOGGER.error('Error while arming "%s" (home): %s', self._system.name, err)
|
||||
LOGGER.error(
|
||||
'Error while arming "%s" (home): %s', self._system.system_id, err
|
||||
)
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
@@ -185,7 +187,9 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity):
|
||||
try:
|
||||
await self._system.set_away()
|
||||
except SimplipyError as err:
|
||||
LOGGER.error('Error while arming "%s" (away): %s', self._system.name, err)
|
||||
LOGGER.error(
|
||||
'Error while arming "%s" (away): %s', self._system.system_id, err
|
||||
)
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMING
|
||||
|
@@ -6,42 +6,32 @@ from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_GAS,
|
||||
DEVICE_CLASS_MOISTURE,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import SimpliSafeEntity
|
||||
from . import SimpliSafeBaseSensor
|
||||
from .const import DATA_CLIENT, DOMAIN, LOGGER
|
||||
|
||||
SUPPORTED_BATTERY_SENSOR_TYPES = [
|
||||
EntityTypes.carbon_monoxide,
|
||||
EntityTypes.entry,
|
||||
EntityTypes.leak,
|
||||
EntityTypes.lock,
|
||||
EntityTypes.lock_keypad,
|
||||
EntityTypes.smoke,
|
||||
EntityTypes.temperature,
|
||||
]
|
||||
|
||||
SUPPORTED_SENSOR_TYPES = [
|
||||
EntityTypes.entry,
|
||||
EntityTypes.carbon_monoxide,
|
||||
EntityTypes.smoke,
|
||||
EntityTypes.leak,
|
||||
]
|
||||
|
||||
HA_SENSOR_TYPES = {
|
||||
EntityTypes.entry: DEVICE_CLASS_DOOR,
|
||||
TRIGGERED_SENSOR_TYPES = {
|
||||
EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS,
|
||||
EntityTypes.smoke: DEVICE_CLASS_SMOKE,
|
||||
EntityTypes.entry: DEVICE_CLASS_DOOR,
|
||||
EntityTypes.glass_break: DEVICE_CLASS_SAFETY,
|
||||
EntityTypes.leak: DEVICE_CLASS_MOISTURE,
|
||||
}
|
||||
|
||||
SENSOR_MODELS = {
|
||||
EntityTypes.entry: "Entry Sensor",
|
||||
EntityTypes.carbon_monoxide: "Carbon Monoxide Detector",
|
||||
EntityTypes.smoke: "Smoke Detector",
|
||||
EntityTypes.leak: "Water Sensor",
|
||||
EntityTypes.motion: DEVICE_CLASS_MOTION,
|
||||
EntityTypes.smoke: DEVICE_CLASS_SMOKE,
|
||||
}
|
||||
|
||||
|
||||
@@ -56,37 +46,34 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
||||
continue
|
||||
|
||||
for sensor in system.sensors.values():
|
||||
if sensor.type in SUPPORTED_SENSOR_TYPES:
|
||||
sensors.append(SimpliSafeBinarySensor(simplisafe, system, sensor))
|
||||
if sensor.type in TRIGGERED_SENSOR_TYPES:
|
||||
sensors.append(
|
||||
TriggeredBinarySensor(
|
||||
simplisafe,
|
||||
system,
|
||||
sensor,
|
||||
TRIGGERED_SENSOR_TYPES[sensor.type],
|
||||
)
|
||||
)
|
||||
if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES:
|
||||
sensors.append(SimpliSafeSensorBattery(simplisafe, system, sensor))
|
||||
sensors.append(BatteryBinarySensor(simplisafe, system, sensor))
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity):
|
||||
"""Define a SimpliSafe binary sensor entity."""
|
||||
class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
|
||||
"""Define a binary sensor related to whether an entity has been triggered."""
|
||||
|
||||
def __init__(self, simplisafe, system, sensor):
|
||||
def __init__(self, simplisafe, system, sensor, device_class):
|
||||
"""Initialize."""
|
||||
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
|
||||
self._system = system
|
||||
self._sensor = sensor
|
||||
super().__init__(simplisafe, system, sensor)
|
||||
self._device_class = device_class
|
||||
self._is_on = False
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return type of sensor."""
|
||||
return HA_SENSOR_TYPES[self._sensor.type]
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
info = super().device_info
|
||||
info["identifiers"] = {(DOMAIN, self._sensor.serial)}
|
||||
info["model"] = SENSOR_MODELS[self._sensor.type]
|
||||
info["name"] = self._sensor.name
|
||||
return info
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -99,19 +86,14 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity):
|
||||
self._is_on = self._sensor.triggered
|
||||
|
||||
|
||||
class SimpliSafeSensorBattery(SimpliSafeEntity, BinarySensorEntity):
|
||||
class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
|
||||
"""Define a SimpliSafe battery binary sensor entity."""
|
||||
|
||||
def __init__(self, simplisafe, system, sensor):
|
||||
"""Initialize."""
|
||||
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
|
||||
self._sensor = sensor
|
||||
super().__init__(simplisafe, system, sensor)
|
||||
self._is_low = False
|
||||
|
||||
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
|
||||
self._device_info["model"] = SENSOR_MODELS[sensor.type]
|
||||
self._device_info["name"] = sensor.name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return type of sensor."""
|
||||
|
@@ -3,6 +3,6 @@
|
||||
"name": "SimpliSafe",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
|
||||
"requirements": ["simplisafe-python==9.5.1"],
|
||||
"requirements": ["simplisafe-python==9.6.0"],
|
||||
"codeowners": ["@bachya"]
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ from simplipy.entity import EntityTypes
|
||||
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import SimpliSafeEntity
|
||||
from . import SimpliSafeBaseSensor
|
||||
from .const import DATA_CLIENT, DOMAIN, LOGGER
|
||||
|
||||
|
||||
@@ -25,19 +25,14 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
class SimplisafeFreezeSensor(SimpliSafeEntity):
|
||||
class SimplisafeFreezeSensor(SimpliSafeBaseSensor):
|
||||
"""Define a SimpliSafe freeze sensor entity."""
|
||||
|
||||
def __init__(self, simplisafe, system, sensor):
|
||||
"""Initialize."""
|
||||
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
|
||||
self._sensor = sensor
|
||||
super().__init__(simplisafe, system, sensor)
|
||||
self._state = None
|
||||
|
||||
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
|
||||
self._device_info["model"] = "Freeze Sensor"
|
||||
self._device_info["name"] = sensor.name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return type of sensor."""
|
||||
|
@@ -118,7 +118,7 @@ def _async_templatize_blocks(hass, value):
|
||||
}
|
||||
|
||||
tmpl = template.Template(value, hass=hass)
|
||||
return tmpl.async_render()
|
||||
return tmpl.async_render(parse_result=False)
|
||||
|
||||
|
||||
class SlackNotificationService(BaseNotificationService):
|
||||
|
@@ -98,7 +98,7 @@ async def async_setup(hass, config):
|
||||
for site_id in site_ids:
|
||||
payload = json.dumps({"siteId": site_id})
|
||||
hass.components.mqtt.async_publish(
|
||||
FEEDBACK_ON_TOPIC, None, qos=0, retain=False
|
||||
FEEDBACK_ON_TOPIC, "", qos=0, retain=False
|
||||
)
|
||||
hass.components.mqtt.async_publish(
|
||||
topic, payload, qos=int(state), retain=state
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"domain": "spotify",
|
||||
"name": "Spotify",
|
||||
"documentation": "https://www.home-assistant.io/integrations/spotify",
|
||||
"requirements": ["spotipy==2.16.0"],
|
||||
"requirements": ["spotipy==2.16.1"],
|
||||
"zeroconf": ["_spotify-connect._tcp.local."],
|
||||
"dependencies": ["http"],
|
||||
"codeowners": ["@frenck"],
|
||||
|
@@ -65,7 +65,7 @@ CONTENT_TYPE_TO_CHILD_TYPE = {
|
||||
"Genres": MEDIA_TYPE_GENRE,
|
||||
}
|
||||
|
||||
BROWSE_LIMIT = 500
|
||||
BROWSE_LIMIT = 1000
|
||||
|
||||
|
||||
async def build_item_response(player, payload):
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"@rajlaud"
|
||||
],
|
||||
"requirements": [
|
||||
"pysqueezebox==0.5.1"
|
||||
"pysqueezebox==0.5.4"
|
||||
],
|
||||
"config_flow": true
|
||||
}
|
||||
|
@@ -20,3 +20,6 @@ MIN_SEGMENT_DURATION = 1.5 # Each segment is at least this many seconds
|
||||
|
||||
PACKETS_TO_WAIT_FOR_AUDIO = 20 # Some streams have an audio stream with no audio
|
||||
MAX_TIMESTAMP_GAP = 10000 # seconds - anything from 10 to 50000 is probably reasonable
|
||||
|
||||
MAX_MISSING_DTS = 6 # Number of packets missing DTS to allow
|
||||
STREAM_TIMEOUT = 30 # Timeout for reading stream
|
||||
|
@@ -6,7 +6,13 @@ import time
|
||||
|
||||
import av
|
||||
|
||||
from .const import MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO
|
||||
from .const import (
|
||||
MAX_MISSING_DTS,
|
||||
MAX_TIMESTAMP_GAP,
|
||||
MIN_SEGMENT_DURATION,
|
||||
PACKETS_TO_WAIT_FOR_AUDIO,
|
||||
STREAM_TIMEOUT,
|
||||
)
|
||||
from .core import Segment, StreamBuffer
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -62,7 +68,7 @@ def stream_worker(hass, stream, quit_event):
|
||||
def _stream_worker_internal(hass, stream, quit_event):
|
||||
"""Handle consuming streams."""
|
||||
|
||||
container = av.open(stream.source, options=stream.options)
|
||||
container = av.open(stream.source, options=stream.options, timeout=STREAM_TIMEOUT)
|
||||
try:
|
||||
video_stream = container.streams.video[0]
|
||||
except (KeyError, IndexError):
|
||||
@@ -81,13 +87,15 @@ def _stream_worker_internal(hass, stream, quit_event):
|
||||
if audio_stream and audio_stream.profile is None:
|
||||
audio_stream = None
|
||||
|
||||
# Iterator for demuxing
|
||||
container_packets = None
|
||||
# The presentation timestamps of the first packet in each stream we receive
|
||||
# Use to adjust before muxing or outputting, but we don't adjust internally
|
||||
first_pts = {}
|
||||
# The decoder timestamps of the latest packet in each stream we processed
|
||||
last_dts = None
|
||||
# Keep track of consecutive packets without a dts to detect end of stream.
|
||||
last_packet_was_without_dts = False
|
||||
missing_dts = 0
|
||||
# Holds the buffers for each stream provider
|
||||
outputs = None
|
||||
# Keep track of the number of segments we've processed
|
||||
@@ -102,8 +110,8 @@ def _stream_worker_internal(hass, stream, quit_event):
|
||||
# 2 - seeking can be problematic https://trac.ffmpeg.org/ticket/7815
|
||||
|
||||
def peek_first_pts():
|
||||
nonlocal first_pts, audio_stream
|
||||
missing_dts = False
|
||||
nonlocal first_pts, audio_stream, container_packets
|
||||
missing_dts = 0
|
||||
|
||||
def empty_stream_dict():
|
||||
return {
|
||||
@@ -112,17 +120,20 @@ def _stream_worker_internal(hass, stream, quit_event):
|
||||
}
|
||||
|
||||
try:
|
||||
container_packets = container.demux((video_stream, audio_stream))
|
||||
first_packet = empty_stream_dict()
|
||||
first_pts = empty_stream_dict()
|
||||
# Get to first video keyframe
|
||||
while first_packet[video_stream] is None:
|
||||
packet = next(container.demux())
|
||||
packet = next(container_packets)
|
||||
if (
|
||||
packet.dts is None
|
||||
): # Allow single packet with no dts, raise error on second
|
||||
if missing_dts:
|
||||
raise av.AVError
|
||||
missing_dts = True
|
||||
): # Allow MAX_MISSING_DTS packets with no dts, raise error on the next one
|
||||
if missing_dts >= MAX_MISSING_DTS:
|
||||
raise StopIteration(
|
||||
f"Invalid data - got {MAX_MISSING_DTS+1} packets with missing DTS while initializing"
|
||||
)
|
||||
missing_dts += 1
|
||||
continue
|
||||
if packet.stream == video_stream and packet.is_keyframe:
|
||||
first_packet[video_stream] = packet
|
||||
@@ -131,13 +142,15 @@ def _stream_worker_internal(hass, stream, quit_event):
|
||||
while any(
|
||||
[pts is None for pts in {**first_packet, **first_pts}.values()]
|
||||
) and (len(initial_packets) < PACKETS_TO_WAIT_FOR_AUDIO):
|
||||
packet = next(container.demux((video_stream, audio_stream)))
|
||||
packet = next(container_packets)
|
||||
if (
|
||||
packet.dts is None
|
||||
): # Allow single packet with no dts, raise error on second
|
||||
if missing_dts:
|
||||
raise av.AVError
|
||||
missing_dts = True
|
||||
): # Allow MAX_MISSING_DTS packet with no dts, raise error on the next one
|
||||
if missing_dts >= MAX_MISSING_DTS:
|
||||
raise StopIteration(
|
||||
f"Invalid data - got {MAX_MISSING_DTS+1} packets with missing DTS while initializing"
|
||||
)
|
||||
missing_dts += 1
|
||||
continue
|
||||
if (
|
||||
first_packet[packet.stream] is None
|
||||
@@ -223,16 +236,16 @@ def _stream_worker_internal(hass, stream, quit_event):
|
||||
if len(initial_packets) > 0:
|
||||
packet = initial_packets.popleft()
|
||||
else:
|
||||
packet = next(container.demux((video_stream, audio_stream)))
|
||||
packet = next(container_packets)
|
||||
if packet.dts is None:
|
||||
_LOGGER.error("Stream packet without dts detected, skipping...")
|
||||
# Allow a single packet without dts before terminating the stream.
|
||||
if last_packet_was_without_dts:
|
||||
# If we get a "flushing" packet, the stream is done
|
||||
raise StopIteration("No dts in consecutive packets")
|
||||
last_packet_was_without_dts = True
|
||||
# Allow MAX_MISSING_DTS consecutive packets without dts. Terminate the stream on the next one.
|
||||
if missing_dts >= MAX_MISSING_DTS:
|
||||
raise StopIteration(
|
||||
f"No dts in {MAX_MISSING_DTS+1} consecutive packets"
|
||||
)
|
||||
missing_dts += 1
|
||||
continue
|
||||
last_packet_was_without_dts = False
|
||||
missing_dts = 0
|
||||
except (av.AVError, StopIteration) as ex:
|
||||
_LOGGER.error("Error demuxing stream: %s", str(ex))
|
||||
finalize_stream()
|
||||
|
@@ -258,10 +258,9 @@ class SynoApi:
|
||||
self._entry.data[CONF_PASSWORD],
|
||||
self._entry.data[CONF_SSL],
|
||||
timeout=self._entry.options.get(CONF_TIMEOUT),
|
||||
device_token=self._entry.data.get("device_token"),
|
||||
)
|
||||
await self._hass.async_add_executor_job(
|
||||
self.dsm.login, self._entry.data.get("device_token")
|
||||
)
|
||||
await self._hass.async_add_executor_job(self.dsm.login)
|
||||
|
||||
self._with_surveillance_station = bool(
|
||||
self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)
|
||||
@@ -316,6 +315,8 @@ class SynoApi:
|
||||
)
|
||||
self._with_surveillance_station = bool(
|
||||
self._fetching_entities.get(SynoSurveillanceStation.CAMERA_API_KEY)
|
||||
) or bool(
|
||||
self._fetching_entities.get(SynoSurveillanceStation.HOME_MODE_API_KEY)
|
||||
)
|
||||
|
||||
# Reset not used API, information is not reset since it's used in device_info
|
||||
|
@@ -275,7 +275,6 @@ def _login_and_fetch_syno_info(api, otp_code):
|
||||
if (
|
||||
not api.information.serial
|
||||
or api.utilisation.cpu_user_load is None
|
||||
or not api.storage.disks_ids
|
||||
or not api.storage.volumes_ids
|
||||
or not api.network.macs
|
||||
):
|
||||
|
@@ -19,12 +19,10 @@ from homeassistant.components.mqtt.subscription import (
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from . import device_automation, discovery
|
||||
from .const import CONF_DISCOVERY_PREFIX, DATA_REMOVE_DISCOVER_COMPONENT, PLATFORMS
|
||||
from .discovery import TASMOTA_DISCOVERY_DEVICE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,13 +53,11 @@ async def async_setup_entry(hass, entry):
|
||||
|
||||
tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics)
|
||||
|
||||
async def async_discover_device(config, mac):
|
||||
"""Discover and add a Tasmota device."""
|
||||
await async_setup_device(hass, mac, config, entry, tasmota_mqtt)
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
|
||||
hass.data[
|
||||
DATA_REMOVE_DISCOVER_COMPONENT.format("device")
|
||||
] = async_dispatcher_connect(hass, TASMOTA_DISCOVERY_DEVICE, async_discover_device)
|
||||
def async_discover_device(config, mac):
|
||||
"""Discover and add a Tasmota device."""
|
||||
async_setup_device(hass, mac, config, entry, tasmota_mqtt, device_registry)
|
||||
|
||||
async def start_platforms():
|
||||
await device_automation.async_setup_entry(hass, entry)
|
||||
@@ -73,7 +69,9 @@ async def async_setup_entry(hass, entry):
|
||||
)
|
||||
|
||||
discovery_prefix = entry.data[CONF_DISCOVERY_PREFIX]
|
||||
await discovery.async_start(hass, discovery_prefix, entry, tasmota_mqtt)
|
||||
await discovery.async_start(
|
||||
hass, discovery_prefix, entry, tasmota_mqtt, async_discover_device
|
||||
)
|
||||
|
||||
hass.async_create_task(start_platforms())
|
||||
return True
|
||||
@@ -97,7 +95,6 @@ async def async_unload_entry(hass, entry):
|
||||
# disable discovery
|
||||
await discovery.async_stop(hass)
|
||||
hass.data.pop(DEVICE_MACS)
|
||||
hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format("device")]()
|
||||
hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation"))()
|
||||
for component in PLATFORMS:
|
||||
hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(component))()
|
||||
@@ -105,9 +102,8 @@ async def async_unload_entry(hass, entry):
|
||||
return True
|
||||
|
||||
|
||||
async def _remove_device(hass, config_entry, mac, tasmota_mqtt):
|
||||
def _remove_device(hass, config_entry, mac, tasmota_mqtt, device_registry):
|
||||
"""Remove device from device registry."""
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get_device(set(), {(CONNECTION_NETWORK_MAC, mac)})
|
||||
|
||||
if device is None:
|
||||
@@ -118,9 +114,8 @@ async def _remove_device(hass, config_entry, mac, tasmota_mqtt):
|
||||
clear_discovery_topic(mac, config_entry.data[CONF_DISCOVERY_PREFIX], tasmota_mqtt)
|
||||
|
||||
|
||||
async def _update_device(hass, config_entry, config):
|
||||
def _update_device(hass, config_entry, config, device_registry):
|
||||
"""Add or update device registry."""
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
config_entry_id = config_entry.entry_id
|
||||
device_info = {
|
||||
"connections": {(CONNECTION_NETWORK_MAC, config[CONF_MAC])},
|
||||
@@ -135,9 +130,9 @@ async def _update_device(hass, config_entry, config):
|
||||
hass.data[DEVICE_MACS][device.id] = config[CONF_MAC]
|
||||
|
||||
|
||||
async def async_setup_device(hass, mac, config, config_entry, tasmota_mqtt):
|
||||
def async_setup_device(hass, mac, config, config_entry, tasmota_mqtt, device_registry):
|
||||
"""Set up the Tasmota device."""
|
||||
if not config:
|
||||
await _remove_device(hass, config_entry, mac, tasmota_mqtt)
|
||||
_remove_device(hass, config_entry, mac, tasmota_mqtt, device_registry)
|
||||
else:
|
||||
await _update_device(hass, config_entry, config)
|
||||
_update_device(hass, config_entry, config, device_registry)
|
||||
|
@@ -21,7 +21,6 @@ from .const import DOMAIN, PLATFORMS
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ALREADY_DISCOVERED = "tasmota_discovered_components"
|
||||
TASMOTA_DISCOVERY_DEVICE = "tasmota_discovery_device"
|
||||
TASMOTA_DISCOVERY_ENTITY_NEW = "tasmota_discovery_entity_new_{}"
|
||||
TASMOTA_DISCOVERY_ENTITY_UPDATED = "tasmota_discovery_entity_updated_{}_{}_{}_{}"
|
||||
TASMOTA_DISCOVERY_INSTANCE = "tasmota_discovery_instance"
|
||||
@@ -29,6 +28,9 @@ TASMOTA_DISCOVERY_INSTANCE = "tasmota_discovery_instance"
|
||||
|
||||
def clear_discovery_hash(hass, discovery_hash):
|
||||
"""Clear entry in ALREADY_DISCOVERED list."""
|
||||
if ALREADY_DISCOVERED not in hass.data:
|
||||
# Discovery is shutting down
|
||||
return
|
||||
del hass.data[ALREADY_DISCOVERED][discovery_hash]
|
||||
|
||||
|
||||
@@ -38,7 +40,7 @@ def set_discovery_hash(hass, discovery_hash):
|
||||
|
||||
|
||||
async def async_start(
|
||||
hass: HomeAssistantType, discovery_topic, config_entry, tasmota_mqtt
|
||||
hass: HomeAssistantType, discovery_topic, config_entry, tasmota_mqtt, setup_device
|
||||
) -> bool:
|
||||
"""Start Tasmota device discovery."""
|
||||
|
||||
@@ -92,9 +94,7 @@ async def async_start(
|
||||
|
||||
_LOGGER.debug("Received discovery data for tasmota device: %s", mac)
|
||||
tasmota_device_config = tasmota_get_device_config(payload)
|
||||
async_dispatcher_send(
|
||||
hass, TASMOTA_DISCOVERY_DEVICE, tasmota_device_config, mac
|
||||
)
|
||||
setup_device(tasmota_device_config, mac)
|
||||
|
||||
if not payload:
|
||||
return
|
||||
|
@@ -80,10 +80,11 @@ class TasmotaLight(
|
||||
|
||||
self._setup_from_entity()
|
||||
|
||||
async def discovery_update(self, update):
|
||||
async def discovery_update(self, update, write_state=True):
|
||||
"""Handle updated discovery message."""
|
||||
await super().discovery_update(update, write_state=False)
|
||||
self._setup_from_entity()
|
||||
await super().discovery_update(update)
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _setup_from_entity(self):
|
||||
"""(Re)Setup the entity."""
|
||||
@@ -127,6 +128,11 @@ class TasmotaLight(
|
||||
white_value = float(attributes["white_value"])
|
||||
percent_white = white_value / TASMOTA_BRIGHTNESS_MAX
|
||||
self._white_value = percent_white * 255
|
||||
if self._white_value == 0:
|
||||
self._color_temp = None
|
||||
self._white_value = None
|
||||
if self._white_value is not None and self._white_value > 0:
|
||||
self._hs = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"name": "Tasmota (beta)",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
||||
"requirements": ["hatasmota==0.0.20"],
|
||||
"requirements": ["hatasmota==0.0.25.1"],
|
||||
"dependencies": ["mqtt"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"codeowners": ["@emontnemery"]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user