mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Migrate lamarzocco to lmcloud 1.1 (#113935)
* migrate to 1.1 * bump to 1.1.1 * fix newlines docstring * cleanup entity_description fns * strict generics * restructure import * tweaks to generics * tweaks to generics * removed exceptions * move initialization, websocket clean shutdown * get rid of duplicate entry addign * bump lmcloud * re-add calendar, auto on/off switches * use asdict for diagnostics * change number generator * use name as entry title * also migrate title * don't migrate title * remove generics for now * satisfy mypy * add s * adapt * migrate entry.runtime_data * remove auto/onoff * add issue on wrong gw firmware * silence mypy * remove breaks in ha version * parametrize issue test * Update update.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update test_config_flow.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * regen snapshots * mapping steam level * remove commented code * fix typo * coderabbitai availability tweak * remove microsecond moving * additonal schedule for coverage * be more specific on date offset * keep mappings the same * config_entry imports sharpened * remove unneccessary testcase, clenup date moving * remove superfluous calendar testcase from diag * guard against future version downgrade * use new entry for downgrade test * switch to lmcloud 1.1.11 * revert runtimedata * revert runtimedata * version to helper * conistent Generator * generator from typing_extensions --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
b7f74532dc
commit
42b984ee4f
@ -1,10 +1,31 @@
|
|||||||
"""The La Marzocco integration."""
|
"""The La Marzocco integration."""
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
import logging
|
||||||
from homeassistant.const import Platform
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from lmcloud.client_bluetooth import LaMarzoccoBluetoothClient
|
||||||
|
from lmcloud.client_cloud import LaMarzoccoCloudClient
|
||||||
|
from lmcloud.client_local import LaMarzoccoLocalClient
|
||||||
|
from lmcloud.const import BT_MODEL_PREFIXES, FirmwareType
|
||||||
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
from homeassistant.components.bluetooth import async_discovered_service_info
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_MAC,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_TOKEN,
|
||||||
|
CONF_USERNAME,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
from homeassistant.helpers.httpx_client import get_async_client
|
||||||
|
|
||||||
|
from .const import CONF_USE_BLUETOOTH, DOMAIN
|
||||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
from .coordinator import LaMarzoccoUpdateCoordinator
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
@ -18,15 +39,89 @@ PLATFORMS = [
|
|||||||
Platform.UPDATE,
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up La Marzocco as config entry."""
|
"""Set up La Marzocco as config entry."""
|
||||||
|
|
||||||
coordinator = LaMarzoccoUpdateCoordinator(hass)
|
assert entry.unique_id
|
||||||
|
serial = entry.unique_id
|
||||||
|
|
||||||
|
cloud_client = LaMarzoccoCloudClient(
|
||||||
|
username=entry.data[CONF_USERNAME],
|
||||||
|
password=entry.data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize local API
|
||||||
|
local_client: LaMarzoccoLocalClient | None = None
|
||||||
|
if (host := entry.data.get(CONF_HOST)) is not None:
|
||||||
|
_LOGGER.debug("Initializing local API")
|
||||||
|
local_client = LaMarzoccoLocalClient(
|
||||||
|
host=host,
|
||||||
|
local_bearer=entry.data[CONF_TOKEN],
|
||||||
|
client=get_async_client(hass),
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize Bluetooth
|
||||||
|
bluetooth_client: LaMarzoccoBluetoothClient | None = None
|
||||||
|
if entry.options.get(CONF_USE_BLUETOOTH, True):
|
||||||
|
|
||||||
|
def bluetooth_configured() -> bool:
|
||||||
|
return entry.data.get(CONF_MAC, "") and entry.data.get(CONF_NAME, "")
|
||||||
|
|
||||||
|
if not bluetooth_configured():
|
||||||
|
for discovery_info in async_discovered_service_info(hass):
|
||||||
|
if (
|
||||||
|
(name := discovery_info.name)
|
||||||
|
and name.startswith(BT_MODEL_PREFIXES)
|
||||||
|
and name.split("_")[1] == serial
|
||||||
|
):
|
||||||
|
_LOGGER.debug("Found Bluetooth device, configuring with Bluetooth")
|
||||||
|
# found a device, add MAC address to config entry
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data={
|
||||||
|
**entry.data,
|
||||||
|
CONF_MAC: discovery_info.address,
|
||||||
|
CONF_NAME: discovery_info.name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if bluetooth_configured():
|
||||||
|
_LOGGER.debug("Initializing Bluetooth device")
|
||||||
|
bluetooth_client = LaMarzoccoBluetoothClient(
|
||||||
|
username=entry.data[CONF_USERNAME],
|
||||||
|
serial_number=serial,
|
||||||
|
token=entry.data[CONF_TOKEN],
|
||||||
|
address_or_ble_device=entry.data[CONF_MAC],
|
||||||
|
)
|
||||||
|
|
||||||
|
coordinator = LaMarzoccoUpdateCoordinator(
|
||||||
|
hass=hass,
|
||||||
|
local_client=local_client,
|
||||||
|
cloud_client=cloud_client,
|
||||||
|
bluetooth_client=bluetooth_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
await coordinator.async_setup()
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
gateway_version = coordinator.device.firmware[FirmwareType.GATEWAY].current_version
|
||||||
|
if version.parse(gateway_version) < version.parse("v3.5-rc5"):
|
||||||
|
# incompatible gateway firmware, create an issue
|
||||||
|
ir.async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"unsupported_gateway_firmware",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=ir.IssueSeverity.ERROR,
|
||||||
|
translation_key="unsupported_gateway_firmware",
|
||||||
|
translation_placeholders={"gateway_version": gateway_version},
|
||||||
|
)
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
@ -39,10 +134,51 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
|
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Migrate config entry."""
|
||||||
|
if entry.version > 2:
|
||||||
|
# guard against downgrade from a future version
|
||||||
|
return False
|
||||||
|
|
||||||
|
if entry.version == 1:
|
||||||
|
cloud_client = LaMarzoccoCloudClient(
|
||||||
|
username=entry.data[CONF_USERNAME],
|
||||||
|
password=entry.data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
fleet = await cloud_client.get_customer_fleet()
|
||||||
|
except (AuthFail, RequestNotSuccessful) as exc:
|
||||||
|
_LOGGER.error("Migration failed with error %s", exc)
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert entry.unique_id is not None
|
||||||
|
device = fleet[entry.unique_id]
|
||||||
|
v2_data = {
|
||||||
|
CONF_USERNAME: entry.data[CONF_USERNAME],
|
||||||
|
CONF_PASSWORD: entry.data[CONF_PASSWORD],
|
||||||
|
CONF_MODEL: device.model,
|
||||||
|
CONF_NAME: device.name,
|
||||||
|
CONF_TOKEN: device.communication_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
if CONF_HOST in entry.data:
|
||||||
|
v2_data[CONF_HOST] = entry.data[CONF_HOST]
|
||||||
|
|
||||||
|
if CONF_MAC in entry.data:
|
||||||
|
v2_data[CONF_MAC] = entry.data[CONF_MAC]
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data=v2_data,
|
||||||
|
version=2,
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Migrated La Marzocco config entry to version 2")
|
||||||
|
return True
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.models import LaMarzoccoMachineConfig
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
@ -26,7 +26,7 @@ class LaMarzoccoBinarySensorEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco binary sensor."""
|
"""Description of a La Marzocco binary sensor."""
|
||||||
|
|
||||||
is_on_fn: Callable[[LaMarzoccoClient], bool]
|
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||||
@ -34,7 +34,7 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
|||||||
key="water_tank",
|
key="water_tank",
|
||||||
translation_key="water_tank",
|
translation_key="water_tank",
|
||||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||||
is_on_fn=lambda lm: not lm.current_status.get("water_reservoir_contact"),
|
is_on_fn=lambda config: not config.water_contact,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
||||||
),
|
),
|
||||||
@ -42,8 +42,8 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
|||||||
key="brew_active",
|
key="brew_active",
|
||||||
translation_key="brew_active",
|
translation_key="brew_active",
|
||||||
device_class=BinarySensorDeviceClass.RUNNING,
|
device_class=BinarySensorDeviceClass.RUNNING,
|
||||||
is_on_fn=lambda lm: bool(lm.current_status.get("brew_active")),
|
is_on_fn=lambda config: config.brew_active,
|
||||||
available_fn=lambda lm: lm.websocket_connected,
|
available_fn=lambda device: device.websocket_connected,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -72,4 +72,4 @@ class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
return self.entity_description.is_on_fn(self.coordinator.lm)
|
return self.entity_description.is_on_fn(self.coordinator.device.config)
|
||||||
|
@ -4,7 +4,7 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
|
||||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -22,14 +22,14 @@ class LaMarzoccoButtonEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco button."""
|
"""Description of a La Marzocco button."""
|
||||||
|
|
||||||
press_fn: Callable[[LaMarzoccoClient], Coroutine[Any, Any, None]]
|
press_fn: Callable[[LaMarzoccoMachine], Coroutine[Any, Any, None]]
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoButtonEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoButtonEntityDescription, ...] = (
|
||||||
LaMarzoccoButtonEntityDescription(
|
LaMarzoccoButtonEntityDescription(
|
||||||
key="start_backflush",
|
key="start_backflush",
|
||||||
translation_key="start_backflush",
|
translation_key="start_backflush",
|
||||||
press_fn=lambda lm: lm.start_backflush(),
|
press_fn=lambda machine: machine.start_backflush(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,4 +56,4 @@ class LaMarzoccoButtonEntity(LaMarzoccoEntity, ButtonEntity):
|
|||||||
|
|
||||||
async def async_press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Press button."""
|
"""Press button."""
|
||||||
await self.entity_description.press_fn(self.coordinator.lm)
|
await self.entity_description.press_fn(self.coordinator.device)
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from lmcloud.models import LaMarzoccoWakeUpSleepEntry
|
||||||
|
|
||||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -10,10 +12,21 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .coordinator import LaMarzoccoUpdateCoordinator
|
||||||
from .entity import LaMarzoccoBaseEntity
|
from .entity import LaMarzoccoBaseEntity
|
||||||
|
|
||||||
CALENDAR_KEY = "auto_on_off_schedule"
|
CALENDAR_KEY = "auto_on_off_schedule"
|
||||||
|
|
||||||
|
DAY_OF_WEEK = [
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday",
|
||||||
|
"sunday",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -23,7 +36,10 @@ async def async_setup_entry(
|
|||||||
"""Set up switch entities and services."""
|
"""Set up switch entities and services."""
|
||||||
|
|
||||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
async_add_entities([LaMarzoccoCalendarEntity(coordinator, CALENDAR_KEY)])
|
async_add_entities(
|
||||||
|
LaMarzoccoCalendarEntity(coordinator, CALENDAR_KEY, wake_up_sleep_entry)
|
||||||
|
for wake_up_sleep_entry in coordinator.device.config.wake_up_sleep_entries.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
||||||
@ -31,6 +47,17 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
|||||||
|
|
||||||
_attr_translation_key = CALENDAR_KEY
|
_attr_translation_key = CALENDAR_KEY
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: LaMarzoccoUpdateCoordinator,
|
||||||
|
key: str,
|
||||||
|
wake_up_sleep_entry: LaMarzoccoWakeUpSleepEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Set up calendar."""
|
||||||
|
super().__init__(coordinator, f"{key}_{wake_up_sleep_entry.entry_id}")
|
||||||
|
self.wake_up_sleep_entry = wake_up_sleep_entry
|
||||||
|
self._attr_translation_placeholders = {"id": wake_up_sleep_entry.entry_id}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event(self) -> CalendarEvent | None:
|
def event(self) -> CalendarEvent | None:
|
||||||
"""Return the next upcoming event."""
|
"""Return the next upcoming event."""
|
||||||
@ -85,29 +112,36 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
|||||||
"""Return calendar event for a given weekday."""
|
"""Return calendar event for a given weekday."""
|
||||||
|
|
||||||
# check first if auto/on off is turned on in general
|
# check first if auto/on off is turned on in general
|
||||||
# because could still be on for that day but disabled
|
if not self.wake_up_sleep_entry.enabled:
|
||||||
if self.coordinator.lm.current_status["global_auto"] != "Enabled":
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# parse the schedule for the day
|
# parse the schedule for the day
|
||||||
schedule_day = self.coordinator.lm.schedule[date.weekday()]
|
|
||||||
if schedule_day["enable"] == "Disabled":
|
if DAY_OF_WEEK[date.weekday()] not in self.wake_up_sleep_entry.days:
|
||||||
return None
|
return None
|
||||||
hour_on, minute_on = schedule_day["on"].split(":")
|
|
||||||
hour_off, minute_off = schedule_day["off"].split(":")
|
hour_on, minute_on = self.wake_up_sleep_entry.time_on.split(":")
|
||||||
|
hour_off, minute_off = self.wake_up_sleep_entry.time_off.split(":")
|
||||||
|
|
||||||
|
# if off time is 24:00, then it means the off time is the next day
|
||||||
|
# only for legacy schedules
|
||||||
|
day_offset = 0
|
||||||
|
if hour_off == "24":
|
||||||
|
day_offset = 1
|
||||||
|
hour_off = "0"
|
||||||
|
|
||||||
|
end_date = date.replace(
|
||||||
|
hour=int(hour_off),
|
||||||
|
minute=int(minute_off),
|
||||||
|
)
|
||||||
|
end_date += timedelta(days=day_offset)
|
||||||
|
|
||||||
return CalendarEvent(
|
return CalendarEvent(
|
||||||
start=date.replace(
|
start=date.replace(
|
||||||
hour=int(hour_on),
|
hour=int(hour_on),
|
||||||
minute=int(minute_on),
|
minute=int(minute_on),
|
||||||
second=0,
|
|
||||||
microsecond=0,
|
|
||||||
),
|
|
||||||
end=date.replace(
|
|
||||||
hour=int(hour_off),
|
|
||||||
minute=int(minute_off),
|
|
||||||
second=0,
|
|
||||||
microsecond=0,
|
|
||||||
),
|
),
|
||||||
|
end=end_date,
|
||||||
summary=f"Machine {self.coordinator.config_entry.title} on",
|
summary=f"Machine {self.coordinator.config_entry.title} on",
|
||||||
description="Machine is scheduled to turn on at the start time and off at the end time",
|
description="Machine is scheduled to turn on at the start time and off at the end time",
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,10 @@ from collections.abc import Mapping
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.client_cloud import LaMarzoccoCloudClient
|
||||||
|
from lmcloud.client_local import LaMarzoccoLocalClient
|
||||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||||
|
from lmcloud.models import LaMarzoccoDeviceInfo
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import BluetoothServiceInfo
|
from homeassistant.components.bluetooth import BluetoothServiceInfo
|
||||||
@ -19,12 +21,15 @@ from homeassistant.config_entries import (
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_MAC,
|
CONF_MAC,
|
||||||
|
CONF_MODEL,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
|
CONF_TOKEN,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.httpx_client import get_async_client
|
||||||
from homeassistant.helpers.selector import (
|
from homeassistant.helpers.selector import (
|
||||||
SelectOptionDict,
|
SelectOptionDict,
|
||||||
SelectSelector,
|
SelectSelector,
|
||||||
@ -32,7 +37,9 @@ from homeassistant.helpers.selector import (
|
|||||||
SelectSelectorMode,
|
SelectSelectorMode,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
from .const import CONF_USE_BLUETOOTH, DOMAIN
|
||||||
|
|
||||||
|
CONF_MACHINE = "machine"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -40,12 +47,14 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for La Marzocco."""
|
"""Handle a config flow for La Marzocco."""
|
||||||
|
|
||||||
|
VERSION = 2
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize the config flow."""
|
"""Initialize the config flow."""
|
||||||
|
|
||||||
self.reauth_entry: ConfigEntry | None = None
|
self.reauth_entry: ConfigEntry | None = None
|
||||||
self._config: dict[str, Any] = {}
|
self._config: dict[str, Any] = {}
|
||||||
self._machines: list[tuple[str, str]] = []
|
self._fleet: dict[str, LaMarzoccoDeviceInfo] = {}
|
||||||
self._discovered: dict[str, str] = {}
|
self._discovered: dict[str, str] = {}
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
@ -65,9 +74,12 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
**self._discovered,
|
**self._discovered,
|
||||||
}
|
}
|
||||||
|
|
||||||
lm = LaMarzoccoClient()
|
cloud_client = LaMarzoccoCloudClient(
|
||||||
|
username=data[CONF_USERNAME],
|
||||||
|
password=data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
self._machines = await lm.get_all_machines(data)
|
self._fleet = await cloud_client.get_customer_fleet()
|
||||||
except AuthFail:
|
except AuthFail:
|
||||||
_LOGGER.debug("Server rejected login credentials")
|
_LOGGER.debug("Server rejected login credentials")
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
@ -75,7 +87,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
_LOGGER.error("Error connecting to server: %s", exc)
|
_LOGGER.error("Error connecting to server: %s", exc)
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
if not self._machines:
|
if not self._fleet:
|
||||||
errors["base"] = "no_machines"
|
errors["base"] = "no_machines"
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
@ -88,8 +100,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
return self.async_abort(reason="reauth_successful")
|
return self.async_abort(reason="reauth_successful")
|
||||||
if self._discovered:
|
if self._discovered:
|
||||||
serials = [machine[0] for machine in self._machines]
|
if self._discovered[CONF_MACHINE] not in self._fleet:
|
||||||
if self._discovered[CONF_MACHINE] not in serials:
|
|
||||||
errors["base"] = "machine_not_found"
|
errors["base"] = "machine_not_found"
|
||||||
else:
|
else:
|
||||||
self._config = data
|
self._config = data
|
||||||
@ -128,28 +139,36 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
else:
|
else:
|
||||||
serial_number = self._discovered[CONF_MACHINE]
|
serial_number = self._discovered[CONF_MACHINE]
|
||||||
|
|
||||||
|
selected_device = self._fleet[serial_number]
|
||||||
|
|
||||||
# validate local connection if host is provided
|
# validate local connection if host is provided
|
||||||
if user_input.get(CONF_HOST):
|
if user_input.get(CONF_HOST):
|
||||||
lm = LaMarzoccoClient()
|
if not await LaMarzoccoLocalClient.validate_connection(
|
||||||
if not await lm.check_local_connection(
|
client=get_async_client(self.hass),
|
||||||
credentials=self._config,
|
|
||||||
host=user_input[CONF_HOST],
|
host=user_input[CONF_HOST],
|
||||||
serial=serial_number,
|
token=selected_device.communication_key,
|
||||||
):
|
):
|
||||||
errors[CONF_HOST] = "cannot_connect"
|
errors[CONF_HOST] = "cannot_connect"
|
||||||
|
else:
|
||||||
|
self._config[CONF_HOST] = user_input[CONF_HOST]
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=serial_number,
|
title=selected_device.name,
|
||||||
data=self._config | user_input,
|
data={
|
||||||
|
**self._config,
|
||||||
|
CONF_NAME: selected_device.name,
|
||||||
|
CONF_MODEL: selected_device.model,
|
||||||
|
CONF_TOKEN: selected_device.communication_key,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
machine_options = [
|
machine_options = [
|
||||||
SelectOptionDict(
|
SelectOptionDict(
|
||||||
value=serial_number,
|
value=device.serial_number,
|
||||||
label=f"{model_name} ({serial_number})",
|
label=f"{device.model} ({device.serial_number})",
|
||||||
)
|
)
|
||||||
for serial_number, model_name in self._machines
|
for device in self._fleet.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
machine_selection_schema = vol.Schema(
|
machine_selection_schema = vol.Schema(
|
||||||
|
@ -4,6 +4,4 @@ from typing import Final
|
|||||||
|
|
||||||
DOMAIN: Final = "lamarzocco"
|
DOMAIN: Final = "lamarzocco"
|
||||||
|
|
||||||
CONF_MACHINE: Final = "machine"
|
CONF_USE_BLUETOOTH: Final = "use_bluetooth"
|
||||||
|
|
||||||
CONF_USE_BLUETOOTH = "use_bluetooth"
|
|
||||||
|
@ -3,133 +3,108 @@
|
|||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
from time import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from bleak.backends.device import BLEDevice
|
from lmcloud.client_bluetooth import LaMarzoccoBluetoothClient
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.client_cloud import LaMarzoccoCloudClient
|
||||||
from lmcloud.const import BT_MODEL_NAMES
|
from lmcloud.client_local import LaMarzoccoLocalClient
|
||||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import (
|
|
||||||
async_ble_device_from_address,
|
|
||||||
async_discovered_service_info,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_USERNAME
|
from homeassistant.const import CONF_MODEL, CONF_NAME, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.httpx_client import get_async_client
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=30)
|
SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
|
FIRMWARE_UPDATE_INTERVAL = 3600
|
||||||
|
STATISTICS_UPDATE_INTERVAL = 300
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
NAME_PREFIXES = tuple(BT_MODEL_NAMES)
|
|
||||||
|
|
||||||
|
|
||||||
class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||||
"""Class to handle fetching data from the La Marzocco API centrally."""
|
"""Class to handle fetching data from the La Marzocco API centrally."""
|
||||||
|
|
||||||
config_entry: ConfigEntry
|
config_entry: ConfigEntry
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
cloud_client: LaMarzoccoCloudClient,
|
||||||
|
local_client: LaMarzoccoLocalClient | None,
|
||||||
|
bluetooth_client: LaMarzoccoBluetoothClient | None,
|
||||||
|
) -> None:
|
||||||
"""Initialize coordinator."""
|
"""Initialize coordinator."""
|
||||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
||||||
self.lm = LaMarzoccoClient(
|
self.local_connection_configured = local_client is not None
|
||||||
callback_websocket_notify=self.async_update_listeners,
|
|
||||||
)
|
|
||||||
self.local_connection_configured = (
|
|
||||||
self.config_entry.data.get(CONF_HOST) is not None
|
|
||||||
)
|
|
||||||
self._use_bluetooth = False
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
assert self.config_entry.unique_id
|
||||||
"""Fetch data from API endpoint."""
|
self.device = LaMarzoccoMachine(
|
||||||
|
model=self.config_entry.data[CONF_MODEL],
|
||||||
if not self.lm.initialized:
|
serial_number=self.config_entry.unique_id,
|
||||||
await self._async_init_client()
|
name=self.config_entry.data[CONF_NAME],
|
||||||
|
cloud_client=cloud_client,
|
||||||
await self._async_handle_request(
|
local_client=local_client,
|
||||||
self.lm.update_local_machine_status, force_update=True
|
bluetooth_client=bluetooth_client,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Current status: %s", str(self.lm.current_status))
|
self._last_firmware_data_update: float | None = None
|
||||||
|
self._last_statistics_data_update: float | None = None
|
||||||
|
self._local_client = local_client
|
||||||
|
|
||||||
async def _async_init_client(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Initialize the La Marzocco Client."""
|
"""Set up the coordinator."""
|
||||||
|
if self._local_client is not None:
|
||||||
# Initialize cloud API
|
_LOGGER.debug("Init WebSocket in background task")
|
||||||
_LOGGER.debug("Initializing Cloud API")
|
|
||||||
await self._async_handle_request(
|
|
||||||
self.lm.init_cloud_api,
|
|
||||||
credentials=self.config_entry.data,
|
|
||||||
machine_serial=self.config_entry.data[CONF_MACHINE],
|
|
||||||
)
|
|
||||||
_LOGGER.debug("Model name: %s", self.lm.model_name)
|
|
||||||
|
|
||||||
# initialize local API
|
|
||||||
if (host := self.config_entry.data.get(CONF_HOST)) is not None:
|
|
||||||
_LOGGER.debug("Initializing local API")
|
|
||||||
await self.lm.init_local_api(
|
|
||||||
host=host,
|
|
||||||
client=get_async_client(self.hass),
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER.debug("Init WebSocket in Background Task")
|
|
||||||
|
|
||||||
self.config_entry.async_create_background_task(
|
self.config_entry.async_create_background_task(
|
||||||
hass=self.hass,
|
hass=self.hass,
|
||||||
target=self.lm.lm_local_api.websocket_connect(
|
target=self.device.websocket_connect(
|
||||||
callback=self.lm.on_websocket_message_received,
|
notify_callback=lambda: self.async_set_updated_data(None)
|
||||||
use_sigterm_handler=False,
|
|
||||||
),
|
),
|
||||||
name="lm_websocket_task",
|
name="lm_websocket_task",
|
||||||
)
|
)
|
||||||
|
|
||||||
# initialize Bluetooth
|
async def websocket_close(_: Any | None = None) -> None:
|
||||||
if self.config_entry.options.get(CONF_USE_BLUETOOTH, True):
|
if (
|
||||||
|
self._local_client is not None
|
||||||
|
and self._local_client.websocket is not None
|
||||||
|
and self._local_client.websocket.open
|
||||||
|
):
|
||||||
|
self._local_client.terminating = True
|
||||||
|
await self._local_client.websocket.close()
|
||||||
|
|
||||||
def bluetooth_configured() -> bool:
|
self.config_entry.async_on_unload(
|
||||||
return self.config_entry.data.get(
|
self.hass.bus.async_listen_once(
|
||||||
CONF_MAC, ""
|
EVENT_HOMEASSISTANT_STOP, websocket_close
|
||||||
) and self.config_entry.data.get(CONF_NAME, "")
|
|
||||||
|
|
||||||
if not bluetooth_configured():
|
|
||||||
machine = self.config_entry.data[CONF_MACHINE]
|
|
||||||
for discovery_info in async_discovered_service_info(self.hass):
|
|
||||||
if (
|
|
||||||
(name := discovery_info.name)
|
|
||||||
and name.startswith(NAME_PREFIXES)
|
|
||||||
and name.split("_")[1] == machine
|
|
||||||
):
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Found Bluetooth device, configuring with Bluetooth"
|
|
||||||
)
|
|
||||||
# found a device, add MAC address to config entry
|
|
||||||
self.hass.config_entries.async_update_entry(
|
|
||||||
self.config_entry,
|
|
||||||
data={
|
|
||||||
**self.config_entry.data,
|
|
||||||
CONF_MAC: discovery_info.address,
|
|
||||||
CONF_NAME: discovery_info.name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if bluetooth_configured():
|
|
||||||
# config entry contains BT config
|
|
||||||
_LOGGER.debug("Initializing with known Bluetooth device")
|
|
||||||
await self.lm.init_bluetooth_with_known_device(
|
|
||||||
self.config_entry.data[CONF_USERNAME],
|
|
||||||
self.config_entry.data.get(CONF_MAC, ""),
|
|
||||||
self.config_entry.data.get(CONF_NAME, ""),
|
|
||||||
)
|
)
|
||||||
self._use_bluetooth = True
|
)
|
||||||
|
self.config_entry.async_on_unload(websocket_close)
|
||||||
|
|
||||||
self.lm.initialized = True
|
async def _async_update_data(self) -> None:
|
||||||
|
"""Fetch data from API endpoint."""
|
||||||
|
await self._async_handle_request(self.device.get_config)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._last_firmware_data_update is None
|
||||||
|
or (self._last_firmware_data_update + FIRMWARE_UPDATE_INTERVAL) < time()
|
||||||
|
):
|
||||||
|
await self._async_handle_request(self.device.get_firmware)
|
||||||
|
self._last_firmware_data_update = time()
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._last_statistics_data_update is None
|
||||||
|
or (self._last_statistics_data_update + STATISTICS_UPDATE_INTERVAL) < time()
|
||||||
|
):
|
||||||
|
await self._async_handle_request(self.device.get_statistics)
|
||||||
|
self._last_statistics_data_update = time()
|
||||||
|
|
||||||
|
_LOGGER.debug("Current status: %s", str(self.device.config))
|
||||||
|
|
||||||
async def _async_handle_request[**_P](
|
async def _async_handle_request[**_P](
|
||||||
self,
|
self,
|
||||||
@ -137,9 +112,8 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
*args: _P.args,
|
*args: _P.args,
|
||||||
**kwargs: _P.kwargs,
|
**kwargs: _P.kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a request to the API."""
|
|
||||||
try:
|
try:
|
||||||
await func(*args, **kwargs)
|
await func()
|
||||||
except AuthFail as ex:
|
except AuthFail as ex:
|
||||||
msg = "Authentication failed."
|
msg = "Authentication failed."
|
||||||
_LOGGER.debug(msg, exc_info=True)
|
_LOGGER.debug(msg, exc_info=True)
|
||||||
@ -147,15 +121,3 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
except RequestNotSuccessful as ex:
|
except RequestNotSuccessful as ex:
|
||||||
_LOGGER.debug(ex, exc_info=True)
|
_LOGGER.debug(ex, exc_info=True)
|
||||||
raise UpdateFailed(f"Querying API failed. Error: {ex}") from ex
|
raise UpdateFailed(f"Querying API failed. Error: {ex}") from ex
|
||||||
|
|
||||||
def async_get_ble_device(self) -> BLEDevice | None:
|
|
||||||
"""Get a Bleak Client for the machine."""
|
|
||||||
# according to HA best practices, we should not reuse the same client
|
|
||||||
# get a new BLE device from hass and init a new Bleak Client with it
|
|
||||||
if not self._use_bluetooth:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return async_ble_device_from_address(
|
|
||||||
self.hass,
|
|
||||||
self.lm.lm_bluetooth.address,
|
|
||||||
)
|
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from dataclasses import asdict
|
||||||
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
|
from lmcloud.const import FirmwareType
|
||||||
|
|
||||||
from homeassistant.components.diagnostics import async_redact_data
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -13,31 +16,30 @@ from .coordinator import LaMarzoccoUpdateCoordinator
|
|||||||
|
|
||||||
TO_REDACT = {
|
TO_REDACT = {
|
||||||
"serial_number",
|
"serial_number",
|
||||||
"machine_sn",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticsData(TypedDict):
|
||||||
|
"""Diagnostic data for La Marzocco."""
|
||||||
|
|
||||||
|
model: str
|
||||||
|
config: dict[str, Any]
|
||||||
|
firmware: list[dict[FirmwareType, dict[str, Any]]]
|
||||||
|
statistics: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
coordinator: LaMarzoccoUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: LaMarzoccoUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
device = coordinator.device
|
||||||
# collect all data sources
|
# collect all data sources
|
||||||
data = {}
|
diagnostics_data = DiagnosticsData(
|
||||||
data["current_status"] = coordinator.lm.current_status
|
model=device.model,
|
||||||
data["machine_info"] = coordinator.lm.machine_info
|
config=asdict(device.config),
|
||||||
data["config"] = coordinator.lm.config
|
firmware=[{key: asdict(firmware)} for key, firmware in device.firmware.items()],
|
||||||
data["statistics"] = {"stats": coordinator.lm.statistics} # wrap to satisfy mypy
|
statistics=asdict(device.statistics),
|
||||||
|
)
|
||||||
|
|
||||||
# build a firmware section
|
return async_redact_data(diagnostics_data, TO_REDACT)
|
||||||
data["firmware"] = {
|
|
||||||
"machine": {
|
|
||||||
"version": coordinator.lm.firmware_version,
|
|
||||||
"latest_version": coordinator.lm.latest_firmware_version,
|
|
||||||
},
|
|
||||||
"gateway": {
|
|
||||||
"version": coordinator.lm.gateway_version,
|
|
||||||
"latest_version": coordinator.lm.latest_gateway_version,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return async_redact_data(data, TO_REDACT)
|
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.const import FirmwareType
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
@ -17,11 +18,13 @@ from .coordinator import LaMarzoccoUpdateCoordinator
|
|||||||
class LaMarzoccoEntityDescription(EntityDescription):
|
class LaMarzoccoEntityDescription(EntityDescription):
|
||||||
"""Description for all LM entities."""
|
"""Description for all LM entities."""
|
||||||
|
|
||||||
available_fn: Callable[[LaMarzoccoClient], bool] = lambda _: True
|
available_fn: Callable[[LaMarzoccoMachine], bool] = lambda _: True
|
||||||
supported_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
|
supported_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
|
||||||
|
|
||||||
|
|
||||||
class LaMarzoccoBaseEntity(CoordinatorEntity[LaMarzoccoUpdateCoordinator]):
|
class LaMarzoccoBaseEntity(
|
||||||
|
CoordinatorEntity[LaMarzoccoUpdateCoordinator],
|
||||||
|
):
|
||||||
"""Common elements for all entities."""
|
"""Common elements for all entities."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
@ -33,15 +36,15 @@ class LaMarzoccoBaseEntity(CoordinatorEntity[LaMarzoccoUpdateCoordinator]):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
lm = coordinator.lm
|
device = coordinator.device
|
||||||
self._attr_unique_id = f"{lm.serial_number}_{key}"
|
self._attr_unique_id = f"{device.serial_number}_{key}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, lm.serial_number)},
|
identifiers={(DOMAIN, device.serial_number)},
|
||||||
name=lm.machine_name,
|
name=device.name,
|
||||||
manufacturer="La Marzocco",
|
manufacturer="La Marzocco",
|
||||||
model=lm.true_model_name,
|
model=device.full_model_name,
|
||||||
serial_number=lm.serial_number,
|
serial_number=device.serial_number,
|
||||||
sw_version=lm.firmware_version,
|
sw_version=device.firmware[FirmwareType.MACHINE].current_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -50,19 +53,18 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
|
|||||||
|
|
||||||
entity_description: LaMarzoccoEntityDescription
|
entity_description: LaMarzoccoEntityDescription
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
if super().available:
|
||||||
|
return self.entity_description.available_fn(self.coordinator.device)
|
||||||
|
return False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: LaMarzoccoUpdateCoordinator,
|
coordinator: LaMarzoccoUpdateCoordinator,
|
||||||
entity_description: LaMarzoccoEntityDescription,
|
entity_description: LaMarzoccoEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
|
|
||||||
super().__init__(coordinator, entity_description.key)
|
super().__init__(coordinator, entity_description.key)
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return super().available and self.entity_description.available_fn(
|
|
||||||
self.coordinator.lm
|
|
||||||
)
|
|
||||||
|
@ -26,10 +26,7 @@
|
|||||||
"default": "mdi:thermometer-water"
|
"default": "mdi:thermometer-water"
|
||||||
},
|
},
|
||||||
"dose": {
|
"dose": {
|
||||||
"default": "mdi:weight-kilogram"
|
"default": "mdi:cup-water"
|
||||||
},
|
|
||||||
"steam_temp": {
|
|
||||||
"default": "mdi:thermometer-water"
|
|
||||||
},
|
},
|
||||||
"prebrew_off": {
|
"prebrew_off": {
|
||||||
"default": "mdi:water-off"
|
"default": "mdi:water-off"
|
||||||
@ -40,6 +37,9 @@
|
|||||||
"preinfusion_off": {
|
"preinfusion_off": {
|
||||||
"default": "mdi:water"
|
"default": "mdi:water"
|
||||||
},
|
},
|
||||||
|
"steam_temp": {
|
||||||
|
"default": "mdi:thermometer-water"
|
||||||
|
},
|
||||||
"tea_water_duration": {
|
"tea_water_duration": {
|
||||||
"default": "mdi:timer-sand"
|
"default": "mdi:timer-sand"
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"disabled": "mdi:water-pump-off",
|
"disabled": "mdi:water-pump-off",
|
||||||
"prebrew": "mdi:water-pump",
|
"prebrew": "mdi:water-pump",
|
||||||
"preinfusion": "mdi:water-pump"
|
"typeb": "mdi:water-pump"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["lmcloud"],
|
"loggers": ["lmcloud"],
|
||||||
"requirements": ["lmcloud==0.4.35"]
|
"requirements": ["lmcloud==1.1.11"]
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,15 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.const import (
|
||||||
from lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel
|
KEYS_PER_MODEL,
|
||||||
|
BoilerType,
|
||||||
|
MachineModel,
|
||||||
|
PhysicalKey,
|
||||||
|
PrebrewMode,
|
||||||
|
)
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
from lmcloud.models import LaMarzoccoMachineConfig
|
||||||
|
|
||||||
from homeassistant.components.number import (
|
from homeassistant.components.number import (
|
||||||
NumberDeviceClass,
|
NumberDeviceClass,
|
||||||
@ -35,10 +42,8 @@ class LaMarzoccoNumberEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco number entity."""
|
"""Description of a La Marzocco number entity."""
|
||||||
|
|
||||||
native_value_fn: Callable[[LaMarzoccoClient], float | int]
|
native_value_fn: Callable[[LaMarzoccoMachineConfig], float | int]
|
||||||
set_value_fn: Callable[
|
set_value_fn: Callable[[LaMarzoccoMachine, float | int], Coroutine[Any, Any, bool]]
|
||||||
[LaMarzoccoUpdateCoordinator, float | int], Coroutine[Any, Any, bool]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -48,9 +53,9 @@ class LaMarzoccoKeyNumberEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of an La Marzocco number entity with keys."""
|
"""Description of an La Marzocco number entity with keys."""
|
||||||
|
|
||||||
native_value_fn: Callable[[LaMarzoccoClient, int], float | int]
|
native_value_fn: Callable[[LaMarzoccoMachineConfig, PhysicalKey], float | int]
|
||||||
set_value_fn: Callable[
|
set_value_fn: Callable[
|
||||||
[LaMarzoccoClient, float | int, int], Coroutine[Any, Any, bool]
|
[LaMarzoccoMachine, float | int, PhysicalKey], Coroutine[Any, Any, bool]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -63,10 +68,10 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
|||||||
native_step=PRECISION_TENTHS,
|
native_step=PRECISION_TENTHS,
|
||||||
native_min_value=85,
|
native_min_value=85,
|
||||||
native_max_value=104,
|
native_max_value=104,
|
||||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp(
|
set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.COFFEE, temp),
|
||||||
temp, coordinator.async_get_ble_device()
|
native_value_fn=lambda config: config.boilers[
|
||||||
),
|
BoilerType.COFFEE
|
||||||
native_value_fn=lambda lm: lm.current_status["coffee_set_temp"],
|
].target_temperature,
|
||||||
),
|
),
|
||||||
LaMarzoccoNumberEntityDescription(
|
LaMarzoccoNumberEntityDescription(
|
||||||
key="steam_temp",
|
key="steam_temp",
|
||||||
@ -76,14 +81,14 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
|||||||
native_step=PRECISION_WHOLE,
|
native_step=PRECISION_WHOLE,
|
||||||
native_min_value=126,
|
native_min_value=126,
|
||||||
native_max_value=131,
|
native_max_value=131,
|
||||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp(
|
set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.STEAM, temp),
|
||||||
int(temp), coordinator.async_get_ble_device()
|
native_value_fn=lambda config: config.boilers[
|
||||||
),
|
BoilerType.STEAM
|
||||||
native_value_fn=lambda lm: lm.current_status["steam_set_temp"],
|
].target_temperature,
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
in (
|
in (
|
||||||
LaMarzoccoModel.GS3_AV,
|
MachineModel.GS3_AV,
|
||||||
LaMarzoccoModel.GS3_MP,
|
MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
LaMarzoccoNumberEntityDescription(
|
LaMarzoccoNumberEntityDescription(
|
||||||
@ -94,54 +99,17 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
|||||||
native_step=PRECISION_WHOLE,
|
native_step=PRECISION_WHOLE,
|
||||||
native_min_value=0,
|
native_min_value=0,
|
||||||
native_max_value=30,
|
native_max_value=30,
|
||||||
set_value_fn=lambda coordinator, value: coordinator.lm.set_dose_hot_water(
|
set_value_fn=lambda machine, value: machine.set_dose_tea_water(int(value)),
|
||||||
value=int(value)
|
native_value_fn=lambda config: config.dose_hot_water,
|
||||||
),
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
native_value_fn=lambda lm: lm.current_status["dose_hot_water"],
|
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
|
||||||
in (
|
in (
|
||||||
LaMarzoccoModel.GS3_AV,
|
MachineModel.GS3_AV,
|
||||||
LaMarzoccoModel.GS3_MP,
|
MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _set_prebrew_on(
|
|
||||||
lm: LaMarzoccoClient,
|
|
||||||
value: float,
|
|
||||||
key: int,
|
|
||||||
) -> bool:
|
|
||||||
return await lm.configure_prebrew(
|
|
||||||
on_time=int(value * 1000),
|
|
||||||
off_time=int(lm.current_status[f"prebrewing_toff_k{key}"] * 1000),
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _set_prebrew_off(
|
|
||||||
lm: LaMarzoccoClient,
|
|
||||||
value: float,
|
|
||||||
key: int,
|
|
||||||
) -> bool:
|
|
||||||
return await lm.configure_prebrew(
|
|
||||||
on_time=int(lm.current_status[f"prebrewing_ton_k{key}"] * 1000),
|
|
||||||
off_time=int(value * 1000),
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _set_preinfusion(
|
|
||||||
lm: LaMarzoccoClient,
|
|
||||||
value: float,
|
|
||||||
key: int,
|
|
||||||
) -> bool:
|
|
||||||
return await lm.configure_prebrew(
|
|
||||||
off_time=int(value * 1000),
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||||
LaMarzoccoKeyNumberEntityDescription(
|
LaMarzoccoKeyNumberEntityDescription(
|
||||||
key="prebrew_off",
|
key="prebrew_off",
|
||||||
@ -152,11 +120,14 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
native_min_value=1,
|
native_min_value=1,
|
||||||
native_max_value=10,
|
native_max_value=10,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
set_value_fn=_set_prebrew_off,
|
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||||
native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_ton_k{key}"],
|
prebrew_off_time=value, key=key
|
||||||
available_fn=lambda lm: lm.current_status["enable_prebrewing"],
|
),
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
||||||
!= LaMarzoccoModel.GS3_MP,
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
|
and device.config.prebrew_mode == PrebrewMode.PREBREW,
|
||||||
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
|
!= MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
LaMarzoccoKeyNumberEntityDescription(
|
LaMarzoccoKeyNumberEntityDescription(
|
||||||
key="prebrew_on",
|
key="prebrew_on",
|
||||||
@ -167,11 +138,14 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
native_min_value=2,
|
native_min_value=2,
|
||||||
native_max_value=10,
|
native_max_value=10,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
set_value_fn=_set_prebrew_on,
|
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||||
native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_toff_k{key}"],
|
prebrew_on_time=value, key=key
|
||||||
available_fn=lambda lm: lm.current_status["enable_prebrewing"],
|
),
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
||||||
!= LaMarzoccoModel.GS3_MP,
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
|
and device.config.prebrew_mode == PrebrewMode.PREBREW,
|
||||||
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
|
!= MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
LaMarzoccoKeyNumberEntityDescription(
|
LaMarzoccoKeyNumberEntityDescription(
|
||||||
key="preinfusion_off",
|
key="preinfusion_off",
|
||||||
@ -182,11 +156,16 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
native_min_value=2,
|
native_min_value=2,
|
||||||
native_max_value=29,
|
native_max_value=29,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
set_value_fn=_set_preinfusion,
|
set_value_fn=lambda machine, value, key: machine.set_preinfusion_time(
|
||||||
native_value_fn=lambda lm, key: lm.current_status[f"preinfusion_k{key}"],
|
preinfusion_time=value, key=key
|
||||||
available_fn=lambda lm: lm.current_status["enable_preinfusion"],
|
),
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
native_value_fn=lambda config, key: config.prebrew_configuration[
|
||||||
!= LaMarzoccoModel.GS3_MP,
|
key
|
||||||
|
].preinfusion_time,
|
||||||
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
|
and device.config.prebrew_mode == PrebrewMode.PREINFUSION,
|
||||||
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
|
!= MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
LaMarzoccoKeyNumberEntityDescription(
|
LaMarzoccoKeyNumberEntityDescription(
|
||||||
key="dose",
|
key="dose",
|
||||||
@ -196,10 +175,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
native_min_value=0,
|
native_min_value=0,
|
||||||
native_max_value=999,
|
native_max_value=999,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
set_value_fn=lambda lm, ticks, key: lm.set_dose(key=key, value=int(ticks)),
|
set_value_fn=lambda machine, ticks, key: machine.set_dose(
|
||||||
native_value_fn=lambda lm, key: lm.current_status[f"dose_k{key}"],
|
dose=int(ticks), key=key
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
),
|
||||||
== LaMarzoccoModel.GS3_AV,
|
native_value_fn=lambda config, key: config.doses[key],
|
||||||
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
|
== MachineModel.GS3_AV,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -211,7 +192,6 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up number entities."""
|
"""Set up number entities."""
|
||||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
entities: list[NumberEntity] = [
|
entities: list[NumberEntity] = [
|
||||||
LaMarzoccoNumberEntity(coordinator, description)
|
LaMarzoccoNumberEntity(coordinator, description)
|
||||||
for description in ENTITIES
|
for description in ENTITIES
|
||||||
@ -220,12 +200,11 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
for description in KEY_ENTITIES:
|
for description in KEY_ENTITIES:
|
||||||
if description.supported_fn(coordinator):
|
if description.supported_fn(coordinator):
|
||||||
num_keys = KEYS_PER_MODEL[coordinator.lm.model_name]
|
num_keys = KEYS_PER_MODEL[MachineModel(coordinator.device.model)]
|
||||||
entities.extend(
|
entities.extend(
|
||||||
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
||||||
for key in range(min(num_keys, 1), num_keys + 1)
|
for key in range(min(num_keys, 1), num_keys + 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -237,12 +216,13 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> float:
|
def native_value(self) -> float:
|
||||||
"""Return the current value."""
|
"""Return the current value."""
|
||||||
return self.entity_description.native_value_fn(self.coordinator.lm)
|
return self.entity_description.native_value_fn(self.coordinator.device.config)
|
||||||
|
|
||||||
async def async_set_native_value(self, value: float) -> None:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
await self.entity_description.set_value_fn(self.coordinator, value)
|
if value != self.native_value:
|
||||||
self.async_write_ha_state()
|
await self.entity_description.set_value_fn(self.coordinator.device, value)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
|
||||||
class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||||
@ -273,12 +253,13 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
|||||||
def native_value(self) -> float:
|
def native_value(self) -> float:
|
||||||
"""Return the current value."""
|
"""Return the current value."""
|
||||||
return self.entity_description.native_value_fn(
|
return self.entity_description.native_value_fn(
|
||||||
self.coordinator.lm, self.pyhsical_key
|
self.coordinator.device.config, PhysicalKey(self.pyhsical_key)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_set_native_value(self, value: float) -> None:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
await self.entity_description.set_value_fn(
|
if value != self.native_value:
|
||||||
self.coordinator.lm, value, self.pyhsical_key
|
await self.entity_description.set_value_fn(
|
||||||
)
|
self.coordinator.device, value, PhysicalKey(self.pyhsical_key)
|
||||||
self.async_write_ha_state()
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@ -4,18 +4,43 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
|
||||||
from lmcloud.const import LaMarzoccoModel
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
from lmcloud.models import LaMarzoccoMachineConfig
|
||||||
|
|
||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||||
|
|
||||||
|
STEAM_LEVEL_HA_TO_LM = {
|
||||||
|
"1": SteamLevel.LEVEL_1,
|
||||||
|
"2": SteamLevel.LEVEL_2,
|
||||||
|
"3": SteamLevel.LEVEL_3,
|
||||||
|
}
|
||||||
|
|
||||||
|
STEAM_LEVEL_LM_TO_HA = {
|
||||||
|
SteamLevel.LEVEL_1: "1",
|
||||||
|
SteamLevel.LEVEL_2: "2",
|
||||||
|
SteamLevel.LEVEL_3: "3",
|
||||||
|
}
|
||||||
|
|
||||||
|
PREBREW_MODE_HA_TO_LM = {
|
||||||
|
"disabled": PrebrewMode.DISABLED,
|
||||||
|
"prebrew": PrebrewMode.PREBREW,
|
||||||
|
"preinfusion": PrebrewMode.PREINFUSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
PREBREW_MODE_LM_TO_HA = {
|
||||||
|
PrebrewMode.DISABLED: "disabled",
|
||||||
|
PrebrewMode.PREBREW: "prebrew",
|
||||||
|
PrebrewMode.PREINFUSION: "preinfusion",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class LaMarzoccoSelectEntityDescription(
|
class LaMarzoccoSelectEntityDescription(
|
||||||
@ -24,10 +49,8 @@ class LaMarzoccoSelectEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco select entity."""
|
"""Description of a La Marzocco select entity."""
|
||||||
|
|
||||||
current_option_fn: Callable[[LaMarzoccoClient], str]
|
current_option_fn: Callable[[LaMarzoccoMachineConfig], str]
|
||||||
select_option_fn: Callable[
|
select_option_fn: Callable[[LaMarzoccoMachine, str], Coroutine[Any, Any, bool]]
|
||||||
[LaMarzoccoUpdateCoordinator, str], Coroutine[Any, Any, bool]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||||
@ -35,25 +58,27 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
|||||||
key="steam_temp_select",
|
key="steam_temp_select",
|
||||||
translation_key="steam_temp_select",
|
translation_key="steam_temp_select",
|
||||||
options=["1", "2", "3"],
|
options=["1", "2", "3"],
|
||||||
select_option_fn=lambda coordinator, option: coordinator.lm.set_steam_level(
|
select_option_fn=lambda machine, option: machine.set_steam_level(
|
||||||
int(option), coordinator.async_get_ble_device()
|
STEAM_LEVEL_HA_TO_LM[option]
|
||||||
),
|
),
|
||||||
current_option_fn=lambda lm: lm.current_status["steam_level_set"],
|
current_option_fn=lambda config: STEAM_LEVEL_LM_TO_HA[config.steam_level],
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
== LaMarzoccoModel.LINEA_MICRA,
|
== MachineModel.LINEA_MICRA,
|
||||||
),
|
),
|
||||||
LaMarzoccoSelectEntityDescription(
|
LaMarzoccoSelectEntityDescription(
|
||||||
key="prebrew_infusion_select",
|
key="prebrew_infusion_select",
|
||||||
translation_key="prebrew_infusion_select",
|
translation_key="prebrew_infusion_select",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
options=["disabled", "prebrew", "preinfusion"],
|
options=["disabled", "prebrew", "preinfusion"],
|
||||||
select_option_fn=lambda coordinator,
|
select_option_fn=lambda machine, option: machine.set_prebrew_mode(
|
||||||
option: coordinator.lm.select_pre_brew_infusion_mode(option.capitalize()),
|
PREBREW_MODE_HA_TO_LM[option]
|
||||||
current_option_fn=lambda lm: lm.pre_brew_infusion_mode.lower(),
|
),
|
||||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
current_option_fn=lambda config: PREBREW_MODE_LM_TO_HA[config.prebrew_mode],
|
||||||
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
in (
|
in (
|
||||||
LaMarzoccoModel.GS3_AV,
|
MachineModel.GS3_AV,
|
||||||
LaMarzoccoModel.LINEA_MICRA,
|
MachineModel.LINEA_MICRA,
|
||||||
LaMarzoccoModel.LINEA_MINI,
|
MachineModel.LINEA_MINI,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -82,9 +107,14 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
|||||||
@property
|
@property
|
||||||
def current_option(self) -> str:
|
def current_option(self) -> str:
|
||||||
"""Return the current selected option."""
|
"""Return the current selected option."""
|
||||||
return str(self.entity_description.current_option_fn(self.coordinator.lm))
|
return str(
|
||||||
|
self.entity_description.current_option_fn(self.coordinator.device.config)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
"""Change the selected option."""
|
"""Change the selected option."""
|
||||||
await self.entity_description.select_option_fn(self.coordinator, option)
|
if option != self.current_option:
|
||||||
self.async_write_ha_state()
|
await self.entity_description.select_option_fn(
|
||||||
|
self.coordinator.device, option
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.const import BoilerType, PhysicalKey
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -22,12 +23,11 @@ from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class LaMarzoccoSensorEntityDescription(
|
class LaMarzoccoSensorEntityDescription(
|
||||||
LaMarzoccoEntityDescription,
|
LaMarzoccoEntityDescription, SensorEntityDescription
|
||||||
SensorEntityDescription,
|
|
||||||
):
|
):
|
||||||
"""Description of a La Marzocco sensor."""
|
"""Description of a La Marzocco sensor."""
|
||||||
|
|
||||||
value_fn: Callable[[LaMarzoccoClient], float | int]
|
value_fn: Callable[[LaMarzoccoMachine], float | int]
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||||
@ -36,7 +36,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
|||||||
translation_key="drink_stats_coffee",
|
translation_key="drink_stats_coffee",
|
||||||
native_unit_of_measurement="drinks",
|
native_unit_of_measurement="drinks",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
value_fn=lambda lm: lm.current_status.get("drinks_k1", 0),
|
value_fn=lambda device: device.statistics.drink_stats.get(PhysicalKey.A, 0),
|
||||||
|
available_fn=lambda device: len(device.statistics.drink_stats) > 0,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
LaMarzoccoSensorEntityDescription(
|
LaMarzoccoSensorEntityDescription(
|
||||||
@ -44,7 +45,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
|||||||
translation_key="drink_stats_flushing",
|
translation_key="drink_stats_flushing",
|
||||||
native_unit_of_measurement="drinks",
|
native_unit_of_measurement="drinks",
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
value_fn=lambda lm: lm.current_status.get("total_flushing", 0),
|
value_fn=lambda device: device.statistics.total_flushes,
|
||||||
|
available_fn=lambda device: len(device.statistics.drink_stats) > 0,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
LaMarzoccoSensorEntityDescription(
|
LaMarzoccoSensorEntityDescription(
|
||||||
@ -53,8 +55,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.DURATION,
|
device_class=SensorDeviceClass.DURATION,
|
||||||
value_fn=lambda lm: lm.current_status.get("brew_active_duration", 0),
|
value_fn=lambda device: device.config.brew_active_duration,
|
||||||
available_fn=lambda lm: lm.websocket_connected,
|
available_fn=lambda device: device.websocket_connected,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
||||||
),
|
),
|
||||||
@ -65,7 +67,9 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
|||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
value_fn=lambda lm: lm.current_status.get("coffee_temp", 0),
|
value_fn=lambda device: device.config.boilers[
|
||||||
|
BoilerType.COFFEE
|
||||||
|
].current_temperature,
|
||||||
),
|
),
|
||||||
LaMarzoccoSensorEntityDescription(
|
LaMarzoccoSensorEntityDescription(
|
||||||
key="current_temp_steam",
|
key="current_temp_steam",
|
||||||
@ -74,7 +78,9 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
|||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
value_fn=lambda lm: lm.current_status.get("steam_temp", 0),
|
value_fn=lambda device: device.config.boilers[
|
||||||
|
BoilerType.STEAM
|
||||||
|
].current_temperature,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,4 +108,4 @@ class LaMarzoccoSensorEntity(LaMarzoccoEntity, SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> int | float:
|
def native_value(self) -> int | float:
|
||||||
"""State of the sensor."""
|
"""State of the sensor."""
|
||||||
return self.entity_description.value_fn(self.coordinator.lm)
|
return self.entity_description.value_fn(self.coordinator.device)
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
},
|
},
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"auto_on_off_schedule": {
|
"auto_on_off_schedule": {
|
||||||
"name": "Auto on/off schedule"
|
"name": "Auto on/off schedule ({id})"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
@ -139,9 +139,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"auto_on_off": {
|
|
||||||
"name": "Auto on/off"
|
|
||||||
},
|
|
||||||
"steam_boiler": {
|
"steam_boiler": {
|
||||||
"name": "Steam boiler"
|
"name": "Steam boiler"
|
||||||
}
|
}
|
||||||
@ -154,5 +151,11 @@
|
|||||||
"name": "Gateway firmware"
|
"name": "Gateway firmware"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"unsupported_gateway_firmware": {
|
||||||
|
"title": "Unsupported gateway firmware",
|
||||||
|
"description": "Gateway firmware {gateway_version} is no longer supported by this integration, please update."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,16 @@ from collections.abc import Callable, Coroutine
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from lmcloud.const import BoilerType
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
from lmcloud.models import LaMarzoccoMachineConfig
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
|
||||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||||
|
|
||||||
|
|
||||||
@ -22,8 +24,8 @@ class LaMarzoccoSwitchEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco Switch."""
|
"""Description of a La Marzocco Switch."""
|
||||||
|
|
||||||
control_fn: Callable[[LaMarzoccoUpdateCoordinator, bool], Coroutine[Any, Any, bool]]
|
control_fn: Callable[[LaMarzoccoMachine, bool], Coroutine[Any, Any, bool]]
|
||||||
is_on_fn: Callable[[LaMarzoccoUpdateCoordinator], bool]
|
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
||||||
@ -31,30 +33,14 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
|||||||
key="main",
|
key="main",
|
||||||
translation_key="main",
|
translation_key="main",
|
||||||
name=None,
|
name=None,
|
||||||
control_fn=lambda coordinator, state: coordinator.lm.set_power(
|
control_fn=lambda machine, state: machine.set_power(state),
|
||||||
state, coordinator.async_get_ble_device()
|
is_on_fn=lambda config: config.turned_on,
|
||||||
),
|
|
||||||
is_on_fn=lambda coordinator: coordinator.lm.current_status["power"],
|
|
||||||
),
|
|
||||||
LaMarzoccoSwitchEntityDescription(
|
|
||||||
key="auto_on_off",
|
|
||||||
translation_key="auto_on_off",
|
|
||||||
control_fn=lambda coordinator, state: coordinator.lm.set_auto_on_off_global(
|
|
||||||
state
|
|
||||||
),
|
|
||||||
is_on_fn=lambda coordinator: coordinator.lm.current_status["global_auto"]
|
|
||||||
== "Enabled",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
|
||||||
),
|
),
|
||||||
LaMarzoccoSwitchEntityDescription(
|
LaMarzoccoSwitchEntityDescription(
|
||||||
key="steam_boiler_enable",
|
key="steam_boiler_enable",
|
||||||
translation_key="steam_boiler",
|
translation_key="steam_boiler",
|
||||||
control_fn=lambda coordinator, state: coordinator.lm.set_steam(
|
control_fn=lambda machine, state: machine.set_steam(state),
|
||||||
state, coordinator.async_get_ble_device()
|
is_on_fn=lambda config: config.boilers[BoilerType.STEAM].enabled,
|
||||||
),
|
|
||||||
is_on_fn=lambda coordinator: coordinator.lm.current_status[
|
|
||||||
"steam_boiler_enable"
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,15 +67,15 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity):
|
|||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
await self.entity_description.control_fn(self.coordinator, True)
|
await self.entity_description.control_fn(self.coordinator.device, True)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn device off."""
|
"""Turn device off."""
|
||||||
await self.entity_description.control_fn(self.coordinator, False)
|
await self.entity_description.control_fn(self.coordinator.device, False)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self.entity_description.is_on_fn(self.coordinator)
|
return self.entity_description.is_on_fn(self.coordinator.device.config)
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"""Support for La Marzocco update entities."""
|
"""Support for La Marzocco update entities."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lmcloud import LMCloud as LaMarzoccoClient
|
from lmcloud.const import FirmwareType
|
||||||
from lmcloud.const import LaMarzoccoUpdateableComponent
|
|
||||||
|
|
||||||
from homeassistant.components.update import (
|
from homeassistant.components.update import (
|
||||||
UpdateDeviceClass,
|
UpdateDeviceClass,
|
||||||
@ -30,9 +28,7 @@ class LaMarzoccoUpdateEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Description of a La Marzocco update entities."""
|
"""Description of a La Marzocco update entities."""
|
||||||
|
|
||||||
current_fw_fn: Callable[[LaMarzoccoClient], str]
|
component: FirmwareType
|
||||||
latest_fw_fn: Callable[[LaMarzoccoClient], str]
|
|
||||||
component: LaMarzoccoUpdateableComponent
|
|
||||||
|
|
||||||
|
|
||||||
ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
|
ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
|
||||||
@ -40,18 +36,14 @@ ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
|
|||||||
key="machine_firmware",
|
key="machine_firmware",
|
||||||
translation_key="machine_firmware",
|
translation_key="machine_firmware",
|
||||||
device_class=UpdateDeviceClass.FIRMWARE,
|
device_class=UpdateDeviceClass.FIRMWARE,
|
||||||
current_fw_fn=lambda lm: lm.firmware_version,
|
component=FirmwareType.MACHINE,
|
||||||
latest_fw_fn=lambda lm: lm.latest_firmware_version,
|
|
||||||
component=LaMarzoccoUpdateableComponent.MACHINE,
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
LaMarzoccoUpdateEntityDescription(
|
LaMarzoccoUpdateEntityDescription(
|
||||||
key="gateway_firmware",
|
key="gateway_firmware",
|
||||||
translation_key="gateway_firmware",
|
translation_key="gateway_firmware",
|
||||||
device_class=UpdateDeviceClass.FIRMWARE,
|
device_class=UpdateDeviceClass.FIRMWARE,
|
||||||
current_fw_fn=lambda lm: lm.gateway_version,
|
component=FirmwareType.GATEWAY,
|
||||||
latest_fw_fn=lambda lm: lm.latest_gateway_version,
|
|
||||||
component=LaMarzoccoUpdateableComponent.GATEWAY,
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -81,12 +73,16 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
|||||||
@property
|
@property
|
||||||
def installed_version(self) -> str | None:
|
def installed_version(self) -> str | None:
|
||||||
"""Return the current firmware version."""
|
"""Return the current firmware version."""
|
||||||
return self.entity_description.current_fw_fn(self.coordinator.lm)
|
return self.coordinator.device.firmware[
|
||||||
|
self.entity_description.component
|
||||||
|
].current_version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_version(self) -> str:
|
def latest_version(self) -> str:
|
||||||
"""Return the latest firmware version."""
|
"""Return the latest firmware version."""
|
||||||
return self.entity_description.latest_fw_fn(self.coordinator.lm)
|
return self.coordinator.device.firmware[
|
||||||
|
self.entity_description.component
|
||||||
|
].latest_version
|
||||||
|
|
||||||
async def async_install(
|
async def async_install(
|
||||||
self, version: str | None, backup: bool, **kwargs: Any
|
self, version: str | None, backup: bool, **kwargs: Any
|
||||||
@ -94,7 +90,7 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
|||||||
"""Install an update."""
|
"""Install an update."""
|
||||||
self._attr_in_progress = True
|
self._attr_in_progress = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
success = await self.coordinator.lm.update_firmware(
|
success = await self.coordinator.device.update_firmware(
|
||||||
self.entity_description.component
|
self.entity_description.component
|
||||||
)
|
)
|
||||||
if not success:
|
if not success:
|
||||||
|
@ -1260,7 +1260,7 @@ linear-garage-door==0.2.9
|
|||||||
linode-api==4.1.9b1
|
linode-api==4.1.9b1
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
lmcloud==0.4.35
|
lmcloud==1.1.11
|
||||||
|
|
||||||
# homeassistant.components.google_maps
|
# homeassistant.components.google_maps
|
||||||
locationsharinglib==5.0.1
|
locationsharinglib==5.0.1
|
||||||
|
@ -1017,7 +1017,7 @@ libsoundtouch==0.8
|
|||||||
linear-garage-door==0.2.9
|
linear-garage-door==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
lmcloud==0.4.35
|
lmcloud==1.1.11
|
||||||
|
|
||||||
# homeassistant.components.logi_circle
|
# homeassistant.components.logi_circle
|
||||||
logi-circle==0.2.3
|
logi-circle==0.2.3
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Mock inputs for tests."""
|
"""Mock inputs for tests."""
|
||||||
|
|
||||||
from lmcloud.const import LaMarzoccoModel
|
from lmcloud.const import MachineModel
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -18,31 +18,34 @@ PASSWORD_SELECTION = {
|
|||||||
|
|
||||||
USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"}
|
USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"}
|
||||||
|
|
||||||
MODEL_DICT = {
|
SERIAL_DICT = {
|
||||||
LaMarzoccoModel.GS3_AV: ("GS01234", "GS3 AV"),
|
MachineModel.GS3_AV: "GS01234",
|
||||||
LaMarzoccoModel.GS3_MP: ("GS01234", "GS3 MP"),
|
MachineModel.GS3_MP: "GS01234",
|
||||||
LaMarzoccoModel.LINEA_MICRA: ("MR01234", "Linea Micra"),
|
MachineModel.LINEA_MICRA: "MR01234",
|
||||||
LaMarzoccoModel.LINEA_MINI: ("LM01234", "Linea Mini"),
|
MachineModel.LINEA_MINI: "LM01234",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WAKE_UP_SLEEP_ENTRY_IDS = ["Os2OswX", "aXFz5bJ"]
|
||||||
|
|
||||||
|
|
||||||
async def async_init_integration(
|
async def async_init_integration(
|
||||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the La Marzocco integration for testing."""
|
"""Set up the La Marzocco integration for testing."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
def get_bluetooth_service_info(
|
def get_bluetooth_service_info(
|
||||||
model: LaMarzoccoModel, serial: str
|
model: MachineModel, serial: str
|
||||||
) -> BluetoothServiceInfo:
|
) -> BluetoothServiceInfo:
|
||||||
"""Return a mocked BluetoothServiceInfo."""
|
"""Return a mocked BluetoothServiceInfo."""
|
||||||
if model in (LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP):
|
if model in (MachineModel.GS3_AV, MachineModel.GS3_MP):
|
||||||
name = f"GS3_{serial}"
|
name = f"GS3_{serial}"
|
||||||
elif model == LaMarzoccoModel.LINEA_MINI:
|
elif model == MachineModel.LINEA_MINI:
|
||||||
name = f"MINI_{serial}"
|
name = f"MINI_{serial}"
|
||||||
elif model == LaMarzoccoModel.LINEA_MICRA:
|
elif model == MachineModel.LINEA_MICRA:
|
||||||
name = f"MICRA_{serial}"
|
name = f"MICRA_{serial}"
|
||||||
return BluetoothServiceInfo(
|
return BluetoothServiceInfo(
|
||||||
name=name,
|
name=name,
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
"""Lamarzocco session fixtures."""
|
"""Lamarzocco session fixtures."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
import json
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from lmcloud.const import LaMarzoccoModel
|
from bleak.backends.device import BLEDevice
|
||||||
|
from lmcloud.const import FirmwareType, MachineModel, SteamLevel
|
||||||
|
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||||
|
from lmcloud.models import LaMarzoccoDeviceInfo
|
||||||
import pytest
|
import pytest
|
||||||
from typing_extensions import Generator
|
from typing_extensions import Generator
|
||||||
|
|
||||||
from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN
|
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_NAME, CONF_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import MODEL_DICT, USER_INPUT, async_init_integration
|
from . import SERIAL_DICT, USER_INPUT, async_init_integration
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture
|
||||||
MockConfigEntry,
|
|
||||||
load_json_array_fixture,
|
|
||||||
load_json_object_fixture,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -27,12 +28,13 @@ def mock_config_entry(
|
|||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
title="My LaMarzocco",
|
title="My LaMarzocco",
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
version=2,
|
||||||
data=USER_INPUT
|
data=USER_INPUT
|
||||||
| {
|
| {
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
CONF_MODEL: mock_lamarzocco.model,
|
||||||
CONF_HOST: "host",
|
CONF_HOST: "host",
|
||||||
CONF_NAME: "name",
|
CONF_TOKEN: "token",
|
||||||
CONF_MAC: "mac",
|
CONF_NAME: "GS3",
|
||||||
},
|
},
|
||||||
unique_id=mock_lamarzocco.serial_number,
|
unique_id=mock_lamarzocco.serial_number,
|
||||||
)
|
)
|
||||||
@ -44,77 +46,96 @@ def mock_config_entry(
|
|||||||
async def init_integration(
|
async def init_integration(
|
||||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock
|
||||||
) -> MockConfigEntry:
|
) -> MockConfigEntry:
|
||||||
"""Set up the LaMetric integration for testing."""
|
"""Set up the La Marzocco integration for testing."""
|
||||||
await async_init_integration(hass, mock_config_entry)
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
return mock_config_entry
|
return mock_config_entry
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def device_fixture() -> LaMarzoccoModel:
|
def device_fixture() -> MachineModel:
|
||||||
"""Return the device fixture for a specific device."""
|
"""Return the device fixture for a specific device."""
|
||||||
return LaMarzoccoModel.GS3_AV
|
return MachineModel.GS3_AV
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_lamarzocco(device_fixture: LaMarzoccoModel) -> Generator[MagicMock]:
|
def mock_device_info() -> LaMarzoccoDeviceInfo:
|
||||||
"""Return a mocked LM client."""
|
"""Return a mocked La Marzocco device info."""
|
||||||
model_name = device_fixture
|
return LaMarzoccoDeviceInfo(
|
||||||
|
model=MachineModel.GS3_AV,
|
||||||
|
serial_number="GS01234",
|
||||||
|
name="GS3",
|
||||||
|
communication_key="token",
|
||||||
|
)
|
||||||
|
|
||||||
(serial_number, true_model_name) = MODEL_DICT[model_name]
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_cloud_client(
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
|
) -> Generator[MagicMock]:
|
||||||
|
"""Return a mocked LM cloud client."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoCloudClient",
|
||||||
|
autospec=True,
|
||||||
|
) as cloud_client,
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.lamarzocco.LaMarzoccoCloudClient",
|
||||||
|
new=cloud_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
client = cloud_client.return_value
|
||||||
|
client.get_customer_fleet.return_value = {
|
||||||
|
mock_device_info.serial_number: mock_device_info
|
||||||
|
}
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_lamarzocco(device_fixture: MachineModel) -> Generator[MagicMock]:
|
||||||
|
"""Return a mocked LM client."""
|
||||||
|
model = device_fixture
|
||||||
|
|
||||||
|
serial_number = SERIAL_DICT[model]
|
||||||
|
|
||||||
|
dummy_machine = LaMarzoccoMachine(
|
||||||
|
model=model,
|
||||||
|
serial_number=serial_number,
|
||||||
|
name=serial_number,
|
||||||
|
)
|
||||||
|
config = load_json_object_fixture("config.json", DOMAIN)
|
||||||
|
statistics = json.loads(load_fixture("statistics.json", DOMAIN))
|
||||||
|
|
||||||
|
dummy_machine.parse_config(config)
|
||||||
|
dummy_machine.parse_statistics(statistics)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient",
|
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoMachine",
|
||||||
autospec=True,
|
autospec=True,
|
||||||
) as lamarzocco_mock,
|
) as lamarzocco_mock,
|
||||||
patch(
|
|
||||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient",
|
|
||||||
new=lamarzocco_mock,
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
lamarzocco = lamarzocco_mock.return_value
|
lamarzocco = lamarzocco_mock.return_value
|
||||||
|
|
||||||
lamarzocco.machine_info = {
|
lamarzocco.name = dummy_machine.name
|
||||||
"machine_name": serial_number,
|
lamarzocco.model = dummy_machine.model
|
||||||
"serial_number": serial_number,
|
lamarzocco.serial_number = dummy_machine.serial_number
|
||||||
}
|
lamarzocco.full_model_name = dummy_machine.full_model_name
|
||||||
|
lamarzocco.config = dummy_machine.config
|
||||||
|
lamarzocco.statistics = dummy_machine.statistics
|
||||||
|
lamarzocco.firmware = dummy_machine.firmware
|
||||||
|
lamarzocco.steam_level = SteamLevel.LEVEL_1
|
||||||
|
|
||||||
lamarzocco.model_name = model_name
|
lamarzocco.firmware[FirmwareType.GATEWAY].latest_version = "v3.5-rc3"
|
||||||
lamarzocco.true_model_name = true_model_name
|
lamarzocco.firmware[FirmwareType.MACHINE].latest_version = "1.55"
|
||||||
lamarzocco.machine_name = serial_number
|
|
||||||
lamarzocco.serial_number = serial_number
|
|
||||||
|
|
||||||
lamarzocco.firmware_version = "1.1"
|
|
||||||
lamarzocco.latest_firmware_version = "1.2"
|
|
||||||
lamarzocco.gateway_version = "v2.2-rc0"
|
|
||||||
lamarzocco.latest_gateway_version = "v3.1-rc4"
|
|
||||||
lamarzocco.update_firmware.return_value = True
|
|
||||||
|
|
||||||
lamarzocco.current_status = load_json_object_fixture(
|
|
||||||
"current_status.json", DOMAIN
|
|
||||||
)
|
|
||||||
lamarzocco.config = load_json_object_fixture("config.json", DOMAIN)
|
|
||||||
lamarzocco.statistics = load_json_array_fixture("statistics.json", DOMAIN)
|
|
||||||
lamarzocco.schedule = load_json_array_fixture("schedule.json", DOMAIN)
|
|
||||||
|
|
||||||
lamarzocco.get_all_machines.return_value = [
|
|
||||||
(serial_number, model_name),
|
|
||||||
]
|
|
||||||
lamarzocco.check_local_connection.return_value = True
|
|
||||||
lamarzocco.initialized = False
|
|
||||||
lamarzocco.websocket_connected = True
|
|
||||||
|
|
||||||
async def websocket_connect_mock(
|
async def websocket_connect_mock(
|
||||||
callback: MagicMock, use_sigterm_handler: MagicMock
|
notify_callback: Callable | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Mock the websocket connect method."""
|
"""Mock the websocket connect method."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
lamarzocco.lm_local_api.websocket_connect = websocket_connect_mock
|
lamarzocco.websocket_connect = websocket_connect_mock
|
||||||
|
|
||||||
lamarzocco.lm_bluetooth = MagicMock()
|
|
||||||
lamarzocco.lm_bluetooth.address = "AA:BB:CC:DD:EE:FF"
|
|
||||||
|
|
||||||
yield lamarzocco
|
yield lamarzocco
|
||||||
|
|
||||||
@ -133,3 +154,11 @@ def remove_local_connection(
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_bluetooth(enable_bluetooth: None) -> None:
|
def mock_bluetooth(enable_bluetooth: None) -> None:
|
||||||
"""Auto mock bluetooth."""
|
"""Auto mock bluetooth."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_ble_device() -> BLEDevice:
|
||||||
|
"""Return a mock BLE device."""
|
||||||
|
return BLEDevice(
|
||||||
|
"00:00:00:00:00:00", "GS_GS01234", details={"path": "path"}, rssi=50
|
||||||
|
)
|
||||||
|
@ -13,11 +13,16 @@
|
|||||||
"schedulingType": "weeklyScheduling"
|
"schedulingType": "weeklyScheduling"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"machine_sn": "GS01234",
|
"machine_sn": "Sn01239157",
|
||||||
"machine_hw": "2",
|
"machine_hw": "2",
|
||||||
"isPlumbedIn": true,
|
"isPlumbedIn": true,
|
||||||
"isBackFlushEnabled": false,
|
"isBackFlushEnabled": false,
|
||||||
"standByTime": 0,
|
"standByTime": 0,
|
||||||
|
"smartStandBy": {
|
||||||
|
"enabled": true,
|
||||||
|
"minutes": 10,
|
||||||
|
"mode": "LastBrewing"
|
||||||
|
},
|
||||||
"tankStatus": true,
|
"tankStatus": true,
|
||||||
"groupCapabilities": [
|
"groupCapabilities": [
|
||||||
{
|
{
|
||||||
@ -121,58 +126,32 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"weeklySchedulingConfig": {
|
"wakeUpSleepEntries": [
|
||||||
"enabled": true,
|
{
|
||||||
"monday": {
|
"days": [
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday",
|
||||||
|
"sunday"
|
||||||
|
],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"h_on": 6,
|
"id": "Os2OswX",
|
||||||
"h_off": 16,
|
"steam": true,
|
||||||
"m_on": 0,
|
"timeOff": "24:0",
|
||||||
"m_off": 0
|
"timeOn": "22:0"
|
||||||
},
|
},
|
||||||
"tuesday": {
|
{
|
||||||
|
"days": ["sunday"],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"h_on": 6,
|
"id": "aXFz5bJ",
|
||||||
"h_off": 16,
|
"steam": true,
|
||||||
"m_on": 0,
|
"timeOff": "7:30",
|
||||||
"m_off": 0
|
"timeOn": "7:0"
|
||||||
},
|
|
||||||
"wednesday": {
|
|
||||||
"enabled": true,
|
|
||||||
"h_on": 6,
|
|
||||||
"h_off": 16,
|
|
||||||
"m_on": 0,
|
|
||||||
"m_off": 0
|
|
||||||
},
|
|
||||||
"thursday": {
|
|
||||||
"enabled": true,
|
|
||||||
"h_on": 6,
|
|
||||||
"h_off": 16,
|
|
||||||
"m_on": 0,
|
|
||||||
"m_off": 0
|
|
||||||
},
|
|
||||||
"friday": {
|
|
||||||
"enabled": true,
|
|
||||||
"h_on": 6,
|
|
||||||
"h_off": 16,
|
|
||||||
"m_on": 0,
|
|
||||||
"m_off": 0
|
|
||||||
},
|
|
||||||
"saturday": {
|
|
||||||
"enabled": true,
|
|
||||||
"h_on": 6,
|
|
||||||
"h_off": 16,
|
|
||||||
"m_on": 0,
|
|
||||||
"m_off": 0
|
|
||||||
},
|
|
||||||
"sunday": {
|
|
||||||
"enabled": true,
|
|
||||||
"h_on": 6,
|
|
||||||
"h_off": 16,
|
|
||||||
"m_on": 0,
|
|
||||||
"m_off": 0
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
"clock": "1901-07-08T10:29:00",
|
"clock": "1901-07-08T10:29:00",
|
||||||
"firmwareVersions": [
|
"firmwareVersions": [
|
||||||
{
|
{
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
"power": true,
|
|
||||||
"global_auto": "Enabled",
|
|
||||||
"enable_prebrewing": true,
|
|
||||||
"coffee_boiler_on": true,
|
|
||||||
"steam_boiler_on": true,
|
|
||||||
"enable_preinfusion": false,
|
|
||||||
"steam_boiler_enable": true,
|
|
||||||
"steam_temp": 113,
|
|
||||||
"steam_set_temp": 128,
|
|
||||||
"steam_level_set": 3,
|
|
||||||
"coffee_temp": 93,
|
|
||||||
"coffee_set_temp": 95,
|
|
||||||
"water_reservoir_contact": true,
|
|
||||||
"brew_active": false,
|
|
||||||
"drinks_k1": 13,
|
|
||||||
"drinks_k2": 2,
|
|
||||||
"drinks_k3": 42,
|
|
||||||
"drinks_k4": 34,
|
|
||||||
"total_flushing": 69,
|
|
||||||
"mon_auto": "Disabled",
|
|
||||||
"mon_on_time": "00:00",
|
|
||||||
"mon_off_time": "00:00",
|
|
||||||
"tue_auto": "Disabled",
|
|
||||||
"tue_on_time": "00:00",
|
|
||||||
"tue_off_time": "00:00",
|
|
||||||
"wed_auto": "Disabled",
|
|
||||||
"wed_on_time": "00:00",
|
|
||||||
"wed_off_time": "00:00",
|
|
||||||
"thu_auto": "Disabled",
|
|
||||||
"thu_on_time": "00:00",
|
|
||||||
"thu_off_time": "00:00",
|
|
||||||
"fri_auto": "Disabled",
|
|
||||||
"fri_on_time": "00:00",
|
|
||||||
"fri_off_time": "00:00",
|
|
||||||
"sat_auto": "Disabled",
|
|
||||||
"sat_on_time": "00:00",
|
|
||||||
"sat_off_time": "00:00",
|
|
||||||
"sun_auto": "Disabled",
|
|
||||||
"sun_on_time": "00:00",
|
|
||||||
"sun_off_time": "00:00",
|
|
||||||
"dose_k1": 1023,
|
|
||||||
"dose_k2": 1023,
|
|
||||||
"dose_k3": 1023,
|
|
||||||
"dose_k4": 1023,
|
|
||||||
"dose_hot_water": 1023,
|
|
||||||
"prebrewing_ton_k1": 3,
|
|
||||||
"prebrewing_toff_k1": 5,
|
|
||||||
"prebrewing_ton_k2": 3,
|
|
||||||
"prebrewing_toff_k2": 5,
|
|
||||||
"prebrewing_ton_k3": 3,
|
|
||||||
"prebrewing_toff_k3": 5,
|
|
||||||
"prebrewing_ton_k4": 3,
|
|
||||||
"prebrewing_toff_k4": 5,
|
|
||||||
"preinfusion_k1": 4,
|
|
||||||
"preinfusion_k2": 4,
|
|
||||||
"preinfusion_k3": 4,
|
|
||||||
"preinfusion_k4": 4
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"day": "MONDAY",
|
|
||||||
"enable": "Disabled",
|
|
||||||
"on": "00:00",
|
|
||||||
"off": "00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "TUESDAY",
|
|
||||||
"enable": "Disabled",
|
|
||||||
"on": "00:00",
|
|
||||||
"off": "00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "WEDNESDAY",
|
|
||||||
"enable": "Enabled",
|
|
||||||
"on": "08:00",
|
|
||||||
"off": "13:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "THURSDAY",
|
|
||||||
"enable": "Disabled",
|
|
||||||
"on": "00:00",
|
|
||||||
"off": "00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "FRIDAY",
|
|
||||||
"enable": "Enabled",
|
|
||||||
"on": "06:00",
|
|
||||||
"off": "09:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "SATURDAY",
|
|
||||||
"enable": "Enabled",
|
|
||||||
"on": "10:00",
|
|
||||||
"off": "23:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"day": "SUNDAY",
|
|
||||||
"enable": "Disabled",
|
|
||||||
"on": "00:00",
|
|
||||||
"off": "00:00"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,7 +1,7 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
# name: test_calendar_edge_cases[start_date0-end_date0]
|
# name: test_calendar_edge_cases[start_date0-end_date0]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
@ -15,7 +15,7 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_calendar_edge_cases[start_date1-end_date1]
|
# name: test_calendar_edge_cases[start_date1-end_date1]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
@ -29,7 +29,7 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_calendar_edge_cases[start_date2-end_date2]
|
# name: test_calendar_edge_cases[start_date2-end_date2]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
@ -43,7 +43,7 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_calendar_edge_cases[start_date3-end_date3]
|
# name: test_calendar_edge_cases[start_date3-end_date3]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
@ -57,7 +57,7 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_calendar_edge_cases[start_date4-end_date4]
|
# name: test_calendar_edge_cases[start_date4-end_date4]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
@ -65,7 +65,7 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_calendar_edge_cases[start_date5-end_date5]
|
# name: test_calendar_edge_cases[start_date5-end_date5]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
@ -83,26 +83,7 @@
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_calendar_events
|
# name: test_calendar_events[entry.GS01234_auto_on_off_schedule_axfz5bj]
|
||||||
StateSnapshot({
|
|
||||||
'attributes': ReadOnlyDict({
|
|
||||||
'all_day': False,
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end_time': '2024-01-13 23:00:00',
|
|
||||||
'friendly_name': 'GS01234 Auto on/off schedule',
|
|
||||||
'location': '',
|
|
||||||
'message': 'Machine My LaMarzocco on',
|
|
||||||
'start_time': '2024-01-13 10:00:00',
|
|
||||||
}),
|
|
||||||
'context': <ANY>,
|
|
||||||
'entity_id': 'calendar.gs01234_auto_on_off_schedule',
|
|
||||||
'last_changed': <ANY>,
|
|
||||||
'last_reported': <ANY>,
|
|
||||||
'last_updated': <ANY>,
|
|
||||||
'state': 'off',
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_calendar_events.1
|
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -114,7 +95,7 @@
|
|||||||
'disabled_by': None,
|
'disabled_by': None,
|
||||||
'domain': 'calendar',
|
'domain': 'calendar',
|
||||||
'entity_category': None,
|
'entity_category': None,
|
||||||
'entity_id': 'calendar.gs01234_auto_on_off_schedule',
|
'entity_id': 'calendar.gs01234_auto_on_off_schedule_axfz5bj',
|
||||||
'has_entity_name': True,
|
'has_entity_name': True,
|
||||||
'hidden_by': None,
|
'hidden_by': None,
|
||||||
'icon': None,
|
'icon': None,
|
||||||
@ -126,86 +107,267 @@
|
|||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': None,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'Auto on/off schedule',
|
'original_name': 'Auto on/off schedule (aXFz5bJ)',
|
||||||
'platform': 'lamarzocco',
|
'platform': 'lamarzocco',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'auto_on_off_schedule',
|
'translation_key': 'auto_on_off_schedule',
|
||||||
'unique_id': 'GS01234_auto_on_off_schedule',
|
'unique_id': 'GS01234_auto_on_off_schedule_aXFz5bJ',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_calendar_events.2
|
# name: test_calendar_events[entry.GS01234_auto_on_off_schedule_os2oswx]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'calendar',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'calendar.gs01234_auto_on_off_schedule_os2oswx',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Auto on/off schedule (Os2OswX)',
|
||||||
|
'platform': 'lamarzocco',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'auto_on_off_schedule',
|
||||||
|
'unique_id': 'GS01234_auto_on_off_schedule_Os2OswX',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_calendar_events[events.GS01234_auto_on_off_schedule_axfz5bj]
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
'end': '2024-01-13T23:00:00-08:00',
|
'end': '2024-01-14T07:30:00-08:00',
|
||||||
'start': '2024-01-13T10:00:00-08:00',
|
'start': '2024-01-14T07:00:00-08:00',
|
||||||
'summary': 'Machine My LaMarzocco on',
|
'summary': 'Machine My LaMarzocco on',
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
'end': '2024-01-17T13:00:00-08:00',
|
'end': '2024-01-21T07:30:00-08:00',
|
||||||
'start': '2024-01-17T08:00:00-08:00',
|
'start': '2024-01-21T07:00:00-08:00',
|
||||||
'summary': 'Machine My LaMarzocco on',
|
'summary': 'Machine My LaMarzocco on',
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
'end': '2024-01-19T09:00:00-08:00',
|
'end': '2024-01-28T07:30:00-08:00',
|
||||||
'start': '2024-01-19T06:00:00-08:00',
|
'start': '2024-01-28T07:00:00-08:00',
|
||||||
'summary': 'Machine My LaMarzocco on',
|
'summary': 'Machine My LaMarzocco on',
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
'end': '2024-01-20T23:00:00-08:00',
|
'end': '2024-02-04T07:30:00-08:00',
|
||||||
'start': '2024-01-20T10:00:00-08:00',
|
'start': '2024-02-04T07:00:00-08:00',
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-01-24T13:00:00-08:00',
|
|
||||||
'start': '2024-01-24T08:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-01-26T09:00:00-08:00',
|
|
||||||
'start': '2024-01-26T06:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-01-27T23:00:00-08:00',
|
|
||||||
'start': '2024-01-27T10:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-01-31T13:00:00-08:00',
|
|
||||||
'start': '2024-01-31T08:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-02-02T09:00:00-08:00',
|
|
||||||
'start': '2024-02-02T06:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
|
||||||
'end': '2024-02-03T23:00:00-08:00',
|
|
||||||
'start': '2024-02-03T10:00:00-08:00',
|
|
||||||
'summary': 'Machine My LaMarzocco on',
|
'summary': 'Machine My LaMarzocco on',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_calendar_events[events.GS01234_auto_on_off_schedule_os2oswx]
|
||||||
|
dict({
|
||||||
|
'calendar.gs01234_auto_on_off_schedule_os2oswx': dict({
|
||||||
|
'events': list([
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-13T00:00:00-08:00',
|
||||||
|
'start': '2024-01-12T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-14T00:00:00-08:00',
|
||||||
|
'start': '2024-01-13T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-15T00:00:00-08:00',
|
||||||
|
'start': '2024-01-14T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-16T00:00:00-08:00',
|
||||||
|
'start': '2024-01-15T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-17T00:00:00-08:00',
|
||||||
|
'start': '2024-01-16T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-18T00:00:00-08:00',
|
||||||
|
'start': '2024-01-17T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-19T00:00:00-08:00',
|
||||||
|
'start': '2024-01-18T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-20T00:00:00-08:00',
|
||||||
|
'start': '2024-01-19T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-21T00:00:00-08:00',
|
||||||
|
'start': '2024-01-20T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-22T00:00:00-08:00',
|
||||||
|
'start': '2024-01-21T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-23T00:00:00-08:00',
|
||||||
|
'start': '2024-01-22T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-24T00:00:00-08:00',
|
||||||
|
'start': '2024-01-23T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-25T00:00:00-08:00',
|
||||||
|
'start': '2024-01-24T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-26T00:00:00-08:00',
|
||||||
|
'start': '2024-01-25T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-27T00:00:00-08:00',
|
||||||
|
'start': '2024-01-26T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-28T00:00:00-08:00',
|
||||||
|
'start': '2024-01-27T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-29T00:00:00-08:00',
|
||||||
|
'start': '2024-01-28T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-30T00:00:00-08:00',
|
||||||
|
'start': '2024-01-29T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-01-31T00:00:00-08:00',
|
||||||
|
'start': '2024-01-30T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-02-01T00:00:00-08:00',
|
||||||
|
'start': '2024-01-31T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-02-02T00:00:00-08:00',
|
||||||
|
'start': '2024-02-01T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-02-03T00:00:00-08:00',
|
||||||
|
'start': '2024-02-02T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end': '2024-02-04T00:00:00-08:00',
|
||||||
|
'start': '2024-02-03T22:00:00-08:00',
|
||||||
|
'summary': 'Machine My LaMarzocco on',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_calendar_events[state.GS01234_auto_on_off_schedule_axfz5bj]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'all_day': False,
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end_time': '2024-01-14 07:30:00',
|
||||||
|
'friendly_name': 'GS01234 Auto on/off schedule (aXFz5bJ)',
|
||||||
|
'location': '',
|
||||||
|
'message': 'Machine My LaMarzocco on',
|
||||||
|
'start_time': '2024-01-14 07:00:00',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'calendar.gs01234_auto_on_off_schedule_axfz5bj',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_calendar_events[state.GS01234_auto_on_off_schedule_os2oswx]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'all_day': False,
|
||||||
|
'description': 'Machine is scheduled to turn on at the start time and off at the end time',
|
||||||
|
'end_time': '2024-01-13 00:00:00',
|
||||||
|
'friendly_name': 'GS01234 Auto on/off schedule (Os2OswX)',
|
||||||
|
'location': '',
|
||||||
|
'message': 'Machine My LaMarzocco on',
|
||||||
|
'start_time': '2024-01-12 22:00:00',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'calendar.gs01234_auto_on_off_schedule_os2oswx',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_no_calendar_events_global_disable
|
# name: test_no_calendar_events_global_disable
|
||||||
dict({
|
dict({
|
||||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
'calendar.gs01234_auto_on_off_schedule_os2oswx': dict({
|
||||||
'events': list([
|
'events': list([
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
@ -2,297 +2,107 @@
|
|||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'config': dict({
|
'config': dict({
|
||||||
'boilerTargetTemperature': dict({
|
'boilers': dict({
|
||||||
'CoffeeBoiler1': 95,
|
'CoffeeBoiler1': dict({
|
||||||
'SteamBoiler': 123.9000015258789,
|
'current_temperature': 96.5,
|
||||||
}),
|
'enabled': True,
|
||||||
'boilers': list([
|
'target_temperature': 95,
|
||||||
dict({
|
|
||||||
'current': 123.80000305175781,
|
|
||||||
'id': 'SteamBoiler',
|
|
||||||
'isEnabled': True,
|
|
||||||
'target': 123.9000015258789,
|
|
||||||
}),
|
}),
|
||||||
dict({
|
'SteamBoiler': dict({
|
||||||
'current': 96.5,
|
'current_temperature': 123.80000305175781,
|
||||||
'id': 'CoffeeBoiler1',
|
'enabled': True,
|
||||||
'isEnabled': True,
|
'target_temperature': 123.9000015258789,
|
||||||
'target': 95,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'clock': '1901-07-08T10:29:00',
|
|
||||||
'firmwareVersions': list([
|
|
||||||
dict({
|
|
||||||
'fw_version': '1.40',
|
|
||||||
'name': 'machine_firmware',
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'fw_version': 'v3.1-rc4',
|
|
||||||
'name': 'gateway_firmware',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'groupCapabilities': list([
|
|
||||||
dict({
|
|
||||||
'capabilities': dict({
|
|
||||||
'boilerId': 'CoffeeBoiler1',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'groupType': 'AV_Group',
|
|
||||||
'hasFlowmeter': True,
|
|
||||||
'hasScale': False,
|
|
||||||
'numberOfDoses': 4,
|
|
||||||
}),
|
|
||||||
'doseMode': dict({
|
|
||||||
'brewingType': 'PulsesType',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
}),
|
|
||||||
'doses': list([
|
|
||||||
dict({
|
|
||||||
'doseIndex': 'DoseA',
|
|
||||||
'doseType': 'PulsesType',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'stopTarget': 135,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseIndex': 'DoseB',
|
|
||||||
'doseType': 'PulsesType',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'stopTarget': 97,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseIndex': 'DoseC',
|
|
||||||
'doseType': 'PulsesType',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'stopTarget': 108,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseIndex': 'DoseD',
|
|
||||||
'doseType': 'PulsesType',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'stopTarget': 121,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'isBackFlushEnabled': False,
|
|
||||||
'isPlumbedIn': True,
|
|
||||||
'machineCapabilities': list([
|
|
||||||
dict({
|
|
||||||
'coffeeBoilersNumber': 1,
|
|
||||||
'family': 'GS3AV',
|
|
||||||
'groupsNumber': 1,
|
|
||||||
'hasCupWarmer': False,
|
|
||||||
'machineModes': list([
|
|
||||||
'BrewingMode',
|
|
||||||
'StandBy',
|
|
||||||
]),
|
|
||||||
'schedulingType': 'weeklyScheduling',
|
|
||||||
'steamBoilersNumber': 1,
|
|
||||||
'teaDosesNumber': 1,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'machineMode': 'BrewingMode',
|
|
||||||
'machine_hw': '2',
|
|
||||||
'machine_sn': '**REDACTED**',
|
|
||||||
'preinfusionMode': dict({
|
|
||||||
'Group1': dict({
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'preinfusionStyle': 'PreinfusionByDoseType',
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'preinfusionModesAvailable': list([
|
|
||||||
'ByDoseType',
|
|
||||||
]),
|
|
||||||
'preinfusionSettings': dict({
|
|
||||||
'Group1': list([
|
|
||||||
dict({
|
|
||||||
'doseType': 'DoseA',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'preWetHoldTime': 1,
|
|
||||||
'preWetTime': 0.5,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseType': 'DoseB',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'preWetHoldTime': 1,
|
|
||||||
'preWetTime': 0.5,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseType': 'DoseC',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'preWetHoldTime': 3.299999952316284,
|
|
||||||
'preWetTime': 3.299999952316284,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'doseType': 'DoseD',
|
|
||||||
'groupNumber': 'Group1',
|
|
||||||
'preWetHoldTime': 2,
|
|
||||||
'preWetTime': 2,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'mode': 'TypeB',
|
|
||||||
}),
|
|
||||||
'standByTime': 0,
|
|
||||||
'tankStatus': True,
|
|
||||||
'teaDoses': dict({
|
|
||||||
'DoseA': dict({
|
|
||||||
'doseIndex': 'DoseA',
|
|
||||||
'stopTarget': 8,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'version': 'v1',
|
|
||||||
'weeklySchedulingConfig': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'friday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'monday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'saturday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'sunday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'thursday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'tuesday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
'wednesday': dict({
|
|
||||||
'enabled': True,
|
|
||||||
'h_off': 16,
|
|
||||||
'h_on': 6,
|
|
||||||
'm_off': 0,
|
|
||||||
'm_on': 0,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'current_status': dict({
|
|
||||||
'brew_active': False,
|
'brew_active': False,
|
||||||
'coffee_boiler_on': True,
|
'brew_active_duration': 0,
|
||||||
'coffee_set_temp': 95,
|
'dose_hot_water': 8,
|
||||||
'coffee_temp': 93,
|
'doses': dict({
|
||||||
'dose_hot_water': 1023,
|
'1': 135,
|
||||||
'dose_k1': 1023,
|
'2': 97,
|
||||||
'dose_k2': 1023,
|
'3': 108,
|
||||||
'dose_k3': 1023,
|
'4': 121,
|
||||||
'dose_k4': 1023,
|
|
||||||
'drinks_k1': 13,
|
|
||||||
'drinks_k2': 2,
|
|
||||||
'drinks_k3': 42,
|
|
||||||
'drinks_k4': 34,
|
|
||||||
'enable_prebrewing': True,
|
|
||||||
'enable_preinfusion': False,
|
|
||||||
'fri_auto': 'Disabled',
|
|
||||||
'fri_off_time': '00:00',
|
|
||||||
'fri_on_time': '00:00',
|
|
||||||
'global_auto': 'Enabled',
|
|
||||||
'mon_auto': 'Disabled',
|
|
||||||
'mon_off_time': '00:00',
|
|
||||||
'mon_on_time': '00:00',
|
|
||||||
'power': True,
|
|
||||||
'prebrewing_toff_k1': 5,
|
|
||||||
'prebrewing_toff_k2': 5,
|
|
||||||
'prebrewing_toff_k3': 5,
|
|
||||||
'prebrewing_toff_k4': 5,
|
|
||||||
'prebrewing_ton_k1': 3,
|
|
||||||
'prebrewing_ton_k2': 3,
|
|
||||||
'prebrewing_ton_k3': 3,
|
|
||||||
'prebrewing_ton_k4': 3,
|
|
||||||
'preinfusion_k1': 4,
|
|
||||||
'preinfusion_k2': 4,
|
|
||||||
'preinfusion_k3': 4,
|
|
||||||
'preinfusion_k4': 4,
|
|
||||||
'sat_auto': 'Disabled',
|
|
||||||
'sat_off_time': '00:00',
|
|
||||||
'sat_on_time': '00:00',
|
|
||||||
'steam_boiler_enable': True,
|
|
||||||
'steam_boiler_on': True,
|
|
||||||
'steam_level_set': 3,
|
|
||||||
'steam_set_temp': 128,
|
|
||||||
'steam_temp': 113,
|
|
||||||
'sun_auto': 'Disabled',
|
|
||||||
'sun_off_time': '00:00',
|
|
||||||
'sun_on_time': '00:00',
|
|
||||||
'thu_auto': 'Disabled',
|
|
||||||
'thu_off_time': '00:00',
|
|
||||||
'thu_on_time': '00:00',
|
|
||||||
'total_flushing': 69,
|
|
||||||
'tue_auto': 'Disabled',
|
|
||||||
'tue_off_time': '00:00',
|
|
||||||
'tue_on_time': '00:00',
|
|
||||||
'water_reservoir_contact': True,
|
|
||||||
'wed_auto': 'Disabled',
|
|
||||||
'wed_off_time': '00:00',
|
|
||||||
'wed_on_time': '00:00',
|
|
||||||
}),
|
|
||||||
'firmware': dict({
|
|
||||||
'gateway': dict({
|
|
||||||
'latest_version': 'v3.1-rc4',
|
|
||||||
'version': 'v2.2-rc0',
|
|
||||||
}),
|
}),
|
||||||
'machine': dict({
|
'plumbed_in': True,
|
||||||
'latest_version': '1.2',
|
'prebrew_configuration': dict({
|
||||||
'version': '1.1',
|
'1': dict({
|
||||||
|
'off_time': 1,
|
||||||
|
'on_time': 0.5,
|
||||||
|
}),
|
||||||
|
'2': dict({
|
||||||
|
'off_time': 1,
|
||||||
|
'on_time': 0.5,
|
||||||
|
}),
|
||||||
|
'3': dict({
|
||||||
|
'off_time': 3.299999952316284,
|
||||||
|
'on_time': 3.299999952316284,
|
||||||
|
}),
|
||||||
|
'4': dict({
|
||||||
|
'off_time': 2,
|
||||||
|
'on_time': 2,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
'prebrew_mode': 'TypeB',
|
||||||
|
'smart_standby': dict({
|
||||||
|
'enabled': True,
|
||||||
|
'minutes': 10,
|
||||||
|
'mode': 'LastBrewing',
|
||||||
|
}),
|
||||||
|
'turned_on': True,
|
||||||
|
'wake_up_sleep_entries': dict({
|
||||||
|
'Os2OswX': dict({
|
||||||
|
'days': list([
|
||||||
|
'monday',
|
||||||
|
'tuesday',
|
||||||
|
'wednesday',
|
||||||
|
'thursday',
|
||||||
|
'friday',
|
||||||
|
'saturday',
|
||||||
|
'sunday',
|
||||||
|
]),
|
||||||
|
'enabled': True,
|
||||||
|
'entry_id': 'Os2OswX',
|
||||||
|
'steam': True,
|
||||||
|
'time_off': '24:0',
|
||||||
|
'time_on': '22:0',
|
||||||
|
}),
|
||||||
|
'aXFz5bJ': dict({
|
||||||
|
'days': list([
|
||||||
|
'sunday',
|
||||||
|
]),
|
||||||
|
'enabled': True,
|
||||||
|
'entry_id': 'aXFz5bJ',
|
||||||
|
'steam': True,
|
||||||
|
'time_off': '7:30',
|
||||||
|
'time_on': '7:0',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'water_contact': True,
|
||||||
}),
|
}),
|
||||||
'machine_info': dict({
|
'firmware': list([
|
||||||
'machine_name': 'GS01234',
|
dict({
|
||||||
'serial_number': '**REDACTED**',
|
'machine': dict({
|
||||||
}),
|
'current_version': '1.40',
|
||||||
|
'latest_version': '1.55',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'gateway': dict({
|
||||||
|
'current_version': 'v3.1-rc4',
|
||||||
|
'latest_version': 'v3.5-rc3',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'model': 'GS3 AV',
|
||||||
'statistics': dict({
|
'statistics': dict({
|
||||||
'stats': list([
|
'continous': 2252,
|
||||||
dict({
|
'drink_stats': dict({
|
||||||
'coffeeType': 0,
|
'1': 1047,
|
||||||
'count': 1047,
|
'2': 560,
|
||||||
}),
|
'3': 468,
|
||||||
dict({
|
'4': 312,
|
||||||
'coffeeType': 1,
|
}),
|
||||||
'count': 560,
|
'total_flushes': 1740,
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'coffeeType': 2,
|
|
||||||
'count': 468,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'coffeeType': 3,
|
|
||||||
'count': 312,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'coffeeType': 4,
|
|
||||||
'count': 2252,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'coffeeType': -1,
|
|
||||||
'count': 1740,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[steam_target_temperature-131-set_steam_temp-kwargs0-GS3 AV]
|
# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 AV]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'temperature',
|
'device_class': 'temperature',
|
||||||
@ -72,10 +72,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '128',
|
'state': '123.900001525879',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[steam_target_temperature-131-set_steam_temp-kwargs0-GS3 AV].1
|
# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 AV].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -113,7 +113,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[steam_target_temperature-131-set_steam_temp-kwargs0-GS3 MP]
|
# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 MP]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'temperature',
|
'device_class': 'temperature',
|
||||||
@ -129,10 +129,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '128',
|
'state': '123.900001525879',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[steam_target_temperature-131-set_steam_temp-kwargs0-GS3 MP].1
|
# name: test_gs3_exclusive[steam_target_temperature-131-set_temp-kwargs0-GS3 MP].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -170,7 +170,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_hot_water-kwargs1-GS3 AV]
|
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 AV]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -186,10 +186,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '8',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_hot_water-kwargs1-GS3 AV].1
|
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 AV].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -227,7 +227,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_hot_water-kwargs1-GS3 MP]
|
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 MP]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -243,10 +243,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '8',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_hot_water-kwargs1-GS3 MP].1
|
# name: test_gs3_exclusive[tea_water_duration-15-set_dose_tea_water-kwargs1-GS3 MP].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -284,7 +284,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS01234_dose_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234 Dose Key 1',
|
'friendly_name': 'GS01234 Dose Key 1',
|
||||||
@ -299,10 +299,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '135',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS01234_dose_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234 Dose Key 2',
|
'friendly_name': 'GS01234 Dose Key 2',
|
||||||
@ -317,10 +317,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '97',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS01234_dose_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234 Dose Key 3',
|
'friendly_name': 'GS01234 Dose Key 3',
|
||||||
@ -335,10 +335,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '108',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[dose-6-set_dose-kwargs3-GS3 AV][GS01234_dose_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[dose-6-Disabled-set_dose-kwargs3-GS3 AV][GS01234_dose_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234 Dose Key 4',
|
'friendly_name': 'GS01234 Dose Key 4',
|
||||||
@ -353,10 +353,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1023',
|
'state': '121',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -372,10 +372,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -391,10 +391,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -410,10 +410,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '3.29999995231628',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-configure_prebrew-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS01234_prebrew_off_time_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -429,10 +429,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '2',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -448,10 +448,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -467,10 +467,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -486,10 +486,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '3.29999995231628',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-configure_prebrew-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS01234_prebrew_on_time_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -505,10 +505,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '2',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS01234_preinfusion_time_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -524,10 +524,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS01234_preinfusion_time_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -543,10 +543,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS01234_preinfusion_time_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -562,10 +562,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '3.29999995231628',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-configure_prebrew-kwargs2-GS3 AV][GS01234_preinfusion_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS01234_preinfusion_time_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -581,10 +581,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '2',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Linea Mini]
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Linea Mini]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -600,10 +600,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Linea Mini].1
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Linea Mini].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -641,7 +641,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Micra]
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Micra]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -657,10 +657,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-6-kwargs0-Micra].1
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Micra].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -698,7 +698,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Linea Mini]
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Linea Mini]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -714,10 +714,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Linea Mini].1
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Linea Mini].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -755,7 +755,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Micra]
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Micra]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -771,10 +771,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '5',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-6-kwargs1-Micra].1
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Micra].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -812,7 +812,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Linea Mini]
|
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Linea Mini]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -828,10 +828,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Linea Mini].1
|
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Linea Mini].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -869,7 +869,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Micra]
|
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -885,10 +885,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unavailable',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-7-kwargs2-Micra].1
|
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unknown',
|
'state': 'preinfusion',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_select[GS3 AV].1
|
# name: test_pre_brew_infusion_select[GS3 AV].1
|
||||||
@ -34,7 +34,7 @@
|
|||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
'disabled_by': None,
|
'disabled_by': None,
|
||||||
'domain': 'select',
|
'domain': 'select',
|
||||||
'entity_category': None,
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
'entity_id': 'select.gs01234_prebrew_infusion_mode',
|
'entity_id': 'select.gs01234_prebrew_infusion_mode',
|
||||||
'has_entity_name': True,
|
'has_entity_name': True,
|
||||||
'hidden_by': None,
|
'hidden_by': None,
|
||||||
@ -71,7 +71,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unknown',
|
'state': 'preinfusion',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_select[Linea Mini].1
|
# name: test_pre_brew_infusion_select[Linea Mini].1
|
||||||
@ -91,7 +91,7 @@
|
|||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
'disabled_by': None,
|
'disabled_by': None,
|
||||||
'domain': 'select',
|
'domain': 'select',
|
||||||
'entity_category': None,
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
'entity_id': 'select.lm01234_prebrew_infusion_mode',
|
'entity_id': 'select.lm01234_prebrew_infusion_mode',
|
||||||
'has_entity_name': True,
|
'has_entity_name': True,
|
||||||
'hidden_by': None,
|
'hidden_by': None,
|
||||||
@ -128,7 +128,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unknown',
|
'state': 'preinfusion',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_select[Micra].1
|
# name: test_pre_brew_infusion_select[Micra].1
|
||||||
@ -148,7 +148,7 @@
|
|||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
'disabled_by': None,
|
'disabled_by': None,
|
||||||
'domain': 'select',
|
'domain': 'select',
|
||||||
'entity_category': None,
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
'entity_id': 'select.mr01234_prebrew_infusion_mode',
|
'entity_id': 'select.mr01234_prebrew_infusion_mode',
|
||||||
'has_entity_name': True,
|
'has_entity_name': True,
|
||||||
'hidden_by': None,
|
'hidden_by': None,
|
||||||
@ -185,7 +185,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_steam_boiler_level[Micra].1
|
# name: test_steam_boiler_level[Micra].1
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '93',
|
'state': '96.5',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[GS01234_current_steam_temperature-entry]
|
# name: test_sensors[GS01234_current_steam_temperature-entry]
|
||||||
@ -104,7 +104,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '113',
|
'state': '123.800003051758',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[GS01234_shot_timer-entry]
|
# name: test_sensors[GS01234_shot_timer-entry]
|
||||||
@ -205,7 +205,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '13',
|
'state': '1047',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[GS01234_total_flushes_made-entry]
|
# name: test_sensors[GS01234_total_flushes_made-entry]
|
||||||
@ -255,6 +255,6 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '69',
|
'state': '1740',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -20,16 +20,16 @@
|
|||||||
'labels': set({
|
'labels': set({
|
||||||
}),
|
}),
|
||||||
'manufacturer': 'La Marzocco',
|
'manufacturer': 'La Marzocco',
|
||||||
'model': 'GS3 AV',
|
'model': <MachineModel.GS3_AV: 'GS3 AV'>,
|
||||||
'name': 'GS01234',
|
'name': 'GS01234',
|
||||||
'name_by_user': None,
|
'name_by_user': None,
|
||||||
'serial_number': 'GS01234',
|
'serial_number': 'GS01234',
|
||||||
'suggested_area': None,
|
'suggested_area': None,
|
||||||
'sw_version': '1.1',
|
'sw_version': '1.40',
|
||||||
'via_device_id': None,
|
'via_device_id': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_switches[-set_power-args_on0-args_off0]
|
# name: test_switches[-set_power]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234',
|
'friendly_name': 'GS01234',
|
||||||
@ -42,7 +42,7 @@
|
|||||||
'state': 'on',
|
'state': 'on',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_switches[-set_power-args_on0-args_off0].1
|
# name: test_switches[-set_power].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -75,141 +75,7 @@
|
|||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_switches[-set_power-kwargs_on0-kwargs_off0]
|
# name: test_switches[_steam_boiler-set_steam]
|
||||||
StateSnapshot({
|
|
||||||
'attributes': ReadOnlyDict({
|
|
||||||
'friendly_name': 'GS01234',
|
|
||||||
'icon': 'mdi:power',
|
|
||||||
}),
|
|
||||||
'context': <ANY>,
|
|
||||||
'entity_id': 'switch.gs01234',
|
|
||||||
'last_changed': <ANY>,
|
|
||||||
'last_updated': <ANY>,
|
|
||||||
'state': 'on',
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[-set_power-kwargs_on0-kwargs_off0].1
|
|
||||||
EntityRegistryEntrySnapshot({
|
|
||||||
'aliases': set({
|
|
||||||
}),
|
|
||||||
'area_id': None,
|
|
||||||
'capabilities': None,
|
|
||||||
'config_entry_id': <ANY>,
|
|
||||||
'device_class': None,
|
|
||||||
'device_id': <ANY>,
|
|
||||||
'disabled_by': None,
|
|
||||||
'domain': 'switch',
|
|
||||||
'entity_category': None,
|
|
||||||
'entity_id': 'switch.gs01234',
|
|
||||||
'has_entity_name': True,
|
|
||||||
'hidden_by': None,
|
|
||||||
'icon': None,
|
|
||||||
'id': <ANY>,
|
|
||||||
'name': None,
|
|
||||||
'options': dict({
|
|
||||||
}),
|
|
||||||
'original_device_class': None,
|
|
||||||
'original_icon': 'mdi:power',
|
|
||||||
'original_name': None,
|
|
||||||
'platform': 'lamarzocco',
|
|
||||||
'previous_unique_id': None,
|
|
||||||
'supported_features': 0,
|
|
||||||
'translation_key': None,
|
|
||||||
'unique_id': 'GS01234_main',
|
|
||||||
'unit_of_measurement': None,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1]
|
|
||||||
StateSnapshot({
|
|
||||||
'attributes': ReadOnlyDict({
|
|
||||||
'friendly_name': 'GS01234 Auto on/off',
|
|
||||||
}),
|
|
||||||
'context': <ANY>,
|
|
||||||
'entity_id': 'switch.gs01234_auto_on_off',
|
|
||||||
'last_changed': <ANY>,
|
|
||||||
'last_reported': <ANY>,
|
|
||||||
'last_updated': <ANY>,
|
|
||||||
'state': 'on',
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1].1
|
|
||||||
EntityRegistryEntrySnapshot({
|
|
||||||
'aliases': set({
|
|
||||||
}),
|
|
||||||
'area_id': None,
|
|
||||||
'capabilities': None,
|
|
||||||
'config_entry_id': <ANY>,
|
|
||||||
'device_class': None,
|
|
||||||
'device_id': <ANY>,
|
|
||||||
'disabled_by': None,
|
|
||||||
'domain': 'switch',
|
|
||||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
|
||||||
'entity_id': 'switch.gs01234_auto_on_off',
|
|
||||||
'has_entity_name': True,
|
|
||||||
'hidden_by': None,
|
|
||||||
'icon': None,
|
|
||||||
'id': <ANY>,
|
|
||||||
'labels': set({
|
|
||||||
}),
|
|
||||||
'name': None,
|
|
||||||
'options': dict({
|
|
||||||
}),
|
|
||||||
'original_device_class': None,
|
|
||||||
'original_icon': None,
|
|
||||||
'original_name': 'Auto on/off',
|
|
||||||
'platform': 'lamarzocco',
|
|
||||||
'previous_unique_id': None,
|
|
||||||
'supported_features': 0,
|
|
||||||
'translation_key': 'auto_on_off',
|
|
||||||
'unique_id': 'GS01234_auto_on_off',
|
|
||||||
'unit_of_measurement': None,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1]
|
|
||||||
StateSnapshot({
|
|
||||||
'attributes': ReadOnlyDict({
|
|
||||||
'friendly_name': 'GS01234 Auto on/off',
|
|
||||||
'icon': 'mdi:alarm',
|
|
||||||
}),
|
|
||||||
'context': <ANY>,
|
|
||||||
'entity_id': 'switch.gs01234_auto_on_off',
|
|
||||||
'last_changed': <ANY>,
|
|
||||||
'last_updated': <ANY>,
|
|
||||||
'state': 'on',
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1].1
|
|
||||||
EntityRegistryEntrySnapshot({
|
|
||||||
'aliases': set({
|
|
||||||
}),
|
|
||||||
'area_id': None,
|
|
||||||
'capabilities': None,
|
|
||||||
'config_entry_id': <ANY>,
|
|
||||||
'device_class': None,
|
|
||||||
'device_id': <ANY>,
|
|
||||||
'disabled_by': None,
|
|
||||||
'domain': 'switch',
|
|
||||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
|
||||||
'entity_id': 'switch.gs01234_auto_on_off',
|
|
||||||
'has_entity_name': True,
|
|
||||||
'hidden_by': None,
|
|
||||||
'icon': None,
|
|
||||||
'id': <ANY>,
|
|
||||||
'name': None,
|
|
||||||
'options': dict({
|
|
||||||
}),
|
|
||||||
'original_device_class': None,
|
|
||||||
'original_icon': 'mdi:alarm',
|
|
||||||
'original_name': 'Auto on/off',
|
|
||||||
'platform': 'lamarzocco',
|
|
||||||
'previous_unique_id': None,
|
|
||||||
'supported_features': 0,
|
|
||||||
'translation_key': 'auto_on_off',
|
|
||||||
'unique_id': 'GS01234_auto_on_off',
|
|
||||||
'unit_of_measurement': None,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2]
|
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'GS01234 Steam boiler',
|
'friendly_name': 'GS01234 Steam boiler',
|
||||||
@ -222,53 +88,7 @@
|
|||||||
'state': 'on',
|
'state': 'on',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2].1
|
# name: test_switches[_steam_boiler-set_steam].1
|
||||||
EntityRegistryEntrySnapshot({
|
|
||||||
'aliases': set({
|
|
||||||
}),
|
|
||||||
'area_id': None,
|
|
||||||
'capabilities': None,
|
|
||||||
'config_entry_id': <ANY>,
|
|
||||||
'device_class': None,
|
|
||||||
'device_id': <ANY>,
|
|
||||||
'disabled_by': None,
|
|
||||||
'domain': 'switch',
|
|
||||||
'entity_category': None,
|
|
||||||
'entity_id': 'switch.gs01234_steam_boiler',
|
|
||||||
'has_entity_name': True,
|
|
||||||
'hidden_by': None,
|
|
||||||
'icon': None,
|
|
||||||
'id': <ANY>,
|
|
||||||
'labels': set({
|
|
||||||
}),
|
|
||||||
'name': None,
|
|
||||||
'options': dict({
|
|
||||||
}),
|
|
||||||
'original_device_class': None,
|
|
||||||
'original_icon': None,
|
|
||||||
'original_name': 'Steam boiler',
|
|
||||||
'platform': 'lamarzocco',
|
|
||||||
'previous_unique_id': None,
|
|
||||||
'supported_features': 0,
|
|
||||||
'translation_key': 'steam_boiler',
|
|
||||||
'unique_id': 'GS01234_steam_boiler_enable',
|
|
||||||
'unit_of_measurement': None,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2]
|
|
||||||
StateSnapshot({
|
|
||||||
'attributes': ReadOnlyDict({
|
|
||||||
'friendly_name': 'GS01234 Steam boiler',
|
|
||||||
'icon': 'mdi:water-boiler',
|
|
||||||
}),
|
|
||||||
'context': <ANY>,
|
|
||||||
'entity_id': 'switch.gs01234_steam_boiler',
|
|
||||||
'last_changed': <ANY>,
|
|
||||||
'last_updated': <ANY>,
|
|
||||||
'state': 'on',
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2].1
|
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
||||||
'friendly_name': 'GS01234 Gateway firmware',
|
'friendly_name': 'GS01234 Gateway firmware',
|
||||||
'in_progress': False,
|
'in_progress': False,
|
||||||
'installed_version': 'v2.2-rc0',
|
'installed_version': 'v3.1-rc4',
|
||||||
'latest_version': 'v3.1-rc4',
|
'latest_version': 'v3.5-rc3',
|
||||||
'release_summary': None,
|
'release_summary': None,
|
||||||
'release_url': None,
|
'release_url': None,
|
||||||
'skipped_version': None,
|
'skipped_version': None,
|
||||||
@ -64,8 +64,8 @@
|
|||||||
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
||||||
'friendly_name': 'GS01234 Machine firmware',
|
'friendly_name': 'GS01234 Machine firmware',
|
||||||
'in_progress': False,
|
'in_progress': False,
|
||||||
'installed_version': '1.1',
|
'installed_version': '1.40',
|
||||||
'latest_version': '1.2',
|
'latest_version': '1.55',
|
||||||
'release_summary': None,
|
'release_summary': None,
|
||||||
'release_url': None,
|
'release_url': None,
|
||||||
'skipped_version': None,
|
'skipped_version': None,
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
"""Tests for La Marzocco binary sensors."""
|
"""Tests for La Marzocco binary sensors."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from lmcloud.exceptions import RequestNotSuccessful
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -11,7 +14,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
|
|
||||||
from . import async_init_integration
|
from . import async_init_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
BINARY_SENSORS = (
|
BINARY_SENSORS = (
|
||||||
"brewing_active",
|
"brewing_active",
|
||||||
@ -70,3 +73,29 @@ async def test_brew_active_unavailable(
|
|||||||
)
|
)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_going_unavailable(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test sensor is going unavailable after an unsuccessful update."""
|
||||||
|
brewing_active_sensor = (
|
||||||
|
f"binary_sensor.{mock_lamarzocco.serial_number}_brewing_active"
|
||||||
|
)
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
state = hass.states.get(brewing_active_sensor)
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
mock_lamarzocco.get_config.side_effect = RequestNotSuccessful("")
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(brewing_active_sensor)
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import async_init_integration
|
from . import WAKE_UP_SLEEP_ENTRY_IDS, async_init_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -40,27 +40,37 @@ async def test_calendar_events(
|
|||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
state = hass.states.get(f"calendar.{serial_number}_auto_on_off_schedule")
|
for identifier in WAKE_UP_SLEEP_ENTRY_IDS:
|
||||||
assert state
|
identifier = identifier.lower()
|
||||||
assert state == snapshot
|
state = hass.states.get(
|
||||||
|
f"calendar.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state == snapshot(
|
||||||
|
name=f"state.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||||
|
)
|
||||||
|
|
||||||
entry = entity_registry.async_get(state.entity_id)
|
entry = entity_registry.async_get(state.entity_id)
|
||||||
assert entry
|
assert entry
|
||||||
assert entry == snapshot
|
assert entry == snapshot(
|
||||||
|
name=f"entry.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||||
|
)
|
||||||
|
|
||||||
events = await hass.services.async_call(
|
events = await hass.services.async_call(
|
||||||
CALENDAR_DOMAIN,
|
CALENDAR_DOMAIN,
|
||||||
SERVICE_GET_EVENTS,
|
SERVICE_GET_EVENTS,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule",
|
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule_{identifier}",
|
||||||
EVENT_START_DATETIME: test_time,
|
EVENT_START_DATETIME: test_time,
|
||||||
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
return_response=True,
|
return_response=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert events == snapshot
|
assert events == snapshot(
|
||||||
|
name=f"events.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -89,21 +99,13 @@ async def test_calendar_edge_cases(
|
|||||||
start_date = start_date.replace(tzinfo=dt_util.get_default_time_zone())
|
start_date = start_date.replace(tzinfo=dt_util.get_default_time_zone())
|
||||||
end_date = end_date.replace(tzinfo=dt_util.get_default_time_zone())
|
end_date = end_date.replace(tzinfo=dt_util.get_default_time_zone())
|
||||||
|
|
||||||
# set schedule to be only on Sunday, 07:00 - 07:30
|
|
||||||
mock_lamarzocco.schedule[2]["enable"] = "Disabled"
|
|
||||||
mock_lamarzocco.schedule[4]["enable"] = "Disabled"
|
|
||||||
mock_lamarzocco.schedule[5]["enable"] = "Disabled"
|
|
||||||
mock_lamarzocco.schedule[6]["enable"] = "Enabled"
|
|
||||||
mock_lamarzocco.schedule[6]["on"] = "07:00"
|
|
||||||
mock_lamarzocco.schedule[6]["off"] = "07:30"
|
|
||||||
|
|
||||||
await async_init_integration(hass, mock_config_entry)
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
events = await hass.services.async_call(
|
events = await hass.services.async_call(
|
||||||
CALENDAR_DOMAIN,
|
CALENDAR_DOMAIN,
|
||||||
SERVICE_GET_EVENTS,
|
SERVICE_GET_EVENTS,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"calendar.{mock_lamarzocco.serial_number}_auto_on_off_schedule",
|
ATTR_ENTITY_ID: f"calendar.{mock_lamarzocco.serial_number}_auto_on_off_schedule_{WAKE_UP_SLEEP_ENTRY_IDS[1].lower()}",
|
||||||
EVENT_START_DATETIME: start_date,
|
EVENT_START_DATETIME: start_date,
|
||||||
EVENT_END_DATETIME: end_date,
|
EVENT_END_DATETIME: end_date,
|
||||||
},
|
},
|
||||||
@ -123,7 +125,9 @@ async def test_no_calendar_events_global_disable(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Assert no events when global auto on/off is disabled."""
|
"""Assert no events when global auto on/off is disabled."""
|
||||||
|
|
||||||
mock_lamarzocco.current_status["global_auto"] = "Disabled"
|
wake_up_sleep_entry_id = WAKE_UP_SLEEP_ENTRY_IDS[0]
|
||||||
|
|
||||||
|
mock_lamarzocco.config.wake_up_sleep_entries[wake_up_sleep_entry_id].enabled = False
|
||||||
test_time = datetime(2024, 1, 12, 11, tzinfo=dt_util.get_default_time_zone())
|
test_time = datetime(2024, 1, 12, 11, tzinfo=dt_util.get_default_time_zone())
|
||||||
freezer.move_to(test_time)
|
freezer.move_to(test_time)
|
||||||
|
|
||||||
@ -131,14 +135,16 @@ async def test_no_calendar_events_global_disable(
|
|||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
state = hass.states.get(f"calendar.{serial_number}_auto_on_off_schedule")
|
state = hass.states.get(
|
||||||
|
f"calendar.{serial_number}_auto_on_off_schedule_{wake_up_sleep_entry_id.lower()}"
|
||||||
|
)
|
||||||
assert state
|
assert state
|
||||||
|
|
||||||
events = await hass.services.async_call(
|
events = await hass.services.async_call(
|
||||||
CALENDAR_DOMAIN,
|
CALENDAR_DOMAIN,
|
||||||
SERVICE_GET_EVENTS,
|
SERVICE_GET_EVENTS,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule",
|
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule_{wake_up_sleep_entry_id.lower()}",
|
||||||
EVENT_START_DATETIME: test_time,
|
EVENT_START_DATETIME: test_time,
|
||||||
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
"""Test the La Marzocco config flow."""
|
"""Test the La Marzocco config flow."""
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||||
|
from lmcloud.models import LaMarzoccoDeviceInfo
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
|
||||||
from homeassistant.components.lamarzocco.const import (
|
from homeassistant.components.lamarzocco.const import CONF_USE_BLUETOOTH, DOMAIN
|
||||||
CONF_MACHINE,
|
from homeassistant.config_entries import (
|
||||||
CONF_USE_BLUETOOTH,
|
SOURCE_BLUETOOTH,
|
||||||
DOMAIN,
|
SOURCE_REAUTH,
|
||||||
|
SOURCE_USER,
|
||||||
|
ConfigEntryState,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_MAC,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_TOKEN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
||||||
|
|
||||||
@ -21,7 +30,7 @@ from tests.common import MockConfigEntry
|
|||||||
|
|
||||||
|
|
||||||
async def __do_successful_user_step(
|
async def __do_successful_user_step(
|
||||||
hass: HomeAssistant, result: FlowResult
|
hass: HomeAssistant, result: FlowResult, mock_cloud_client: MagicMock
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Successfully configure the user step."""
|
"""Successfully configure the user step."""
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -36,51 +45,63 @@ async def __do_successful_user_step(
|
|||||||
|
|
||||||
|
|
||||||
async def __do_sucessful_machine_selection_step(
|
async def __do_sucessful_machine_selection_step(
|
||||||
hass: HomeAssistant, result2: FlowResult, mock_lamarzocco: MagicMock
|
hass: HomeAssistant, result2: FlowResult, mock_device_info: LaMarzoccoDeviceInfo
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Successfully configure the machine selection step."""
|
"""Successfully configure the machine selection step."""
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
|
||||||
result2["flow_id"],
|
with patch(
|
||||||
{
|
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||||
CONF_HOST: "192.168.1.1",
|
return_value=True,
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
):
|
||||||
},
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
)
|
result2["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.1",
|
||||||
|
CONF_MACHINE: mock_device_info.serial_number,
|
||||||
|
},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
|
||||||
assert result3["title"] == mock_lamarzocco.serial_number
|
assert result3["title"] == "GS3"
|
||||||
assert result3["data"] == {
|
assert result3["data"] == {
|
||||||
**USER_INPUT,
|
**USER_INPUT,
|
||||||
CONF_HOST: "192.168.1.1",
|
CONF_HOST: "192.168.1.1",
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
CONF_MODEL: mock_device_info.model,
|
||||||
|
CONF_NAME: mock_device_info.name,
|
||||||
|
CONF_TOKEN: mock_device_info.communication_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_form(hass: HomeAssistant, mock_lamarzocco: MagicMock) -> None:
|
async def test_form(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
|
) -> None:
|
||||||
"""Test we get the form."""
|
"""Test we get the form."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
result2 = await __do_successful_user_step(hass, result)
|
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||||
|
|
||||||
assert len(mock_lamarzocco.check_local_connection.mock_calls) == 1
|
|
||||||
|
|
||||||
|
|
||||||
async def test_form_abort_already_configured(
|
async def test_form_abort_already_configured(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we abort if already configured."""
|
"""Test we abort if already configured."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
@ -98,7 +119,7 @@ async def test_form_abort_already_configured(
|
|||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_HOST: "192.168.1.1",
|
CONF_HOST: "192.168.1.1",
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
CONF_MACHINE: mock_device_info.serial_number,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -108,13 +129,15 @@ async def test_form_abort_already_configured(
|
|||||||
|
|
||||||
|
|
||||||
async def test_form_invalid_auth(
|
async def test_form_invalid_auth(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
hass: HomeAssistant,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test invalid auth error."""
|
"""Test invalid auth error."""
|
||||||
|
|
||||||
mock_lamarzocco.get_all_machines.side_effect = AuthFail("")
|
mock_cloud_client.get_customer_fleet.side_effect = AuthFail("")
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -124,20 +147,22 @@ async def test_form_invalid_auth(
|
|||||||
|
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": "invalid_auth"}
|
assert result2["errors"] == {"base": "invalid_auth"}
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
|
|
||||||
# test recovery from failure
|
# test recovery from failure
|
||||||
mock_lamarzocco.get_all_machines.side_effect = None
|
mock_cloud_client.get_customer_fleet.side_effect = None
|
||||||
result2 = await __do_successful_user_step(hass, result)
|
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||||
|
|
||||||
|
|
||||||
async def test_form_invalid_host(
|
async def test_form_invalid_host(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
hass: HomeAssistant,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test invalid auth error."""
|
"""Test invalid auth error."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
@ -148,38 +173,41 @@ async def test_form_invalid_host(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_lamarzocco.check_local_connection.return_value = False
|
|
||||||
|
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "machine_selection"
|
assert result2["step_id"] == "machine_selection"
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
with patch(
|
||||||
result2["flow_id"],
|
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||||
{
|
return_value=False,
|
||||||
CONF_HOST: "192.168.1.1",
|
):
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
},
|
result2["flow_id"],
|
||||||
)
|
{
|
||||||
|
CONF_HOST: "192.168.1.1",
|
||||||
|
CONF_MACHINE: mock_device_info.serial_number,
|
||||||
|
},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.FORM
|
assert result3["type"] is FlowResultType.FORM
|
||||||
assert result3["errors"] == {"host": "cannot_connect"}
|
assert result3["errors"] == {"host": "cannot_connect"}
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
|
|
||||||
# test recovery from failure
|
# test recovery from failure
|
||||||
mock_lamarzocco.check_local_connection.return_value = True
|
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_form_cannot_connect(
|
async def test_form_cannot_connect(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
hass: HomeAssistant,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test cannot connect error."""
|
"""Test cannot connect error."""
|
||||||
|
|
||||||
mock_lamarzocco.get_all_machines.return_value = []
|
mock_cloud_client.get_customer_fleet.return_value = {}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -189,9 +217,9 @@ async def test_form_cannot_connect(
|
|||||||
|
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": "no_machines"}
|
assert result2["errors"] == {"base": "no_machines"}
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
|
|
||||||
mock_lamarzocco.get_all_machines.side_effect = RequestNotSuccessful("")
|
mock_cloud_client.get_customer_fleet.side_effect = RequestNotSuccessful("")
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
USER_INPUT,
|
USER_INPUT,
|
||||||
@ -199,21 +227,26 @@ async def test_form_cannot_connect(
|
|||||||
|
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": "cannot_connect"}
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 2
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 2
|
||||||
|
|
||||||
# test recovery from failure
|
# test recovery from failure
|
||||||
mock_lamarzocco.get_all_machines.side_effect = None
|
mock_cloud_client.get_customer_fleet.side_effect = None
|
||||||
mock_lamarzocco.get_all_machines.return_value = [
|
mock_cloud_client.get_customer_fleet.return_value = {
|
||||||
(mock_lamarzocco.serial_number, mock_lamarzocco.model_name)
|
mock_device_info.serial_number: mock_device_info
|
||||||
]
|
}
|
||||||
result2 = await __do_successful_user_step(hass, result)
|
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth_flow(
|
async def test_reauth_flow(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry
|
hass: HomeAssistant,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that the reauth flow."""
|
"""Test that the reauth flow."""
|
||||||
|
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={
|
context={
|
||||||
@ -235,19 +268,21 @@ async def test_reauth_flow(
|
|||||||
assert result2["type"] is FlowResultType.ABORT
|
assert result2["type"] is FlowResultType.ABORT
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result2["reason"] == "reauth_successful"
|
assert result2["reason"] == "reauth_successful"
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
assert mock_config_entry.data[CONF_PASSWORD] == "new_password"
|
assert mock_config_entry.data[CONF_PASSWORD] == "new_password"
|
||||||
|
|
||||||
|
|
||||||
async def test_bluetooth_discovery(
|
async def test_bluetooth_discovery(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
hass: HomeAssistant,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test bluetooth discovery."""
|
"""Test bluetooth discovery."""
|
||||||
service_info = get_bluetooth_service_info(
|
service_info = get_bluetooth_service_info(
|
||||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
mock_lamarzocco.model, mock_lamarzocco.serial_number
|
||||||
)
|
)
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, data=service_info
|
DOMAIN, context={"source": SOURCE_BLUETOOTH}, data=service_info
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
@ -260,82 +295,95 @@ async def test_bluetooth_discovery(
|
|||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "machine_selection"
|
assert result2["step_id"] == "machine_selection"
|
||||||
|
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
with patch(
|
||||||
result2["flow_id"],
|
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||||
{
|
return_value=True,
|
||||||
CONF_HOST: "192.168.1.1",
|
):
|
||||||
},
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
)
|
result2["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.1",
|
||||||
|
},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
|
||||||
assert result3["title"] == mock_lamarzocco.serial_number
|
assert result3["title"] == "GS3"
|
||||||
assert result3["data"] == {
|
assert result3["data"] == {
|
||||||
**USER_INPUT,
|
**USER_INPUT,
|
||||||
CONF_HOST: "192.168.1.1",
|
CONF_HOST: "192.168.1.1",
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||||
CONF_NAME: service_info.name,
|
CONF_NAME: "GS3",
|
||||||
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||||
|
CONF_MODEL: mock_lamarzocco.model,
|
||||||
|
CONF_TOKEN: "token",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_lamarzocco.check_local_connection.mock_calls) == 1
|
|
||||||
|
|
||||||
|
|
||||||
async def test_bluetooth_discovery_errors(
|
async def test_bluetooth_discovery_errors(
|
||||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
hass: HomeAssistant,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_device_info: LaMarzoccoDeviceInfo,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test bluetooth discovery errors."""
|
"""Test bluetooth discovery errors."""
|
||||||
service_info = get_bluetooth_service_info(
|
service_info = get_bluetooth_service_info(
|
||||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
mock_lamarzocco.model, mock_lamarzocco.serial_number
|
||||||
)
|
)
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_BLUETOOTH},
|
context={"source": SOURCE_BLUETOOTH},
|
||||||
data=service_info,
|
data=service_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
mock_lamarzocco.get_all_machines.return_value = [("GS98765", "GS3 MP")]
|
mock_cloud_client.get_customer_fleet.return_value = {"GS98765", ""}
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
USER_INPUT,
|
USER_INPUT,
|
||||||
)
|
)
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": "machine_not_found"}
|
assert result2["errors"] == {"base": "machine_not_found"}
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||||
|
|
||||||
mock_lamarzocco.get_all_machines.return_value = [
|
mock_cloud_client.get_customer_fleet.return_value = {
|
||||||
(mock_lamarzocco.serial_number, mock_lamarzocco.model_name)
|
mock_device_info.serial_number: mock_device_info
|
||||||
]
|
}
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
USER_INPUT,
|
USER_INPUT,
|
||||||
)
|
)
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "machine_selection"
|
assert result2["step_id"] == "machine_selection"
|
||||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 2
|
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 2
|
||||||
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
with patch(
|
||||||
result2["flow_id"],
|
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||||
{
|
return_value=True,
|
||||||
CONF_HOST: "192.168.1.1",
|
):
|
||||||
},
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
)
|
result2["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.1",
|
||||||
|
},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
|
||||||
assert result3["title"] == mock_lamarzocco.serial_number
|
assert result3["title"] == "GS3"
|
||||||
assert result3["data"] == {
|
assert result3["data"] == {
|
||||||
**USER_INPUT,
|
**USER_INPUT,
|
||||||
CONF_HOST: "192.168.1.1",
|
CONF_HOST: "192.168.1.1",
|
||||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||||
CONF_NAME: service_info.name,
|
CONF_NAME: "GS3",
|
||||||
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||||
|
CONF_MODEL: mock_lamarzocco.model,
|
||||||
|
CONF_TOKEN: "token",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
"""Test initialization of lamarzocco."""
|
"""Test initialization of lamarzocco."""
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
from lmcloud.const import FirmwareType
|
||||||
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
|
||||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import CONF_MAC, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
|
||||||
from . import async_init_integration, get_bluetooth_service_info
|
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -20,7 +24,9 @@ async def test_load_unload_config_entry(
|
|||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test loading and unloading the integration."""
|
"""Test loading and unloading the integration."""
|
||||||
await async_init_integration(hass, mock_config_entry)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
@ -36,11 +42,13 @@ async def test_config_entry_not_ready(
|
|||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco configuration entry not ready."""
|
"""Test the La Marzocco configuration entry not ready."""
|
||||||
mock_lamarzocco.update_local_machine_status.side_effect = RequestNotSuccessful("")
|
mock_lamarzocco.get_config.side_effect = RequestNotSuccessful("")
|
||||||
|
|
||||||
await async_init_integration(hass, mock_config_entry)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1
|
assert len(mock_lamarzocco.get_config.mock_calls) == 1
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
@ -50,11 +58,13 @@ async def test_invalid_auth(
|
|||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test auth error during setup."""
|
"""Test auth error during setup."""
|
||||||
mock_lamarzocco.update_local_machine_status.side_effect = AuthFail("")
|
mock_lamarzocco.get_config.side_effect = AuthFail("")
|
||||||
await async_init_integration(hass, mock_config_entry)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1
|
assert len(mock_lamarzocco.get_config.mock_calls) == 1
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
@ -68,27 +78,132 @@ async def test_invalid_auth(
|
|||||||
assert flow["context"].get("entry_id") == mock_config_entry.entry_id
|
assert flow["context"].get("entry_id") == mock_config_entry.entry_id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_v1_migration(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test v1 -> v2 Migration."""
|
||||||
|
entry_v1 = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
version=1,
|
||||||
|
unique_id=mock_lamarzocco.serial_number,
|
||||||
|
data={
|
||||||
|
**USER_INPUT,
|
||||||
|
CONF_HOST: "host",
|
||||||
|
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||||
|
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
entry_v1.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry_v1.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry_v1.version == 2
|
||||||
|
assert dict(entry_v1.data) == dict(mock_config_entry.data) | {
|
||||||
|
CONF_MAC: "aa:bb:cc:dd:ee:ff"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migration_errors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_cloud_client: MagicMock,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test errors during migration."""
|
||||||
|
|
||||||
|
mock_cloud_client.get_customer_fleet.side_effect = RequestNotSuccessful("Error")
|
||||||
|
|
||||||
|
entry_v1 = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
version=1,
|
||||||
|
unique_id=mock_lamarzocco.serial_number,
|
||||||
|
data={
|
||||||
|
**USER_INPUT,
|
||||||
|
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry_v1.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert not await hass.config_entries.async_setup(entry_v1.entry_id)
|
||||||
|
assert entry_v1.state is ConfigEntryState.MIGRATION_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_entry_migration_downgrade(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test that config entry fails setup if the version is from the future."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, version=3)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def test_bluetooth_is_set_from_discovery(
|
async def test_bluetooth_is_set_from_discovery(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Assert we're not searching for a new BT device when we already found one previously."""
|
"""Check we can fill a device from discovery info."""
|
||||||
|
|
||||||
# remove the bluetooth configuration from entry
|
|
||||||
data = mock_config_entry.data.copy()
|
|
||||||
del data[CONF_NAME]
|
|
||||||
del data[CONF_MAC]
|
|
||||||
hass.config_entries.async_update_entry(mock_config_entry, data=data)
|
|
||||||
|
|
||||||
service_info = get_bluetooth_service_info(
|
service_info = get_bluetooth_service_info(
|
||||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
mock_lamarzocco.model, mock_lamarzocco.serial_number
|
||||||
)
|
)
|
||||||
with patch(
|
with (
|
||||||
"homeassistant.components.lamarzocco.coordinator.async_discovered_service_info",
|
patch(
|
||||||
return_value=[service_info],
|
"homeassistant.components.lamarzocco.async_discovered_service_info",
|
||||||
|
return_value=[service_info],
|
||||||
|
) as discovery,
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoMachine"
|
||||||
|
) as init_device,
|
||||||
):
|
):
|
||||||
await async_init_integration(hass, mock_config_entry)
|
await async_init_integration(hass, mock_config_entry)
|
||||||
mock_lamarzocco.init_bluetooth_with_known_device.assert_called_once()
|
discovery.assert_called_once()
|
||||||
|
init_device.assert_called_once()
|
||||||
|
_, kwargs = init_device.call_args
|
||||||
|
assert kwargs["bluetooth_client"] is not None
|
||||||
assert mock_config_entry.data[CONF_NAME] == service_info.name
|
assert mock_config_entry.data[CONF_NAME] == service_info.name
|
||||||
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_closed_on_unload(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the websocket is closed on unload."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.lamarzocco.LaMarzoccoLocalClient",
|
||||||
|
autospec=True,
|
||||||
|
) as local_client:
|
||||||
|
client = local_client.return_value
|
||||||
|
client.websocket = AsyncMock()
|
||||||
|
client.websocket.connected = True
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
client.websocket.close.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("version", "issue_exists"), [("v3.5-rc6", False), ("v3.3-rc4", True)]
|
||||||
|
)
|
||||||
|
async def test_gateway_version_issue(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
version: str,
|
||||||
|
issue_exists: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Make sure we get the issue for certain gateway firmware versions."""
|
||||||
|
mock_lamarzocco.firmware[FirmwareType.GATEWAY].current_version = version
|
||||||
|
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
issue_registry = ir.async_get(hass)
|
||||||
|
issue = issue_registry.async_get_issue(DOMAIN, "unsupported_gateway_firmware")
|
||||||
|
assert (issue is not None) == issue_exists
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel
|
from lmcloud.const import (
|
||||||
|
KEYS_PER_MODEL,
|
||||||
|
BoilerType,
|
||||||
|
MachineModel,
|
||||||
|
PhysicalKey,
|
||||||
|
PrebrewMode,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -15,17 +21,22 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
from . import async_init_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
async def test_coffee_boiler(
|
async def test_coffee_boiler(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco coffee temperature Number."""
|
"""Test the La Marzocco coffee temperature Number."""
|
||||||
|
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
state = hass.states.get(f"number.{serial_number}_coffee_target_temperature")
|
state = hass.states.get(f"number.{serial_number}_coffee_target_temperature")
|
||||||
@ -47,35 +58,34 @@ async def test_coffee_boiler(
|
|||||||
SERVICE_SET_VALUE,
|
SERVICE_SET_VALUE,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"number.{serial_number}_coffee_target_temperature",
|
ATTR_ENTITY_ID: f"number.{serial_number}_coffee_target_temperature",
|
||||||
ATTR_VALUE: 95,
|
ATTR_VALUE: 94,
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_lamarzocco.set_coffee_temp.mock_calls) == 1
|
assert len(mock_lamarzocco.set_temp.mock_calls) == 1
|
||||||
mock_lamarzocco.set_coffee_temp.assert_called_once_with(
|
mock_lamarzocco.set_temp.assert_called_once_with(
|
||||||
temperature=95, ble_device=None
|
boiler=BoilerType.COFFEE, temperature=94
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV, MachineModel.GS3_MP])
|
||||||
"device_fixture", [LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP]
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_name", "value", "func_name", "kwargs"),
|
("entity_name", "value", "func_name", "kwargs"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"steam_target_temperature",
|
"steam_target_temperature",
|
||||||
131,
|
131,
|
||||||
"set_steam_temp",
|
"set_temp",
|
||||||
{"temperature": 131, "ble_device": None},
|
{"boiler": BoilerType.STEAM, "temperature": 131},
|
||||||
),
|
),
|
||||||
("tea_water_duration", 15, "set_dose_hot_water", {"value": 15}),
|
("tea_water_duration", 15, "set_dose_tea_water", {"dose": 15}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_gs3_exclusive(
|
async def test_gs3_exclusive(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
@ -85,7 +95,7 @@ async def test_gs3_exclusive(
|
|||||||
kwargs: dict[str, float],
|
kwargs: dict[str, float],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test exclusive entities for GS3 AV/MP."""
|
"""Test exclusive entities for GS3 AV/MP."""
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
func = getattr(mock_lamarzocco, func_name)
|
func = getattr(mock_lamarzocco, func_name)
|
||||||
@ -118,14 +128,15 @@ async def test_gs3_exclusive(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture", [LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI]
|
"device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI]
|
||||||
)
|
)
|
||||||
async def test_gs3_exclusive_none(
|
async def test_gs3_exclusive_none(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Ensure GS3 exclusive is None for unsupported models."""
|
"""Ensure GS3 exclusive is None for unsupported models."""
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
ENTITIES = ("steam_target_temperature", "tea_water_duration")
|
ENTITIES = ("steam_target_temperature", "tea_water_duration")
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
@ -135,29 +146,50 @@ async def test_gs3_exclusive_none(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture", [LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI]
|
"device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI]
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_name", "value", "kwargs"),
|
("entity_name", "function_name", "prebrew_mode", "value", "kwargs"),
|
||||||
[
|
[
|
||||||
("prebrew_off_time", 6, {"on_time": 3000, "off_time": 6000, "key": 1}),
|
(
|
||||||
("prebrew_on_time", 6, {"on_time": 6000, "off_time": 5000, "key": 1}),
|
"prebrew_off_time",
|
||||||
("preinfusion_time", 7, {"off_time": 7000, "key": 1}),
|
"set_prebrew_time",
|
||||||
|
PrebrewMode.PREBREW,
|
||||||
|
6,
|
||||||
|
{"prebrew_off_time": 6.0, "key": PhysicalKey.A},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"prebrew_on_time",
|
||||||
|
"set_prebrew_time",
|
||||||
|
PrebrewMode.PREBREW,
|
||||||
|
6,
|
||||||
|
{"prebrew_on_time": 6.0, "key": PhysicalKey.A},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"preinfusion_time",
|
||||||
|
"set_preinfusion_time",
|
||||||
|
PrebrewMode.PREINFUSION,
|
||||||
|
7,
|
||||||
|
{"preinfusion_time": 7.0, "key": PhysicalKey.A},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_pre_brew_infusion_numbers(
|
async def test_pre_brew_infusion_numbers(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
device_registry: dr.DeviceRegistry,
|
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_name: str,
|
entity_name: str,
|
||||||
|
function_name: str,
|
||||||
|
prebrew_mode: PrebrewMode,
|
||||||
value: float,
|
value: float,
|
||||||
kwargs: dict[str, float],
|
kwargs: dict[str, float],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco prebrew/-infusion sensors."""
|
"""Test the La Marzocco prebrew/-infusion sensors."""
|
||||||
|
|
||||||
mock_lamarzocco.current_status["enable_preinfusion"] = True
|
mock_lamarzocco.config.prebrew_mode = prebrew_mode
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
@ -168,12 +200,8 @@ async def test_pre_brew_infusion_numbers(
|
|||||||
|
|
||||||
entry = entity_registry.async_get(state.entity_id)
|
entry = entity_registry.async_get(state.entity_id)
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.device_id
|
|
||||||
assert entry == snapshot
|
assert entry == snapshot
|
||||||
|
|
||||||
device = device_registry.async_get(entry.device_id)
|
|
||||||
assert device
|
|
||||||
|
|
||||||
# service call
|
# service call
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
NUMBER_DOMAIN,
|
NUMBER_DOMAIN,
|
||||||
@ -185,43 +213,97 @@ async def test_pre_brew_infusion_numbers(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_lamarzocco.configure_prebrew.mock_calls) == 1
|
function = getattr(mock_lamarzocco, function_name)
|
||||||
mock_lamarzocco.configure_prebrew.assert_called_once_with(**kwargs)
|
function.assert_called_once_with(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.GS3_AV])
|
@pytest.mark.parametrize(
|
||||||
|
"device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI]
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("prebrew_mode", "entity", "unavailable"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
PrebrewMode.PREBREW,
|
||||||
|
("prebrew_off_time", "prebrew_on_time"),
|
||||||
|
("preinfusion_time",),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PrebrewMode.PREINFUSION,
|
||||||
|
("preinfusion_time",),
|
||||||
|
("prebrew_off_time", "prebrew_on_time"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_pre_brew_infusion_numbers_unavailable(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
prebrew_mode: PrebrewMode,
|
||||||
|
entity: tuple[str, ...],
|
||||||
|
unavailable: tuple[str, ...],
|
||||||
|
) -> None:
|
||||||
|
"""Test entities are unavailable depending on selected state."""
|
||||||
|
|
||||||
|
mock_lamarzocco.config.prebrew_mode = prebrew_mode
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
for entity_name in entity:
|
||||||
|
state = hass.states.get(f"number.{serial_number}_{entity_name}")
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
for entity_name in unavailable:
|
||||||
|
state = hass.states.get(f"number.{serial_number}_{entity_name}")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV])
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_name", "value", "function_name", "kwargs"),
|
("entity_name", "value", "prebrew_mode", "function_name", "kwargs"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"prebrew_off_time",
|
"prebrew_off_time",
|
||||||
6,
|
6,
|
||||||
"configure_prebrew",
|
PrebrewMode.PREBREW,
|
||||||
{"on_time": 3000, "off_time": 6000},
|
"set_prebrew_time",
|
||||||
|
{"prebrew_off_time": 6.0},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"prebrew_on_time",
|
"prebrew_on_time",
|
||||||
6,
|
6,
|
||||||
"configure_prebrew",
|
PrebrewMode.PREBREW,
|
||||||
{"on_time": 6000, "off_time": 5000},
|
"set_prebrew_time",
|
||||||
|
{"prebrew_on_time": 6.0},
|
||||||
),
|
),
|
||||||
("preinfusion_time", 7, "configure_prebrew", {"off_time": 7000}),
|
(
|
||||||
("dose", 6, "set_dose", {"value": 6}),
|
"preinfusion_time",
|
||||||
|
7,
|
||||||
|
PrebrewMode.PREINFUSION,
|
||||||
|
"set_preinfusion_time",
|
||||||
|
{"preinfusion_time": 7.0},
|
||||||
|
),
|
||||||
|
("dose", 6, PrebrewMode.DISABLED, "set_dose", {"dose": 6}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_pre_brew_infusion_key_numbers(
|
async def test_pre_brew_infusion_key_numbers(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_name: str,
|
entity_name: str,
|
||||||
value: float,
|
value: float,
|
||||||
|
prebrew_mode: PrebrewMode,
|
||||||
function_name: str,
|
function_name: str,
|
||||||
kwargs: dict[str, float],
|
kwargs: dict[str, float],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco number sensors for GS3AV model."""
|
"""Test the La Marzocco number sensors for GS3AV model."""
|
||||||
|
|
||||||
mock_lamarzocco.current_status["enable_preinfusion"] = True
|
mock_lamarzocco.config.prebrew_mode = prebrew_mode
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
@ -230,7 +312,7 @@ async def test_pre_brew_infusion_key_numbers(
|
|||||||
state = hass.states.get(f"number.{serial_number}_{entity_name}")
|
state = hass.states.get(f"number.{serial_number}_{entity_name}")
|
||||||
assert state is None
|
assert state is None
|
||||||
|
|
||||||
for key in range(1, KEYS_PER_MODEL[mock_lamarzocco.model_name] + 1):
|
for key in PhysicalKey:
|
||||||
state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}")
|
state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}")
|
||||||
assert state
|
assert state
|
||||||
assert state == snapshot(name=f"{serial_number}_{entity_name}_key_{key}-state")
|
assert state == snapshot(name=f"{serial_number}_{entity_name}_key_{key}-state")
|
||||||
@ -248,17 +330,18 @@ async def test_pre_brew_infusion_key_numbers(
|
|||||||
|
|
||||||
kwargs["key"] = key
|
kwargs["key"] = key
|
||||||
|
|
||||||
assert len(func.mock_calls) == key
|
assert len(func.mock_calls) == key.value
|
||||||
func.assert_called_with(**kwargs)
|
func.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.GS3_AV])
|
@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV])
|
||||||
async def test_disabled_entites(
|
async def test_disabled_entites(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco prebrew/-infusion sensors for GS3AV model."""
|
"""Test the La Marzocco prebrew/-infusion sensors for GS3AV model."""
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
ENTITIES = (
|
ENTITIES = (
|
||||||
"prebrew_off_time",
|
"prebrew_off_time",
|
||||||
"prebrew_on_time",
|
"prebrew_on_time",
|
||||||
@ -269,21 +352,22 @@ async def test_disabled_entites(
|
|||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
for entity_name in ENTITIES:
|
for entity_name in ENTITIES:
|
||||||
for key in range(1, KEYS_PER_MODEL[mock_lamarzocco.model_name] + 1):
|
for key in PhysicalKey:
|
||||||
state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}")
|
state = hass.states.get(f"number.{serial_number}_{entity_name}_key_{key}")
|
||||||
assert state is None
|
assert state is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[LaMarzoccoModel.GS3_MP, LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI],
|
[MachineModel.GS3_MP, MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI],
|
||||||
)
|
)
|
||||||
async def test_not_existing_key_entites(
|
async def test_not_existing_key_entities(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Assert not existing key entities."""
|
"""Assert not existing key entities."""
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
for entity in (
|
for entity in (
|
||||||
@ -292,42 +376,6 @@ async def test_not_existing_key_entites(
|
|||||||
"preinfusion_time",
|
"preinfusion_time",
|
||||||
"set_dose",
|
"set_dose",
|
||||||
):
|
):
|
||||||
for key in range(1, KEYS_PER_MODEL[LaMarzoccoModel.GS3_AV] + 1):
|
for key in range(1, KEYS_PER_MODEL[MachineModel.GS3_AV] + 1):
|
||||||
state = hass.states.get(f"number.{serial_number}_{entity}_key_{key}")
|
state = hass.states.get(f"number.{serial_number}_{entity}_key_{key}")
|
||||||
assert state is None
|
assert state is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"device_fixture",
|
|
||||||
[LaMarzoccoModel.GS3_MP],
|
|
||||||
)
|
|
||||||
async def test_not_existing_entites(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_lamarzocco: MagicMock,
|
|
||||||
) -> None:
|
|
||||||
"""Assert not existing entities."""
|
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
|
||||||
|
|
||||||
for entity in (
|
|
||||||
"prebrew_off_time",
|
|
||||||
"prebrew_on_time",
|
|
||||||
"preinfusion_time",
|
|
||||||
"set_dose",
|
|
||||||
):
|
|
||||||
state = hass.states.get(f"number.{serial_number}_{entity}")
|
|
||||||
assert state is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.LINEA_MICRA])
|
|
||||||
async def test_not_settable_entites(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_lamarzocco: MagicMock,
|
|
||||||
) -> None:
|
|
||||||
"""Assert not settable causes error."""
|
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
|
||||||
|
|
||||||
state = hass.states.get(f"number.{serial_number}_preinfusion_time")
|
|
||||||
assert state
|
|
||||||
assert state.state == STATE_UNAVAILABLE
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from lmcloud.const import LaMarzoccoModel
|
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("device_fixture", [LaMarzoccoModel.LINEA_MICRA])
|
@pytest.mark.parametrize("device_fixture", [MachineModel.LINEA_MICRA])
|
||||||
async def test_steam_boiler_level(
|
async def test_steam_boiler_level(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
@ -44,18 +44,17 @@ async def test_steam_boiler_level(
|
|||||||
SERVICE_SELECT_OPTION,
|
SERVICE_SELECT_OPTION,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"select.{serial_number}_steam_level",
|
ATTR_ENTITY_ID: f"select.{serial_number}_steam_level",
|
||||||
ATTR_OPTION: "1",
|
ATTR_OPTION: "2",
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_lamarzocco.set_steam_level.mock_calls) == 1
|
mock_lamarzocco.set_steam_level.assert_called_once_with(level=SteamLevel.LEVEL_2)
|
||||||
mock_lamarzocco.set_steam_level.assert_called_once_with(1, None)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP, LaMarzoccoModel.LINEA_MINI],
|
[MachineModel.GS3_AV, MachineModel.GS3_MP, MachineModel.LINEA_MINI],
|
||||||
)
|
)
|
||||||
async def test_steam_boiler_level_none(
|
async def test_steam_boiler_level_none(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -70,7 +69,7 @@ async def test_steam_boiler_level_none(
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.GS3_AV, LaMarzoccoModel.LINEA_MINI],
|
[MachineModel.LINEA_MICRA, MachineModel.GS3_AV, MachineModel.LINEA_MINI],
|
||||||
)
|
)
|
||||||
async def test_pre_brew_infusion_select(
|
async def test_pre_brew_infusion_select(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -97,20 +96,17 @@ async def test_pre_brew_infusion_select(
|
|||||||
SERVICE_SELECT_OPTION,
|
SERVICE_SELECT_OPTION,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: f"select.{serial_number}_prebrew_infusion_mode",
|
ATTR_ENTITY_ID: f"select.{serial_number}_prebrew_infusion_mode",
|
||||||
ATTR_OPTION: "preinfusion",
|
ATTR_OPTION: "prebrew",
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(mock_lamarzocco.select_pre_brew_infusion_mode.mock_calls) == 1
|
mock_lamarzocco.set_prebrew_mode.assert_called_once_with(mode=PrebrewMode.PREBREW)
|
||||||
mock_lamarzocco.select_pre_brew_infusion_mode.assert_called_once_with(
|
|
||||||
mode="Preinfusion"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_fixture",
|
"device_fixture",
|
||||||
[LaMarzoccoModel.GS3_MP],
|
[MachineModel.GS3_MP],
|
||||||
)
|
)
|
||||||
async def test_pre_brew_infusion_select_none(
|
async def test_pre_brew_infusion_select_none(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -5,7 +5,6 @@ from unittest.mock import MagicMock
|
|||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
|
||||||
from homeassistant.components.switch import (
|
from homeassistant.components.switch import (
|
||||||
DOMAIN as SWITCH_DOMAIN,
|
DOMAIN as SWITCH_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
@ -15,35 +14,39 @@ from homeassistant.const import ATTR_ENTITY_ID
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from . import async_init_integration
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_name", "method_name", "args_on", "args_off"),
|
(
|
||||||
|
"entity_name",
|
||||||
|
"method_name",
|
||||||
|
),
|
||||||
[
|
[
|
||||||
("", "set_power", (True, None), (False, None)),
|
|
||||||
(
|
(
|
||||||
"_auto_on_off",
|
"",
|
||||||
"set_auto_on_off_global",
|
"set_power",
|
||||||
(True,),
|
),
|
||||||
(False,),
|
(
|
||||||
|
"_steam_boiler",
|
||||||
|
"set_steam",
|
||||||
),
|
),
|
||||||
("_steam_boiler", "set_steam", (True, None), (False, None)),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_switches(
|
async def test_switches(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_name: str,
|
entity_name: str,
|
||||||
method_name: str,
|
method_name: str,
|
||||||
args_on: tuple,
|
|
||||||
args_off: tuple,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco switches."""
|
"""Test the La Marzocco switches."""
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
serial_number = mock_lamarzocco.serial_number
|
||||||
|
|
||||||
control_fn = getattr(mock_lamarzocco, method_name)
|
control_fn = getattr(mock_lamarzocco, method_name)
|
||||||
@ -66,7 +69,7 @@ async def test_switches(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(control_fn.mock_calls) == 1
|
assert len(control_fn.mock_calls) == 1
|
||||||
control_fn.assert_called_once_with(*args_off)
|
control_fn.assert_called_once_with(False)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
SWITCH_DOMAIN,
|
SWITCH_DOMAIN,
|
||||||
@ -78,18 +81,21 @@ async def test_switches(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(control_fn.mock_calls) == 2
|
assert len(control_fn.mock_calls) == 2
|
||||||
control_fn.assert_called_with(*args_on)
|
control_fn.assert_called_with(True)
|
||||||
|
|
||||||
|
|
||||||
async def test_device(
|
async def test_device(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the device for one switch."""
|
"""Test the device for one switch."""
|
||||||
|
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
state = hass.states.get(f"switch.{mock_lamarzocco.serial_number}")
|
state = hass.states.get(f"switch.{mock_lamarzocco.serial_number}")
|
||||||
assert state
|
assert state
|
||||||
|
|
||||||
@ -100,26 +106,3 @@ async def test_device(
|
|||||||
device = device_registry.async_get(entry.device_id)
|
device = device_registry.async_get(entry.device_id)
|
||||||
assert device
|
assert device
|
||||||
assert device == snapshot
|
assert device == snapshot
|
||||||
|
|
||||||
|
|
||||||
async def test_call_without_bluetooth_works(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_lamarzocco: MagicMock,
|
|
||||||
mock_config_entry: MockConfigEntry,
|
|
||||||
) -> None:
|
|
||||||
"""Test that if not using bluetooth, the switch still works."""
|
|
||||||
serial_number = mock_lamarzocco.serial_number
|
|
||||||
coordinator = hass.data[DOMAIN][mock_config_entry.entry_id]
|
|
||||||
coordinator._use_bluetooth = False
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
SWITCH_DOMAIN,
|
|
||||||
SERVICE_TURN_OFF,
|
|
||||||
{
|
|
||||||
ATTR_ENTITY_ID: f"switch.{serial_number}_steam_boiler",
|
|
||||||
},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(mock_lamarzocco.set_steam.mock_calls) == 1
|
|
||||||
mock_lamarzocco.set_steam.assert_called_once_with(False, None)
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from lmcloud.const import LaMarzoccoUpdateableComponent
|
from lmcloud.const import FirmwareType
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
@ -18,8 +18,8 @@ pytestmark = pytest.mark.usefixtures("init_integration")
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_name", "component"),
|
("entity_name", "component"),
|
||||||
[
|
[
|
||||||
("machine_firmware", LaMarzoccoUpdateableComponent.MACHINE),
|
("machine_firmware", FirmwareType.MACHINE),
|
||||||
("gateway_firmware", LaMarzoccoUpdateableComponent.GATEWAY),
|
("gateway_firmware", FirmwareType.GATEWAY),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_update_entites(
|
async def test_update_entites(
|
||||||
@ -28,7 +28,7 @@ async def test_update_entites(
|
|||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
entity_name: str,
|
entity_name: str,
|
||||||
component: LaMarzoccoUpdateableComponent,
|
component: FirmwareType,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the La Marzocco update entities."""
|
"""Test the La Marzocco update entities."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user