mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Migrate emulate_hue to use storage to fix I/O in event loop (#50473)
This commit is contained in:
parent
72f342aa5b
commit
70961c79a0
@ -1,5 +1,4 @@
|
|||||||
"""Support for local control of entities by emulating a Philips Hue bridge."""
|
"""Support for local control of entities by emulating a Philips Hue bridge."""
|
||||||
from contextlib import suppress
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@ -12,9 +11,8 @@ from homeassistant.const import (
|
|||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.helpers import storage
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.json import load_json, save_json
|
|
||||||
|
|
||||||
from .hue_api import (
|
from .hue_api import (
|
||||||
HueAllGroupsStateView,
|
HueAllGroupsStateView,
|
||||||
@ -34,6 +32,9 @@ DOMAIN = "emulated_hue"
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
NUMBERS_FILE = "emulated_hue_ids.json"
|
NUMBERS_FILE = "emulated_hue_ids.json"
|
||||||
|
DATA_KEY = "emulated_hue.ids"
|
||||||
|
DATA_VERSION = "1"
|
||||||
|
SAVE_DELAY = 60
|
||||||
|
|
||||||
CONF_ADVERTISE_IP = "advertise_ip"
|
CONF_ADVERTISE_IP = "advertise_ip"
|
||||||
CONF_ADVERTISE_PORT = "advertise_port"
|
CONF_ADVERTISE_PORT = "advertise_port"
|
||||||
@ -155,6 +156,7 @@ async def async_setup(hass, yaml_config):
|
|||||||
nonlocal protocol
|
nonlocal protocol
|
||||||
nonlocal site
|
nonlocal site
|
||||||
nonlocal runner
|
nonlocal runner
|
||||||
|
await config.async_setup()
|
||||||
|
|
||||||
_, protocol = await listen
|
_, protocol = await listen
|
||||||
|
|
||||||
@ -189,6 +191,7 @@ class Config:
|
|||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.type = conf.get(CONF_TYPE)
|
self.type = conf.get(CONF_TYPE)
|
||||||
self.numbers = None
|
self.numbers = None
|
||||||
|
self.store = None
|
||||||
self.cached_states = {}
|
self.cached_states = {}
|
||||||
self._exposed_cache = {}
|
self._exposed_cache = {}
|
||||||
|
|
||||||
@ -257,14 +260,21 @@ class Config:
|
|||||||
# for compatibility with older installations.
|
# for compatibility with older installations.
|
||||||
self.lights_all_dimmable = conf.get(CONF_LIGHTS_ALL_DIMMABLE)
|
self.lights_all_dimmable = conf.get(CONF_LIGHTS_ALL_DIMMABLE)
|
||||||
|
|
||||||
|
async def async_setup(self):
|
||||||
|
"""Set up and migrate to storage."""
|
||||||
|
self.store = storage.Store(self.hass, DATA_VERSION, DATA_KEY)
|
||||||
|
self.numbers = (
|
||||||
|
await storage.async_migrator(
|
||||||
|
self.hass, self.hass.config.path(NUMBERS_FILE), self.store
|
||||||
|
)
|
||||||
|
or {}
|
||||||
|
)
|
||||||
|
|
||||||
def entity_id_to_number(self, entity_id):
|
def entity_id_to_number(self, entity_id):
|
||||||
"""Get a unique number for the entity id."""
|
"""Get a unique number for the entity id."""
|
||||||
if self.type == TYPE_ALEXA:
|
if self.type == TYPE_ALEXA:
|
||||||
return entity_id
|
return entity_id
|
||||||
|
|
||||||
if self.numbers is None:
|
|
||||||
self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE))
|
|
||||||
|
|
||||||
# Google Home
|
# Google Home
|
||||||
for number, ent_id in self.numbers.items():
|
for number, ent_id in self.numbers.items():
|
||||||
if entity_id == ent_id:
|
if entity_id == ent_id:
|
||||||
@ -274,7 +284,7 @@ class Config:
|
|||||||
if self.numbers:
|
if self.numbers:
|
||||||
number = str(max(int(k) for k in self.numbers) + 1)
|
number = str(max(int(k) for k in self.numbers) + 1)
|
||||||
self.numbers[number] = entity_id
|
self.numbers[number] = entity_id
|
||||||
save_json(self.hass.config.path(NUMBERS_FILE), self.numbers)
|
self.store.async_delay_save(lambda: self.numbers, SAVE_DELAY)
|
||||||
return number
|
return number
|
||||||
|
|
||||||
def number_to_entity_id(self, number):
|
def number_to_entity_id(self, number):
|
||||||
@ -282,9 +292,6 @@ class Config:
|
|||||||
if self.type == TYPE_ALEXA:
|
if self.type == TYPE_ALEXA:
|
||||||
return number
|
return number
|
||||||
|
|
||||||
if self.numbers is None:
|
|
||||||
self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE))
|
|
||||||
|
|
||||||
# Google Home
|
# Google Home
|
||||||
assert isinstance(number, str)
|
assert isinstance(number, str)
|
||||||
return self.numbers.get(number)
|
return self.numbers.get(number)
|
||||||
@ -338,10 +345,3 @@ class Config:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _load_json(filename):
|
|
||||||
"""Load JSON, handling invalid syntax."""
|
|
||||||
with suppress(HomeAssistantError):
|
|
||||||
return load_json(filename)
|
|
||||||
return {}
|
|
||||||
|
@ -1,106 +1,101 @@
|
|||||||
"""Test the Emulated Hue component."""
|
"""Test the Emulated Hue component."""
|
||||||
from unittest.mock import MagicMock, Mock, patch
|
from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.components.emulated_hue import Config
|
from homeassistant.components.emulated_hue import (
|
||||||
|
DATA_KEY,
|
||||||
|
DATA_VERSION,
|
||||||
|
SAVE_DELAY,
|
||||||
|
Config,
|
||||||
|
)
|
||||||
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
def test_config_google_home_entity_id_to_number():
|
async def test_config_google_home_entity_id_to_number(hass, hass_storage):
|
||||||
"""Test config adheres to the type."""
|
"""Test config adheres to the type."""
|
||||||
mock_hass = Mock()
|
conf = Config(hass, {"type": "google_home"})
|
||||||
mock_hass.config.path = MagicMock("path", return_value="test_path")
|
hass_storage[DATA_KEY] = {
|
||||||
conf = Config(mock_hass, {"type": "google_home"})
|
"version": DATA_VERSION,
|
||||||
|
"key": DATA_KEY,
|
||||||
|
"data": {"1": "light.test2"},
|
||||||
|
}
|
||||||
|
|
||||||
with patch(
|
await conf.async_setup()
|
||||||
"homeassistant.components.emulated_hue.load_json",
|
|
||||||
return_value={"1": "light.test2"},
|
|
||||||
) as json_loader, patch(
|
|
||||||
"homeassistant.components.emulated_hue.save_json"
|
|
||||||
) as json_saver:
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
|
||||||
assert number == "2"
|
|
||||||
|
|
||||||
assert json_saver.mock_calls[0][1][1] == {
|
number = conf.entity_id_to_number("light.test")
|
||||||
"1": "light.test2",
|
assert number == "2"
|
||||||
"2": "light.test",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert json_saver.call_count == 1
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY))
|
||||||
assert json_loader.call_count == 1
|
await hass.async_block_till_done()
|
||||||
|
assert hass_storage[DATA_KEY]["data"] == {
|
||||||
|
"1": "light.test2",
|
||||||
|
"2": "light.test",
|
||||||
|
}
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
number = conf.entity_id_to_number("light.test")
|
||||||
assert number == "2"
|
assert number == "2"
|
||||||
assert json_saver.call_count == 1
|
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test2")
|
number = conf.entity_id_to_number("light.test2")
|
||||||
assert number == "1"
|
assert number == "1"
|
||||||
assert json_saver.call_count == 1
|
|
||||||
|
|
||||||
entity_id = conf.number_to_entity_id("1")
|
entity_id = conf.number_to_entity_id("1")
|
||||||
assert entity_id == "light.test2"
|
assert entity_id == "light.test2"
|
||||||
|
|
||||||
|
|
||||||
def test_config_google_home_entity_id_to_number_altered():
|
async def test_config_google_home_entity_id_to_number_altered(hass, hass_storage):
|
||||||
"""Test config adheres to the type."""
|
"""Test config adheres to the type."""
|
||||||
mock_hass = Mock()
|
conf = Config(hass, {"type": "google_home"})
|
||||||
mock_hass.config.path = MagicMock("path", return_value="test_path")
|
hass_storage[DATA_KEY] = {
|
||||||
conf = Config(mock_hass, {"type": "google_home"})
|
"version": DATA_VERSION,
|
||||||
|
"key": DATA_KEY,
|
||||||
|
"data": {"21": "light.test2"},
|
||||||
|
}
|
||||||
|
|
||||||
with patch(
|
await conf.async_setup()
|
||||||
"homeassistant.components.emulated_hue.load_json",
|
|
||||||
return_value={"21": "light.test2"},
|
|
||||||
) as json_loader, patch(
|
|
||||||
"homeassistant.components.emulated_hue.save_json"
|
|
||||||
) as json_saver:
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
|
||||||
assert number == "22"
|
|
||||||
assert json_saver.call_count == 1
|
|
||||||
assert json_loader.call_count == 1
|
|
||||||
|
|
||||||
assert json_saver.mock_calls[0][1][1] == {
|
number = conf.entity_id_to_number("light.test")
|
||||||
"21": "light.test2",
|
assert number == "22"
|
||||||
"22": "light.test",
|
|
||||||
}
|
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY))
|
||||||
assert number == "22"
|
await hass.async_block_till_done()
|
||||||
assert json_saver.call_count == 1
|
assert hass_storage[DATA_KEY]["data"] == {
|
||||||
|
"21": "light.test2",
|
||||||
|
"22": "light.test",
|
||||||
|
}
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test2")
|
number = conf.entity_id_to_number("light.test")
|
||||||
assert number == "21"
|
assert number == "22"
|
||||||
assert json_saver.call_count == 1
|
|
||||||
|
|
||||||
entity_id = conf.number_to_entity_id("21")
|
number = conf.entity_id_to_number("light.test2")
|
||||||
assert entity_id == "light.test2"
|
assert number == "21"
|
||||||
|
|
||||||
|
entity_id = conf.number_to_entity_id("21")
|
||||||
|
assert entity_id == "light.test2"
|
||||||
|
|
||||||
|
|
||||||
def test_config_google_home_entity_id_to_number_empty():
|
async def test_config_google_home_entity_id_to_number_empty(hass, hass_storage):
|
||||||
"""Test config adheres to the type."""
|
"""Test config adheres to the type."""
|
||||||
mock_hass = Mock()
|
conf = Config(hass, {"type": "google_home"})
|
||||||
mock_hass.config.path = MagicMock("path", return_value="test_path")
|
hass_storage[DATA_KEY] = {"version": DATA_VERSION, "key": DATA_KEY, "data": {}}
|
||||||
conf = Config(mock_hass, {"type": "google_home"})
|
|
||||||
|
|
||||||
with patch(
|
await conf.async_setup()
|
||||||
"homeassistant.components.emulated_hue.load_json", return_value={}
|
|
||||||
) as json_loader, patch(
|
|
||||||
"homeassistant.components.emulated_hue.save_json"
|
|
||||||
) as json_saver:
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
|
||||||
assert number == "1"
|
|
||||||
assert json_saver.call_count == 1
|
|
||||||
assert json_loader.call_count == 1
|
|
||||||
|
|
||||||
assert json_saver.mock_calls[0][1][1] == {"1": "light.test"}
|
number = conf.entity_id_to_number("light.test")
|
||||||
|
assert number == "1"
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test")
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY))
|
||||||
assert number == "1"
|
await hass.async_block_till_done()
|
||||||
assert json_saver.call_count == 1
|
assert hass_storage[DATA_KEY]["data"] == {"1": "light.test"}
|
||||||
|
|
||||||
number = conf.entity_id_to_number("light.test2")
|
number = conf.entity_id_to_number("light.test")
|
||||||
assert number == "2"
|
assert number == "1"
|
||||||
assert json_saver.call_count == 2
|
|
||||||
|
|
||||||
entity_id = conf.number_to_entity_id("2")
|
number = conf.entity_id_to_number("light.test2")
|
||||||
assert entity_id == "light.test2"
|
assert number == "2"
|
||||||
|
|
||||||
|
entity_id = conf.number_to_entity_id("2")
|
||||||
|
assert entity_id == "light.test2"
|
||||||
|
|
||||||
|
|
||||||
def test_config_alexa_entity_id_to_number():
|
def test_config_alexa_entity_id_to_number():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user