mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +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."""
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
import logging
|
||||
|
||||
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
|
||||
|
||||
PLATFORMS = [
|
||||
@ -18,15 +39,89 @@ PLATFORMS = [
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""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()
|
||||
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)
|
||||
|
||||
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:
|
||||
"""Unload a config entry."""
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
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 dataclasses import dataclass
|
||||
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.models import LaMarzoccoMachineConfig
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
@ -26,7 +26,7 @@ class LaMarzoccoBinarySensorEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco binary sensor."""
|
||||
|
||||
is_on_fn: Callable[[LaMarzoccoClient], bool]
|
||||
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
@ -34,7 +34,7 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
key="water_tank",
|
||||
translation_key="water_tank",
|
||||
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,
|
||||
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
||||
),
|
||||
@ -42,8 +42,8 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
|
||||
key="brew_active",
|
||||
translation_key="brew_active",
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
is_on_fn=lambda lm: bool(lm.current_status.get("brew_active")),
|
||||
available_fn=lambda lm: lm.websocket_connected,
|
||||
is_on_fn=lambda config: config.brew_active,
|
||||
available_fn=lambda device: device.websocket_connected,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
@ -72,4 +72,4 @@ class LaMarzoccoBinarySensorEntity(LaMarzoccoEntity, BinarySensorEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""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 typing import Any
|
||||
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -22,14 +22,14 @@ class LaMarzoccoButtonEntityDescription(
|
||||
):
|
||||
"""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, ...] = (
|
||||
LaMarzoccoButtonEntityDescription(
|
||||
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:
|
||||
"""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 datetime import datetime, timedelta
|
||||
|
||||
from lmcloud.models import LaMarzoccoWakeUpSleepEntry
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
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 .const import DOMAIN
|
||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
||||
from .entity import LaMarzoccoBaseEntity
|
||||
|
||||
CALENDAR_KEY = "auto_on_off_schedule"
|
||||
|
||||
DAY_OF_WEEK = [
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday",
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -23,7 +36,10 @@ async def async_setup_entry(
|
||||
"""Set up switch entities and services."""
|
||||
|
||||
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):
|
||||
@ -31,6 +47,17 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
||||
|
||||
_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
|
||||
def event(self) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
@ -85,29 +112,36 @@ class LaMarzoccoCalendarEntity(LaMarzoccoBaseEntity, CalendarEntity):
|
||||
"""Return calendar event for a given weekday."""
|
||||
|
||||
# check first if auto/on off is turned on in general
|
||||
# because could still be on for that day but disabled
|
||||
if self.coordinator.lm.current_status["global_auto"] != "Enabled":
|
||||
if not self.wake_up_sleep_entry.enabled:
|
||||
return None
|
||||
|
||||
# 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
|
||||
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(
|
||||
start=date.replace(
|
||||
hour=int(hour_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",
|
||||
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
|
||||
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.models import LaMarzoccoDeviceInfo
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfo
|
||||
@ -19,12 +21,15 @@ from homeassistant.config_entries import (
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_TOKEN,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
@ -32,7 +37,9 @@ from homeassistant.helpers.selector import (
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN
|
||||
from .const import CONF_USE_BLUETOOTH, DOMAIN
|
||||
|
||||
CONF_MACHINE = "machine"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -40,12 +47,14 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for La Marzocco."""
|
||||
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
|
||||
self.reauth_entry: ConfigEntry | None = None
|
||||
self._config: dict[str, Any] = {}
|
||||
self._machines: list[tuple[str, str]] = []
|
||||
self._fleet: dict[str, LaMarzoccoDeviceInfo] = {}
|
||||
self._discovered: dict[str, str] = {}
|
||||
|
||||
async def async_step_user(
|
||||
@ -65,9 +74,12 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
**self._discovered,
|
||||
}
|
||||
|
||||
lm = LaMarzoccoClient()
|
||||
cloud_client = LaMarzoccoCloudClient(
|
||||
username=data[CONF_USERNAME],
|
||||
password=data[CONF_PASSWORD],
|
||||
)
|
||||
try:
|
||||
self._machines = await lm.get_all_machines(data)
|
||||
self._fleet = await cloud_client.get_customer_fleet()
|
||||
except AuthFail:
|
||||
_LOGGER.debug("Server rejected login credentials")
|
||||
errors["base"] = "invalid_auth"
|
||||
@ -75,7 +87,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.error("Error connecting to server: %s", exc)
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
if not self._machines:
|
||||
if not self._fleet:
|
||||
errors["base"] = "no_machines"
|
||||
|
||||
if not errors:
|
||||
@ -88,8 +100,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
if self._discovered:
|
||||
serials = [machine[0] for machine in self._machines]
|
||||
if self._discovered[CONF_MACHINE] not in serials:
|
||||
if self._discovered[CONF_MACHINE] not in self._fleet:
|
||||
errors["base"] = "machine_not_found"
|
||||
else:
|
||||
self._config = data
|
||||
@ -128,28 +139,36 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
serial_number = self._discovered[CONF_MACHINE]
|
||||
|
||||
selected_device = self._fleet[serial_number]
|
||||
|
||||
# validate local connection if host is provided
|
||||
if user_input.get(CONF_HOST):
|
||||
lm = LaMarzoccoClient()
|
||||
if not await lm.check_local_connection(
|
||||
credentials=self._config,
|
||||
if not await LaMarzoccoLocalClient.validate_connection(
|
||||
client=get_async_client(self.hass),
|
||||
host=user_input[CONF_HOST],
|
||||
serial=serial_number,
|
||||
token=selected_device.communication_key,
|
||||
):
|
||||
errors[CONF_HOST] = "cannot_connect"
|
||||
else:
|
||||
self._config[CONF_HOST] = user_input[CONF_HOST]
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(
|
||||
title=serial_number,
|
||||
data=self._config | user_input,
|
||||
title=selected_device.name,
|
||||
data={
|
||||
**self._config,
|
||||
CONF_NAME: selected_device.name,
|
||||
CONF_MODEL: selected_device.model,
|
||||
CONF_TOKEN: selected_device.communication_key,
|
||||
},
|
||||
)
|
||||
|
||||
machine_options = [
|
||||
SelectOptionDict(
|
||||
value=serial_number,
|
||||
label=f"{model_name} ({serial_number})",
|
||||
value=device.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(
|
||||
|
@ -4,6 +4,4 @@ from typing import Final
|
||||
|
||||
DOMAIN: Final = "lamarzocco"
|
||||
|
||||
CONF_MACHINE: Final = "machine"
|
||||
|
||||
CONF_USE_BLUETOOTH = "use_bluetooth"
|
||||
CONF_USE_BLUETOOTH: Final = "use_bluetooth"
|
||||
|
@ -3,133 +3,108 @@
|
||||
from collections.abc import Callable, Coroutine
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from time import time
|
||||
from typing import Any
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.const import BT_MODEL_NAMES
|
||||
from lmcloud.client_bluetooth import LaMarzoccoBluetoothClient
|
||||
from lmcloud.client_cloud import LaMarzoccoCloudClient
|
||||
from lmcloud.client_local import LaMarzoccoLocalClient
|
||||
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.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.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
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)
|
||||
FIRMWARE_UPDATE_INTERVAL = 3600
|
||||
STATISTICS_UPDATE_INTERVAL = 300
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NAME_PREFIXES = tuple(BT_MODEL_NAMES)
|
||||
|
||||
|
||||
class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Class to handle fetching data from the La Marzocco API centrally."""
|
||||
|
||||
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."""
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
||||
self.lm = LaMarzoccoClient(
|
||||
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
|
||||
self.local_connection_configured = local_client is not None
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data from API endpoint."""
|
||||
|
||||
if not self.lm.initialized:
|
||||
await self._async_init_client()
|
||||
|
||||
await self._async_handle_request(
|
||||
self.lm.update_local_machine_status, force_update=True
|
||||
assert self.config_entry.unique_id
|
||||
self.device = LaMarzoccoMachine(
|
||||
model=self.config_entry.data[CONF_MODEL],
|
||||
serial_number=self.config_entry.unique_id,
|
||||
name=self.config_entry.data[CONF_NAME],
|
||||
cloud_client=cloud_client,
|
||||
local_client=local_client,
|
||||
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:
|
||||
"""Initialize the La Marzocco Client."""
|
||||
|
||||
# Initialize cloud API
|
||||
_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")
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
if self._local_client is not None:
|
||||
_LOGGER.debug("Init WebSocket in background task")
|
||||
|
||||
self.config_entry.async_create_background_task(
|
||||
hass=self.hass,
|
||||
target=self.lm.lm_local_api.websocket_connect(
|
||||
callback=self.lm.on_websocket_message_received,
|
||||
use_sigterm_handler=False,
|
||||
target=self.device.websocket_connect(
|
||||
notify_callback=lambda: self.async_set_updated_data(None)
|
||||
),
|
||||
name="lm_websocket_task",
|
||||
)
|
||||
|
||||
# initialize Bluetooth
|
||||
if self.config_entry.options.get(CONF_USE_BLUETOOTH, True):
|
||||
async def websocket_close(_: Any | None = None) -> None:
|
||||
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:
|
||||
return self.config_entry.data.get(
|
||||
CONF_MAC, ""
|
||||
) 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.config_entry.async_on_unload(
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, websocket_close
|
||||
)
|
||||
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](
|
||||
self,
|
||||
@ -137,9 +112,8 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
*args: _P.args,
|
||||
**kwargs: _P.kwargs,
|
||||
) -> None:
|
||||
"""Handle a request to the API."""
|
||||
try:
|
||||
await func(*args, **kwargs)
|
||||
await func()
|
||||
except AuthFail as ex:
|
||||
msg = "Authentication failed."
|
||||
_LOGGER.debug(msg, exc_info=True)
|
||||
@ -147,15 +121,3 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
except RequestNotSuccessful as ex:
|
||||
_LOGGER.debug(ex, exc_info=True)
|
||||
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 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.config_entries import ConfigEntry
|
||||
@ -13,31 +16,30 @@ from .coordinator import LaMarzoccoUpdateCoordinator
|
||||
|
||||
TO_REDACT = {
|
||||
"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(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""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
|
||||
data = {}
|
||||
data["current_status"] = coordinator.lm.current_status
|
||||
data["machine_info"] = coordinator.lm.machine_info
|
||||
data["config"] = coordinator.lm.config
|
||||
data["statistics"] = {"stats": coordinator.lm.statistics} # wrap to satisfy mypy
|
||||
diagnostics_data = DiagnosticsData(
|
||||
model=device.model,
|
||||
config=asdict(device.config),
|
||||
firmware=[{key: asdict(firmware)} for key, firmware in device.firmware.items()],
|
||||
statistics=asdict(device.statistics),
|
||||
)
|
||||
|
||||
# build a firmware section
|
||||
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)
|
||||
return async_redact_data(diagnostics_data, TO_REDACT)
|
||||
|
@ -3,7 +3,8 @@
|
||||
from collections.abc import Callable
|
||||
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.entity import EntityDescription
|
||||
@ -17,11 +18,13 @@ from .coordinator import LaMarzoccoUpdateCoordinator
|
||||
class LaMarzoccoEntityDescription(EntityDescription):
|
||||
"""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
|
||||
|
||||
|
||||
class LaMarzoccoBaseEntity(CoordinatorEntity[LaMarzoccoUpdateCoordinator]):
|
||||
class LaMarzoccoBaseEntity(
|
||||
CoordinatorEntity[LaMarzoccoUpdateCoordinator],
|
||||
):
|
||||
"""Common elements for all entities."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
@ -33,15 +36,15 @@ class LaMarzoccoBaseEntity(CoordinatorEntity[LaMarzoccoUpdateCoordinator]):
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
lm = coordinator.lm
|
||||
self._attr_unique_id = f"{lm.serial_number}_{key}"
|
||||
device = coordinator.device
|
||||
self._attr_unique_id = f"{device.serial_number}_{key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, lm.serial_number)},
|
||||
name=lm.machine_name,
|
||||
identifiers={(DOMAIN, device.serial_number)},
|
||||
name=device.name,
|
||||
manufacturer="La Marzocco",
|
||||
model=lm.true_model_name,
|
||||
serial_number=lm.serial_number,
|
||||
sw_version=lm.firmware_version,
|
||||
model=device.full_model_name,
|
||||
serial_number=device.serial_number,
|
||||
sw_version=device.firmware[FirmwareType.MACHINE].current_version,
|
||||
)
|
||||
|
||||
|
||||
@ -50,19 +53,18 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
|
||||
|
||||
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__(
|
||||
self,
|
||||
coordinator: LaMarzoccoUpdateCoordinator,
|
||||
entity_description: LaMarzoccoEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
|
||||
super().__init__(coordinator, entity_description.key)
|
||||
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"
|
||||
},
|
||||
"dose": {
|
||||
"default": "mdi:weight-kilogram"
|
||||
},
|
||||
"steam_temp": {
|
||||
"default": "mdi:thermometer-water"
|
||||
"default": "mdi:cup-water"
|
||||
},
|
||||
"prebrew_off": {
|
||||
"default": "mdi:water-off"
|
||||
@ -40,6 +37,9 @@
|
||||
"preinfusion_off": {
|
||||
"default": "mdi:water"
|
||||
},
|
||||
"steam_temp": {
|
||||
"default": "mdi:thermometer-water"
|
||||
},
|
||||
"tea_water_duration": {
|
||||
"default": "mdi:timer-sand"
|
||||
}
|
||||
@ -58,7 +58,7 @@
|
||||
"state": {
|
||||
"disabled": "mdi:water-pump-off",
|
||||
"prebrew": "mdi:water-pump",
|
||||
"preinfusion": "mdi:water-pump"
|
||||
"typeb": "mdi:water-pump"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -22,5 +22,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"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 typing import Any
|
||||
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel
|
||||
from lmcloud.const import (
|
||||
KEYS_PER_MODEL,
|
||||
BoilerType,
|
||||
MachineModel,
|
||||
PhysicalKey,
|
||||
PrebrewMode,
|
||||
)
|
||||
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||
from lmcloud.models import LaMarzoccoMachineConfig
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
@ -35,10 +42,8 @@ class LaMarzoccoNumberEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco number entity."""
|
||||
|
||||
native_value_fn: Callable[[LaMarzoccoClient], float | int]
|
||||
set_value_fn: Callable[
|
||||
[LaMarzoccoUpdateCoordinator, float | int], Coroutine[Any, Any, bool]
|
||||
]
|
||||
native_value_fn: Callable[[LaMarzoccoMachineConfig], float | int]
|
||||
set_value_fn: Callable[[LaMarzoccoMachine, float | int], Coroutine[Any, Any, bool]]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@ -48,9 +53,9 @@ class LaMarzoccoKeyNumberEntityDescription(
|
||||
):
|
||||
"""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[
|
||||
[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_min_value=85,
|
||||
native_max_value=104,
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp(
|
||||
temp, coordinator.async_get_ble_device()
|
||||
),
|
||||
native_value_fn=lambda lm: lm.current_status["coffee_set_temp"],
|
||||
set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.COFFEE, temp),
|
||||
native_value_fn=lambda config: config.boilers[
|
||||
BoilerType.COFFEE
|
||||
].target_temperature,
|
||||
),
|
||||
LaMarzoccoNumberEntityDescription(
|
||||
key="steam_temp",
|
||||
@ -76,14 +81,14 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
||||
native_step=PRECISION_WHOLE,
|
||||
native_min_value=126,
|
||||
native_max_value=131,
|
||||
set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp(
|
||||
int(temp), coordinator.async_get_ble_device()
|
||||
),
|
||||
native_value_fn=lambda lm: lm.current_status["steam_set_temp"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
set_value_fn=lambda machine, temp: machine.set_temp(BoilerType.STEAM, temp),
|
||||
native_value_fn=lambda config: config.boilers[
|
||||
BoilerType.STEAM
|
||||
].target_temperature,
|
||||
supported_fn=lambda coordinator: coordinator.device.model
|
||||
in (
|
||||
LaMarzoccoModel.GS3_AV,
|
||||
LaMarzoccoModel.GS3_MP,
|
||||
MachineModel.GS3_AV,
|
||||
MachineModel.GS3_MP,
|
||||
),
|
||||
),
|
||||
LaMarzoccoNumberEntityDescription(
|
||||
@ -94,54 +99,17 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
|
||||
native_step=PRECISION_WHOLE,
|
||||
native_min_value=0,
|
||||
native_max_value=30,
|
||||
set_value_fn=lambda coordinator, value: coordinator.lm.set_dose_hot_water(
|
||||
value=int(value)
|
||||
),
|
||||
native_value_fn=lambda lm: lm.current_status["dose_hot_water"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
set_value_fn=lambda machine, value: machine.set_dose_tea_water(int(value)),
|
||||
native_value_fn=lambda config: config.dose_hot_water,
|
||||
supported_fn=lambda coordinator: coordinator.device.model
|
||||
in (
|
||||
LaMarzoccoModel.GS3_AV,
|
||||
LaMarzoccoModel.GS3_MP,
|
||||
MachineModel.GS3_AV,
|
||||
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, ...] = (
|
||||
LaMarzoccoKeyNumberEntityDescription(
|
||||
key="prebrew_off",
|
||||
@ -152,11 +120,14 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
native_min_value=1,
|
||||
native_max_value=10,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=_set_prebrew_off,
|
||||
native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_ton_k{key}"],
|
||||
available_fn=lambda lm: lm.current_status["enable_prebrewing"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
!= LaMarzoccoModel.GS3_MP,
|
||||
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||
prebrew_off_time=value, key=key
|
||||
),
|
||||
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
||||
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(
|
||||
key="prebrew_on",
|
||||
@ -167,11 +138,14 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
native_min_value=2,
|
||||
native_max_value=10,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=_set_prebrew_on,
|
||||
native_value_fn=lambda lm, key: lm.current_status[f"prebrewing_toff_k{key}"],
|
||||
available_fn=lambda lm: lm.current_status["enable_prebrewing"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
!= LaMarzoccoModel.GS3_MP,
|
||||
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||
prebrew_on_time=value, key=key
|
||||
),
|
||||
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
||||
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(
|
||||
key="preinfusion_off",
|
||||
@ -182,11 +156,16 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
native_min_value=2,
|
||||
native_max_value=29,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=_set_preinfusion,
|
||||
native_value_fn=lambda lm, key: lm.current_status[f"preinfusion_k{key}"],
|
||||
available_fn=lambda lm: lm.current_status["enable_preinfusion"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
!= LaMarzoccoModel.GS3_MP,
|
||||
set_value_fn=lambda machine, value, key: machine.set_preinfusion_time(
|
||||
preinfusion_time=value, key=key
|
||||
),
|
||||
native_value_fn=lambda config, key: config.prebrew_configuration[
|
||||
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(
|
||||
key="dose",
|
||||
@ -196,10 +175,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
||||
native_min_value=0,
|
||||
native_max_value=999,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=lambda lm, ticks, key: lm.set_dose(key=key, value=int(ticks)),
|
||||
native_value_fn=lambda lm, key: lm.current_status[f"dose_k{key}"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
== LaMarzoccoModel.GS3_AV,
|
||||
set_value_fn=lambda machine, ticks, key: machine.set_dose(
|
||||
dose=int(ticks), key=key
|
||||
),
|
||||
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:
|
||||
"""Set up number entities."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities: list[NumberEntity] = [
|
||||
LaMarzoccoNumberEntity(coordinator, description)
|
||||
for description in ENTITIES
|
||||
@ -220,12 +200,11 @@ async def async_setup_entry(
|
||||
|
||||
for description in KEY_ENTITIES:
|
||||
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(
|
||||
LaMarzoccoKeyNumberEntity(coordinator, description, key)
|
||||
for key in range(min(num_keys, 1), num_keys + 1)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
@ -237,12 +216,13 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||
@property
|
||||
def native_value(self) -> float:
|
||||
"""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:
|
||||
"""Set the value."""
|
||||
await self.entity_description.set_value_fn(self.coordinator, value)
|
||||
self.async_write_ha_state()
|
||||
if value != self.native_value:
|
||||
await self.entity_description.set_value_fn(self.coordinator.device, value)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||
@ -273,12 +253,13 @@ class LaMarzoccoKeyNumberEntity(LaMarzoccoEntity, NumberEntity):
|
||||
def native_value(self) -> float:
|
||||
"""Return the current value."""
|
||||
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:
|
||||
"""Set the value."""
|
||||
await self.entity_description.set_value_fn(
|
||||
self.coordinator.lm, value, self.pyhsical_key
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
if value != self.native_value:
|
||||
await self.entity_description.set_value_fn(
|
||||
self.coordinator.device, value, PhysicalKey(self.pyhsical_key)
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
@ -4,18 +4,43 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.const import LaMarzoccoModel
|
||||
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
|
||||
from lmcloud.lm_machine import LaMarzoccoMachine
|
||||
from lmcloud.models import LaMarzoccoMachineConfig
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
||||
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)
|
||||
class LaMarzoccoSelectEntityDescription(
|
||||
@ -24,10 +49,8 @@ class LaMarzoccoSelectEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco select entity."""
|
||||
|
||||
current_option_fn: Callable[[LaMarzoccoClient], str]
|
||||
select_option_fn: Callable[
|
||||
[LaMarzoccoUpdateCoordinator, str], Coroutine[Any, Any, bool]
|
||||
]
|
||||
current_option_fn: Callable[[LaMarzoccoMachineConfig], str]
|
||||
select_option_fn: Callable[[LaMarzoccoMachine, str], Coroutine[Any, Any, bool]]
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||
@ -35,25 +58,27 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
|
||||
key="steam_temp_select",
|
||||
translation_key="steam_temp_select",
|
||||
options=["1", "2", "3"],
|
||||
select_option_fn=lambda coordinator, option: coordinator.lm.set_steam_level(
|
||||
int(option), coordinator.async_get_ble_device()
|
||||
select_option_fn=lambda machine, option: machine.set_steam_level(
|
||||
STEAM_LEVEL_HA_TO_LM[option]
|
||||
),
|
||||
current_option_fn=lambda lm: lm.current_status["steam_level_set"],
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
== LaMarzoccoModel.LINEA_MICRA,
|
||||
current_option_fn=lambda config: STEAM_LEVEL_LM_TO_HA[config.steam_level],
|
||||
supported_fn=lambda coordinator: coordinator.device.model
|
||||
== MachineModel.LINEA_MICRA,
|
||||
),
|
||||
LaMarzoccoSelectEntityDescription(
|
||||
key="prebrew_infusion_select",
|
||||
translation_key="prebrew_infusion_select",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
options=["disabled", "prebrew", "preinfusion"],
|
||||
select_option_fn=lambda coordinator,
|
||||
option: coordinator.lm.select_pre_brew_infusion_mode(option.capitalize()),
|
||||
current_option_fn=lambda lm: lm.pre_brew_infusion_mode.lower(),
|
||||
supported_fn=lambda coordinator: coordinator.lm.model_name
|
||||
select_option_fn=lambda machine, option: machine.set_prebrew_mode(
|
||||
PREBREW_MODE_HA_TO_LM[option]
|
||||
),
|
||||
current_option_fn=lambda config: PREBREW_MODE_LM_TO_HA[config.prebrew_mode],
|
||||
supported_fn=lambda coordinator: coordinator.device.model
|
||||
in (
|
||||
LaMarzoccoModel.GS3_AV,
|
||||
LaMarzoccoModel.LINEA_MICRA,
|
||||
LaMarzoccoModel.LINEA_MINI,
|
||||
MachineModel.GS3_AV,
|
||||
MachineModel.LINEA_MICRA,
|
||||
MachineModel.LINEA_MINI,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -82,9 +107,14 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity):
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
"""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:
|
||||
"""Change the selected option."""
|
||||
await self.entity_description.select_option_fn(self.coordinator, option)
|
||||
self.async_write_ha_state()
|
||||
if option != self.current_option:
|
||||
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 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 (
|
||||
SensorDeviceClass,
|
||||
@ -22,12 +23,11 @@ from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class LaMarzoccoSensorEntityDescription(
|
||||
LaMarzoccoEntityDescription,
|
||||
SensorEntityDescription,
|
||||
LaMarzoccoEntityDescription, SensorEntityDescription
|
||||
):
|
||||
"""Description of a La Marzocco sensor."""
|
||||
|
||||
value_fn: Callable[[LaMarzoccoClient], float | int]
|
||||
value_fn: Callable[[LaMarzoccoMachine], float | int]
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
@ -36,7 +36,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
translation_key="drink_stats_coffee",
|
||||
native_unit_of_measurement="drinks",
|
||||
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,
|
||||
),
|
||||
LaMarzoccoSensorEntityDescription(
|
||||
@ -44,7 +45,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
translation_key="drink_stats_flushing",
|
||||
native_unit_of_measurement="drinks",
|
||||
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,
|
||||
),
|
||||
LaMarzoccoSensorEntityDescription(
|
||||
@ -53,8 +55,8 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
value_fn=lambda lm: lm.current_status.get("brew_active_duration", 0),
|
||||
available_fn=lambda lm: lm.websocket_connected,
|
||||
value_fn=lambda device: device.config.brew_active_duration,
|
||||
available_fn=lambda device: device.websocket_connected,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
supported_fn=lambda coordinator: coordinator.local_connection_configured,
|
||||
),
|
||||
@ -65,7 +67,9 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
suggested_display_precision=1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
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(
|
||||
key="current_temp_steam",
|
||||
@ -74,7 +78,9 @@ ENTITIES: tuple[LaMarzoccoSensorEntityDescription, ...] = (
|
||||
suggested_display_precision=1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
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
|
||||
def native_value(self) -> int | float:
|
||||
"""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": {
|
||||
"auto_on_off_schedule": {
|
||||
"name": "Auto on/off schedule"
|
||||
"name": "Auto on/off schedule ({id})"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
@ -139,9 +139,6 @@
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"auto_on_off": {
|
||||
"name": "Auto on/off"
|
||||
},
|
||||
"steam_boiler": {
|
||||
"name": "Steam boiler"
|
||||
}
|
||||
@ -154,5 +151,11 @@
|
||||
"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 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.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LaMarzoccoUpdateCoordinator
|
||||
from .entity import LaMarzoccoEntity, LaMarzoccoEntityDescription
|
||||
|
||||
|
||||
@ -22,8 +24,8 @@ class LaMarzoccoSwitchEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco Switch."""
|
||||
|
||||
control_fn: Callable[[LaMarzoccoUpdateCoordinator, bool], Coroutine[Any, Any, bool]]
|
||||
is_on_fn: Callable[[LaMarzoccoUpdateCoordinator], bool]
|
||||
control_fn: Callable[[LaMarzoccoMachine, bool], Coroutine[Any, Any, bool]]
|
||||
is_on_fn: Callable[[LaMarzoccoMachineConfig], bool]
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
||||
@ -31,30 +33,14 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = (
|
||||
key="main",
|
||||
translation_key="main",
|
||||
name=None,
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_power(
|
||||
state, coordinator.async_get_ble_device()
|
||||
),
|
||||
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,
|
||||
control_fn=lambda machine, state: machine.set_power(state),
|
||||
is_on_fn=lambda config: config.turned_on,
|
||||
),
|
||||
LaMarzoccoSwitchEntityDescription(
|
||||
key="steam_boiler_enable",
|
||||
translation_key="steam_boiler",
|
||||
control_fn=lambda coordinator, state: coordinator.lm.set_steam(
|
||||
state, coordinator.async_get_ble_device()
|
||||
),
|
||||
is_on_fn=lambda coordinator: coordinator.lm.current_status[
|
||||
"steam_boiler_enable"
|
||||
],
|
||||
control_fn=lambda machine, state: machine.set_steam(state),
|
||||
is_on_fn=lambda config: config.boilers[BoilerType.STEAM].enabled,
|
||||
),
|
||||
)
|
||||
|
||||
@ -81,15 +67,15 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity):
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""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()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""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()
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""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."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from lmcloud import LMCloud as LaMarzoccoClient
|
||||
from lmcloud.const import LaMarzoccoUpdateableComponent
|
||||
from lmcloud.const import FirmwareType
|
||||
|
||||
from homeassistant.components.update import (
|
||||
UpdateDeviceClass,
|
||||
@ -30,9 +28,7 @@ class LaMarzoccoUpdateEntityDescription(
|
||||
):
|
||||
"""Description of a La Marzocco update entities."""
|
||||
|
||||
current_fw_fn: Callable[[LaMarzoccoClient], str]
|
||||
latest_fw_fn: Callable[[LaMarzoccoClient], str]
|
||||
component: LaMarzoccoUpdateableComponent
|
||||
component: FirmwareType
|
||||
|
||||
|
||||
ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
|
||||
@ -40,18 +36,14 @@ ENTITIES: tuple[LaMarzoccoUpdateEntityDescription, ...] = (
|
||||
key="machine_firmware",
|
||||
translation_key="machine_firmware",
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
current_fw_fn=lambda lm: lm.firmware_version,
|
||||
latest_fw_fn=lambda lm: lm.latest_firmware_version,
|
||||
component=LaMarzoccoUpdateableComponent.MACHINE,
|
||||
component=FirmwareType.MACHINE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
LaMarzoccoUpdateEntityDescription(
|
||||
key="gateway_firmware",
|
||||
translation_key="gateway_firmware",
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
current_fw_fn=lambda lm: lm.gateway_version,
|
||||
latest_fw_fn=lambda lm: lm.latest_gateway_version,
|
||||
component=LaMarzoccoUpdateableComponent.GATEWAY,
|
||||
component=FirmwareType.GATEWAY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
@ -81,12 +73,16 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""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
|
||||
def latest_version(self) -> str:
|
||||
"""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(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
@ -94,7 +90,7 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
|
||||
"""Install an update."""
|
||||
self._attr_in_progress = True
|
||||
self.async_write_ha_state()
|
||||
success = await self.coordinator.lm.update_firmware(
|
||||
success = await self.coordinator.device.update_firmware(
|
||||
self.entity_description.component
|
||||
)
|
||||
if not success:
|
||||
|
@ -1260,7 +1260,7 @@ linear-garage-door==0.2.9
|
||||
linode-api==4.1.9b1
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
lmcloud==0.4.35
|
||||
lmcloud==1.1.11
|
||||
|
||||
# homeassistant.components.google_maps
|
||||
locationsharinglib==5.0.1
|
||||
|
@ -1017,7 +1017,7 @@ libsoundtouch==0.8
|
||||
linear-garage-door==0.2.9
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
lmcloud==0.4.35
|
||||
lmcloud==1.1.11
|
||||
|
||||
# homeassistant.components.logi_circle
|
||||
logi-circle==0.2.3
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""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.core import HomeAssistant
|
||||
@ -18,31 +18,34 @@ PASSWORD_SELECTION = {
|
||||
|
||||
USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"}
|
||||
|
||||
MODEL_DICT = {
|
||||
LaMarzoccoModel.GS3_AV: ("GS01234", "GS3 AV"),
|
||||
LaMarzoccoModel.GS3_MP: ("GS01234", "GS3 MP"),
|
||||
LaMarzoccoModel.LINEA_MICRA: ("MR01234", "Linea Micra"),
|
||||
LaMarzoccoModel.LINEA_MINI: ("LM01234", "Linea Mini"),
|
||||
SERIAL_DICT = {
|
||||
MachineModel.GS3_AV: "GS01234",
|
||||
MachineModel.GS3_MP: "GS01234",
|
||||
MachineModel.LINEA_MICRA: "MR01234",
|
||||
MachineModel.LINEA_MINI: "LM01234",
|
||||
}
|
||||
|
||||
WAKE_UP_SLEEP_ENTRY_IDS = ["Os2OswX", "aXFz5bJ"]
|
||||
|
||||
|
||||
async def async_init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""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.async_block_till_done()
|
||||
|
||||
|
||||
def get_bluetooth_service_info(
|
||||
model: LaMarzoccoModel, serial: str
|
||||
model: MachineModel, serial: str
|
||||
) -> 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}"
|
||||
elif model == LaMarzoccoModel.LINEA_MINI:
|
||||
elif model == MachineModel.LINEA_MINI:
|
||||
name = f"MINI_{serial}"
|
||||
elif model == LaMarzoccoModel.LINEA_MICRA:
|
||||
elif model == MachineModel.LINEA_MICRA:
|
||||
name = f"MICRA_{serial}"
|
||||
return BluetoothServiceInfo(
|
||||
name=name,
|
||||
|
@ -1,22 +1,23 @@
|
||||
"""Lamarzocco session fixtures."""
|
||||
|
||||
from collections.abc import Callable
|
||||
import json
|
||||
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
|
||||
from typing_extensions import Generator
|
||||
|
||||
from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
|
||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_NAME, CONF_TOKEN
|
||||
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 (
|
||||
MockConfigEntry,
|
||||
load_json_array_fixture,
|
||||
load_json_object_fixture,
|
||||
)
|
||||
from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -27,12 +28,13 @@ def mock_config_entry(
|
||||
entry = MockConfigEntry(
|
||||
title="My LaMarzocco",
|
||||
domain=DOMAIN,
|
||||
version=2,
|
||||
data=USER_INPUT
|
||||
| {
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_MODEL: mock_lamarzocco.model,
|
||||
CONF_HOST: "host",
|
||||
CONF_NAME: "name",
|
||||
CONF_MAC: "mac",
|
||||
CONF_TOKEN: "token",
|
||||
CONF_NAME: "GS3",
|
||||
},
|
||||
unique_id=mock_lamarzocco.serial_number,
|
||||
)
|
||||
@ -44,77 +46,96 @@ def mock_config_entry(
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_lamarzocco: MagicMock
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the LaMetric integration for testing."""
|
||||
"""Set up the La Marzocco integration for testing."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_fixture() -> LaMarzoccoModel:
|
||||
def device_fixture() -> MachineModel:
|
||||
"""Return the device fixture for a specific device."""
|
||||
return LaMarzoccoModel.GS3_AV
|
||||
return MachineModel.GS3_AV
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_lamarzocco(device_fixture: LaMarzoccoModel) -> Generator[MagicMock]:
|
||||
"""Return a mocked LM client."""
|
||||
model_name = device_fixture
|
||||
def mock_device_info() -> LaMarzoccoDeviceInfo:
|
||||
"""Return a mocked La Marzocco device info."""
|
||||
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 (
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient",
|
||||
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoMachine",
|
||||
autospec=True,
|
||||
) as lamarzocco_mock,
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient",
|
||||
new=lamarzocco_mock,
|
||||
),
|
||||
):
|
||||
lamarzocco = lamarzocco_mock.return_value
|
||||
|
||||
lamarzocco.machine_info = {
|
||||
"machine_name": serial_number,
|
||||
"serial_number": serial_number,
|
||||
}
|
||||
lamarzocco.name = dummy_machine.name
|
||||
lamarzocco.model = dummy_machine.model
|
||||
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.true_model_name = true_model_name
|
||||
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
|
||||
lamarzocco.firmware[FirmwareType.GATEWAY].latest_version = "v3.5-rc3"
|
||||
lamarzocco.firmware[FirmwareType.MACHINE].latest_version = "1.55"
|
||||
|
||||
async def websocket_connect_mock(
|
||||
callback: MagicMock, use_sigterm_handler: MagicMock
|
||||
notify_callback: Callable | None,
|
||||
) -> None:
|
||||
"""Mock the websocket connect method."""
|
||||
return None
|
||||
|
||||
lamarzocco.lm_local_api.websocket_connect = websocket_connect_mock
|
||||
|
||||
lamarzocco.lm_bluetooth = MagicMock()
|
||||
lamarzocco.lm_bluetooth.address = "AA:BB:CC:DD:EE:FF"
|
||||
lamarzocco.websocket_connect = websocket_connect_mock
|
||||
|
||||
yield lamarzocco
|
||||
|
||||
@ -133,3 +154,11 @@ def remove_local_connection(
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_bluetooth(enable_bluetooth: None) -> None:
|
||||
"""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"
|
||||
}
|
||||
],
|
||||
"machine_sn": "GS01234",
|
||||
"machine_sn": "Sn01239157",
|
||||
"machine_hw": "2",
|
||||
"isPlumbedIn": true,
|
||||
"isBackFlushEnabled": false,
|
||||
"standByTime": 0,
|
||||
"smartStandBy": {
|
||||
"enabled": true,
|
||||
"minutes": 10,
|
||||
"mode": "LastBrewing"
|
||||
},
|
||||
"tankStatus": true,
|
||||
"groupCapabilities": [
|
||||
{
|
||||
@ -121,58 +126,32 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"weeklySchedulingConfig": {
|
||||
"enabled": true,
|
||||
"monday": {
|
||||
"wakeUpSleepEntries": [
|
||||
{
|
||||
"days": [
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday"
|
||||
],
|
||||
"enabled": true,
|
||||
"h_on": 6,
|
||||
"h_off": 16,
|
||||
"m_on": 0,
|
||||
"m_off": 0
|
||||
"id": "Os2OswX",
|
||||
"steam": true,
|
||||
"timeOff": "24:0",
|
||||
"timeOn": "22:0"
|
||||
},
|
||||
"tuesday": {
|
||||
{
|
||||
"days": ["sunday"],
|
||||
"enabled": true,
|
||||
"h_on": 6,
|
||||
"h_off": 16,
|
||||
"m_on": 0,
|
||||
"m_off": 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
|
||||
"id": "aXFz5bJ",
|
||||
"steam": true,
|
||||
"timeOff": "7:30",
|
||||
"timeOn": "7:0"
|
||||
}
|
||||
},
|
||||
],
|
||||
"clock": "1901-07-08T10:29:00",
|
||||
"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
|
||||
# name: test_calendar_edge_cases[start_date0-end_date0]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
]),
|
||||
}),
|
||||
@ -65,7 +65,7 @@
|
||||
# ---
|
||||
# name: test_calendar_edge_cases[start_date5-end_date5]
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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
|
||||
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
|
||||
# name: test_calendar_events[entry.GS01234_auto_on_off_schedule_axfz5bj]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -114,7 +95,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'calendar',
|
||||
'entity_category': None,
|
||||
'entity_id': 'calendar.gs01234_auto_on_off_schedule',
|
||||
'entity_id': 'calendar.gs01234_auto_on_off_schedule_axfz5bj',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@ -126,86 +107,267 @@
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Auto on/off schedule',
|
||||
'original_name': 'Auto on/off schedule (aXFz5bJ)',
|
||||
'platform': 'lamarzocco',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'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,
|
||||
})
|
||||
# ---
|
||||
# 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({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_axfz5bj': dict({
|
||||
'events': list([
|
||||
dict({
|
||||
'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',
|
||||
'start': '2024-01-13T10:00:00-08:00',
|
||||
'end': '2024-01-14T07:30:00-08:00',
|
||||
'start': '2024-01-14T07: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-17T13:00:00-08:00',
|
||||
'start': '2024-01-17T08:00:00-08:00',
|
||||
'end': '2024-01-21T07:30:00-08:00',
|
||||
'start': '2024-01-21T07: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-19T09:00:00-08:00',
|
||||
'start': '2024-01-19T06:00:00-08:00',
|
||||
'end': '2024-01-28T07:30:00-08:00',
|
||||
'start': '2024-01-28T07: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-20T23:00:00-08:00',
|
||||
'start': '2024-01-20T10: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',
|
||||
'end': '2024-02-04T07:30:00-08:00',
|
||||
'start': '2024-02-04T07:00:00-08:00',
|
||||
'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
|
||||
dict({
|
||||
'calendar.gs01234_auto_on_off_schedule': dict({
|
||||
'calendar.gs01234_auto_on_off_schedule_os2oswx': dict({
|
||||
'events': list([
|
||||
]),
|
||||
}),
|
||||
|
@ -2,297 +2,107 @@
|
||||
# name: test_diagnostics
|
||||
dict({
|
||||
'config': dict({
|
||||
'boilerTargetTemperature': dict({
|
||||
'CoffeeBoiler1': 95,
|
||||
'SteamBoiler': 123.9000015258789,
|
||||
}),
|
||||
'boilers': list([
|
||||
dict({
|
||||
'current': 123.80000305175781,
|
||||
'id': 'SteamBoiler',
|
||||
'isEnabled': True,
|
||||
'target': 123.9000015258789,
|
||||
'boilers': dict({
|
||||
'CoffeeBoiler1': dict({
|
||||
'current_temperature': 96.5,
|
||||
'enabled': True,
|
||||
'target_temperature': 95,
|
||||
}),
|
||||
dict({
|
||||
'current': 96.5,
|
||||
'id': 'CoffeeBoiler1',
|
||||
'isEnabled': True,
|
||||
'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',
|
||||
'SteamBoiler': dict({
|
||||
'current_temperature': 123.80000305175781,
|
||||
'enabled': True,
|
||||
'target_temperature': 123.9000015258789,
|
||||
}),
|
||||
}),
|
||||
'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,
|
||||
'coffee_boiler_on': True,
|
||||
'coffee_set_temp': 95,
|
||||
'coffee_temp': 93,
|
||||
'dose_hot_water': 1023,
|
||||
'dose_k1': 1023,
|
||||
'dose_k2': 1023,
|
||||
'dose_k3': 1023,
|
||||
'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',
|
||||
'brew_active_duration': 0,
|
||||
'dose_hot_water': 8,
|
||||
'doses': dict({
|
||||
'1': 135,
|
||||
'2': 97,
|
||||
'3': 108,
|
||||
'4': 121,
|
||||
}),
|
||||
'machine': dict({
|
||||
'latest_version': '1.2',
|
||||
'version': '1.1',
|
||||
'plumbed_in': True,
|
||||
'prebrew_configuration': dict({
|
||||
'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({
|
||||
'machine_name': 'GS01234',
|
||||
'serial_number': '**REDACTED**',
|
||||
}),
|
||||
'firmware': list([
|
||||
dict({
|
||||
'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({
|
||||
'stats': list([
|
||||
dict({
|
||||
'coffeeType': 0,
|
||||
'count': 1047,
|
||||
}),
|
||||
dict({
|
||||
'coffeeType': 1,
|
||||
'count': 560,
|
||||
}),
|
||||
dict({
|
||||
'coffeeType': 2,
|
||||
'count': 468,
|
||||
}),
|
||||
dict({
|
||||
'coffeeType': 3,
|
||||
'count': 312,
|
||||
}),
|
||||
dict({
|
||||
'coffeeType': 4,
|
||||
'count': 2252,
|
||||
}),
|
||||
dict({
|
||||
'coffeeType': -1,
|
||||
'count': 1740,
|
||||
}),
|
||||
]),
|
||||
'continous': 2252,
|
||||
'drink_stats': dict({
|
||||
'1': 1047,
|
||||
'2': 560,
|
||||
'3': 468,
|
||||
'4': 312,
|
||||
}),
|
||||
'total_flushes': 1740,
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
@ -56,7 +56,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
@ -72,10 +72,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -113,7 +113,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
@ -129,10 +129,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -170,7 +170,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -186,10 +186,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -227,7 +227,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -243,10 +243,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -284,7 +284,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Dose Key 1',
|
||||
@ -299,10 +299,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Dose Key 2',
|
||||
@ -317,10 +317,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Dose Key 3',
|
||||
@ -335,10 +335,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Dose Key 4',
|
||||
@ -353,10 +353,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -372,10 +372,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -391,10 +391,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -410,10 +410,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -429,10 +429,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -448,10 +448,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -467,10 +467,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -486,10 +486,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -505,10 +505,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -524,10 +524,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -543,10 +543,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -562,10 +562,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -581,10 +581,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -600,10 +600,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -641,7 +641,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -657,10 +657,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -698,7 +698,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -714,10 +714,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -755,7 +755,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -771,10 +771,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -812,7 +812,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -828,10 +828,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -869,7 +869,7 @@
|
||||
'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({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
@ -885,10 +885,10 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <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({
|
||||
'aliases': set({
|
||||
}),
|
||||
|
@ -14,7 +14,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': 'preinfusion',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_select[GS3 AV].1
|
||||
@ -34,7 +34,7 @@
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.gs01234_prebrew_infusion_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
@ -71,7 +71,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': 'preinfusion',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_select[Linea Mini].1
|
||||
@ -91,7 +91,7 @@
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.lm01234_prebrew_infusion_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
@ -128,7 +128,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': 'preinfusion',
|
||||
})
|
||||
# ---
|
||||
# name: test_pre_brew_infusion_select[Micra].1
|
||||
@ -148,7 +148,7 @@
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.mr01234_prebrew_infusion_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
@ -185,7 +185,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '3',
|
||||
'state': '1',
|
||||
})
|
||||
# ---
|
||||
# name: test_steam_boiler_level[Micra].1
|
||||
|
@ -50,7 +50,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '93',
|
||||
'state': '96.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[GS01234_current_steam_temperature-entry]
|
||||
@ -104,7 +104,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '113',
|
||||
'state': '123.800003051758',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[GS01234_shot_timer-entry]
|
||||
@ -205,7 +205,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '13',
|
||||
'state': '1047',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[GS01234_total_flushes_made-entry]
|
||||
@ -255,6 +255,6 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '69',
|
||||
'state': '1740',
|
||||
})
|
||||
# ---
|
||||
|
@ -20,16 +20,16 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'La Marzocco',
|
||||
'model': 'GS3 AV',
|
||||
'model': <MachineModel.GS3_AV: 'GS3 AV'>,
|
||||
'name': 'GS01234',
|
||||
'name_by_user': None,
|
||||
'serial_number': 'GS01234',
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.1',
|
||||
'sw_version': '1.40',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power-args_on0-args_off0]
|
||||
# name: test_switches[-set_power]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234',
|
||||
@ -42,7 +42,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power-args_on0-args_off0].1
|
||||
# name: test_switches[-set_power].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
@ -75,141 +75,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[-set_power-kwargs_on0-kwargs_off0]
|
||||
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]
|
||||
# name: test_switches[_steam_boiler-set_steam]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'GS01234 Steam boiler',
|
||||
@ -222,53 +88,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2].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
|
||||
# name: test_switches[_steam_boiler-set_steam].1
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
|
@ -7,8 +7,8 @@
|
||||
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
||||
'friendly_name': 'GS01234 Gateway firmware',
|
||||
'in_progress': False,
|
||||
'installed_version': 'v2.2-rc0',
|
||||
'latest_version': 'v3.1-rc4',
|
||||
'installed_version': 'v3.1-rc4',
|
||||
'latest_version': 'v3.5-rc3',
|
||||
'release_summary': None,
|
||||
'release_url': None,
|
||||
'skipped_version': None,
|
||||
@ -64,8 +64,8 @@
|
||||
'entity_picture': 'https://brands.home-assistant.io/_/lamarzocco/icon.png',
|
||||
'friendly_name': 'GS01234 Machine firmware',
|
||||
'in_progress': False,
|
||||
'installed_version': '1.1',
|
||||
'latest_version': '1.2',
|
||||
'installed_version': '1.40',
|
||||
'latest_version': '1.55',
|
||||
'release_summary': None,
|
||||
'release_url': None,
|
||||
'skipped_version': None,
|
||||
|
@ -1,7 +1,10 @@
|
||||
"""Tests for La Marzocco binary sensors."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from lmcloud.exceptions import RequestNotSuccessful
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -11,7 +14,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import async_init_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
BINARY_SENSORS = (
|
||||
"brewing_active",
|
||||
@ -70,3 +73,29 @@ async def test_brew_active_unavailable(
|
||||
)
|
||||
assert state
|
||||
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.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
|
||||
|
||||
@ -40,27 +40,37 @@ async def test_calendar_events(
|
||||
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
state = hass.states.get(f"calendar.{serial_number}_auto_on_off_schedule")
|
||||
assert state
|
||||
assert state == snapshot
|
||||
for identifier in WAKE_UP_SLEEP_ENTRY_IDS:
|
||||
identifier = identifier.lower()
|
||||
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)
|
||||
assert entry
|
||||
assert entry == snapshot
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry == snapshot(
|
||||
name=f"entry.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||
)
|
||||
|
||||
events = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule",
|
||||
EVENT_START_DATETIME: test_time,
|
||||
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
events = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"calendar.{serial_number}_auto_on_off_schedule_{identifier}",
|
||||
EVENT_START_DATETIME: test_time,
|
||||
EVENT_END_DATETIME: test_time + timedelta(days=23),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
assert events == snapshot
|
||||
assert events == snapshot(
|
||||
name=f"events.{serial_number}_auto_on_off_schedule_{identifier}"
|
||||
)
|
||||
|
||||
|
||||
@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())
|
||||
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)
|
||||
|
||||
events = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
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_END_DATETIME: end_date,
|
||||
},
|
||||
@ -123,7 +125,9 @@ async def test_no_calendar_events_global_disable(
|
||||
) -> None:
|
||||
"""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())
|
||||
freezer.move_to(test_time)
|
||||
|
||||
@ -131,14 +135,16 @@ async def test_no_calendar_events_global_disable(
|
||||
|
||||
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
|
||||
|
||||
events = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
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_END_DATETIME: test_time + timedelta(days=23),
|
||||
},
|
||||
|
@ -1,17 +1,26 @@
|
||||
"""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.models import LaMarzoccoDeviceInfo
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.lamarzocco.const import (
|
||||
CONF_MACHINE,
|
||||
CONF_USE_BLUETOOTH,
|
||||
DOMAIN,
|
||||
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
|
||||
from homeassistant.components.lamarzocco.const import CONF_USE_BLUETOOTH, DOMAIN
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_BLUETOOTH,
|
||||
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.data_entry_flow import FlowResult, FlowResultType
|
||||
|
||||
@ -21,7 +30,7 @@ from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def __do_successful_user_step(
|
||||
hass: HomeAssistant, result: FlowResult
|
||||
hass: HomeAssistant, result: FlowResult, mock_cloud_client: MagicMock
|
||||
) -> FlowResult:
|
||||
"""Successfully configure the user step."""
|
||||
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(
|
||||
hass: HomeAssistant, result2: FlowResult, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant, result2: FlowResult, mock_device_info: LaMarzoccoDeviceInfo
|
||||
) -> None:
|
||||
"""Successfully configure the machine selection step."""
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||
return_value=True,
|
||||
):
|
||||
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()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
assert result3["title"] == mock_lamarzocco.serial_number
|
||||
assert result3["title"] == "GS3"
|
||||
assert result3["data"] == {
|
||||
**USER_INPUT,
|
||||
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."""
|
||||
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["errors"] == {}
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result2 = await __do_successful_user_step(hass, result)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
||||
|
||||
assert len(mock_lamarzocco.check_local_connection.mock_calls) == 1
|
||||
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||
|
||||
|
||||
async def test_form_abort_already_configured(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_cloud_client: MagicMock,
|
||||
mock_device_info: LaMarzoccoDeviceInfo,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test we abort if already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
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["errors"] == {}
|
||||
@ -98,7 +119,7 @@ async def test_form_abort_already_configured(
|
||||
result2["flow_id"],
|
||||
{
|
||||
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()
|
||||
@ -108,13 +129,15 @@ async def test_form_abort_already_configured(
|
||||
|
||||
|
||||
async def test_form_invalid_auth(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_device_info: LaMarzoccoDeviceInfo,
|
||||
mock_cloud_client: MagicMock,
|
||||
) -> None:
|
||||
"""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(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
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["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
|
||||
mock_lamarzocco.get_all_machines.side_effect = None
|
||||
result2 = await __do_successful_user_step(hass, result)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
||||
mock_cloud_client.get_customer_fleet.side_effect = None
|
||||
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||
|
||||
|
||||
async def test_form_invalid_host(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_cloud_client: MagicMock,
|
||||
mock_device_info: LaMarzoccoDeviceInfo,
|
||||
) -> None:
|
||||
"""Test invalid auth error."""
|
||||
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["errors"] == {}
|
||||
@ -148,38 +173,41 @@ async def test_form_invalid_host(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_lamarzocco.check_local_connection.return_value = False
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "machine_selection"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
},
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||
return_value=False,
|
||||
):
|
||||
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()
|
||||
|
||||
assert result3["type"] is FlowResultType.FORM
|
||||
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
|
||||
mock_lamarzocco.check_local_connection.return_value = True
|
||||
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_cannot_connect(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_cloud_client: MagicMock,
|
||||
mock_device_info: LaMarzoccoDeviceInfo,
|
||||
) -> None:
|
||||
"""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(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
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["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(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
@ -199,21 +227,26 @@ async def test_form_cannot_connect(
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
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
|
||||
mock_lamarzocco.get_all_machines.side_effect = None
|
||||
mock_lamarzocco.get_all_machines.return_value = [
|
||||
(mock_lamarzocco.serial_number, mock_lamarzocco.model_name)
|
||||
]
|
||||
result2 = await __do_successful_user_step(hass, result)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_lamarzocco)
|
||||
mock_cloud_client.get_customer_fleet.side_effect = None
|
||||
mock_cloud_client.get_customer_fleet.return_value = {
|
||||
mock_device_info.serial_number: mock_device_info
|
||||
}
|
||||
result2 = await __do_successful_user_step(hass, result, mock_cloud_client)
|
||||
await __do_sucessful_machine_selection_step(hass, result2, mock_device_info)
|
||||
|
||||
|
||||
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:
|
||||
"""Test that the reauth flow."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={
|
||||
@ -235,19 +268,21 @@ async def test_reauth_flow(
|
||||
assert result2["type"] is FlowResultType.ABORT
|
||||
await hass.async_block_till_done()
|
||||
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"
|
||||
|
||||
|
||||
async def test_bluetooth_discovery(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_cloud_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test bluetooth discovery."""
|
||||
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(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, data=service_info
|
||||
DOMAIN, context={"source": SOURCE_BLUETOOTH}, data=service_info
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@ -260,82 +295,95 @@ async def test_bluetooth_discovery(
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "machine_selection"
|
||||
|
||||
assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
assert len(mock_cloud_client.get_customer_fleet.mock_calls) == 1
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||
return_value=True,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
assert result3["title"] == mock_lamarzocco.serial_number
|
||||
assert result3["title"] == "GS3"
|
||||
assert result3["data"] == {
|
||||
**USER_INPUT,
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_NAME: service_info.name,
|
||||
CONF_NAME: "GS3",
|
||||
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(
|
||||
hass: HomeAssistant, mock_lamarzocco: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_cloud_client: MagicMock,
|
||||
mock_device_info: LaMarzoccoDeviceInfo,
|
||||
) -> None:
|
||||
"""Test bluetooth discovery errors."""
|
||||
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(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_BLUETOOTH},
|
||||
context={"source": SOURCE_BLUETOOTH},
|
||||
data=service_info,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
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(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
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_lamarzocco.serial_number, mock_lamarzocco.model_name)
|
||||
]
|
||||
mock_cloud_client.get_customer_fleet.return_value = {
|
||||
mock_device_info.serial_number: mock_device_info
|
||||
}
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
USER_INPUT,
|
||||
)
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
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(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.config_flow.LaMarzoccoLocalClient.validate_connection",
|
||||
return_value=True,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_HOST: "192.168.1.1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
assert result3["title"] == mock_lamarzocco.serial_number
|
||||
assert result3["title"] == "GS3"
|
||||
assert result3["data"] == {
|
||||
**USER_INPUT,
|
||||
CONF_HOST: "192.168.1.1",
|
||||
CONF_MACHINE: mock_lamarzocco.serial_number,
|
||||
CONF_NAME: service_info.name,
|
||||
CONF_NAME: "GS3",
|
||||
CONF_MAC: "aa:bb:cc:dd:ee:ff",
|
||||
CONF_MODEL: mock_lamarzocco.model,
|
||||
CONF_TOKEN: "token",
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,15 +1,19 @@
|
||||
"""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
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
|
||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||
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.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
|
||||
|
||||
@ -20,7 +24,9 @@ async def test_load_unload_config_entry(
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""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
|
||||
|
||||
@ -36,11 +42,13 @@ async def test_config_entry_not_ready(
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""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
|
||||
|
||||
|
||||
@ -50,11 +58,13 @@ async def test_invalid_auth(
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""Test auth error during setup."""
|
||||
mock_lamarzocco.update_local_machine_status.side_effect = AuthFail("")
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
mock_lamarzocco.get_config.side_effect = AuthFail("")
|
||||
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 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()
|
||||
assert len(flows) == 1
|
||||
@ -68,27 +78,132 @@ async def test_invalid_auth(
|
||||
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(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_lamarzocco: MagicMock,
|
||||
) -> None:
|
||||
"""Assert we're not searching for a new BT device when we already found one previously."""
|
||||
|
||||
# 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)
|
||||
"""Check we can fill a device from discovery info."""
|
||||
|
||||
service_info = get_bluetooth_service_info(
|
||||
mock_lamarzocco.model_name, mock_lamarzocco.serial_number
|
||||
mock_lamarzocco.model, mock_lamarzocco.serial_number
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.lamarzocco.coordinator.async_discovered_service_info",
|
||||
return_value=[service_info],
|
||||
with (
|
||||
patch(
|
||||
"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)
|
||||
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_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 lmcloud.const import KEYS_PER_MODEL, LaMarzoccoModel
|
||||
from lmcloud.const import (
|
||||
KEYS_PER_MODEL,
|
||||
BoilerType,
|
||||
MachineModel,
|
||||
PhysicalKey,
|
||||
PrebrewMode,
|
||||
)
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -15,17 +21,22 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the La Marzocco coffee temperature Number."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
state = hass.states.get(f"number.{serial_number}_coffee_target_temperature")
|
||||
@ -47,35 +58,34 @@ async def test_coffee_boiler(
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"number.{serial_number}_coffee_target_temperature",
|
||||
ATTR_VALUE: 95,
|
||||
ATTR_VALUE: 94,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.set_coffee_temp.mock_calls) == 1
|
||||
mock_lamarzocco.set_coffee_temp.assert_called_once_with(
|
||||
temperature=95, ble_device=None
|
||||
assert len(mock_lamarzocco.set_temp.mock_calls) == 1
|
||||
mock_lamarzocco.set_temp.assert_called_once_with(
|
||||
boiler=BoilerType.COFFEE, temperature=94
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture", [LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP]
|
||||
)
|
||||
@pytest.mark.parametrize("device_fixture", [MachineModel.GS3_AV, MachineModel.GS3_MP])
|
||||
@pytest.mark.parametrize(
|
||||
("entity_name", "value", "func_name", "kwargs"),
|
||||
[
|
||||
(
|
||||
"steam_target_temperature",
|
||||
131,
|
||||
"set_steam_temp",
|
||||
{"temperature": 131, "ble_device": None},
|
||||
"set_temp",
|
||||
{"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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
@ -85,7 +95,7 @@ async def test_gs3_exclusive(
|
||||
kwargs: dict[str, float],
|
||||
) -> None:
|
||||
"""Test exclusive entities for GS3 AV/MP."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
func = getattr(mock_lamarzocco, func_name)
|
||||
@ -118,14 +128,15 @@ async def test_gs3_exclusive(
|
||||
|
||||
|
||||
@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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Ensure GS3 exclusive is None for unsupported models."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
ENTITIES = ("steam_target_temperature", "tea_water_duration")
|
||||
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
@ -135,29 +146,50 @@ async def test_gs3_exclusive_none(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture", [LaMarzoccoModel.LINEA_MICRA, LaMarzoccoModel.LINEA_MINI]
|
||||
"device_fixture", [MachineModel.LINEA_MICRA, MachineModel.LINEA_MINI]
|
||||
)
|
||||
@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}),
|
||||
("preinfusion_time", 7, {"off_time": 7000, "key": 1}),
|
||||
(
|
||||
"prebrew_off_time",
|
||||
"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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_name: str,
|
||||
function_name: str,
|
||||
prebrew_mode: PrebrewMode,
|
||||
value: float,
|
||||
kwargs: dict[str, float],
|
||||
) -> None:
|
||||
"""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
|
||||
|
||||
@ -168,12 +200,8 @@ async def test_pre_brew_infusion_numbers(
|
||||
|
||||
entry = entity_registry.async_get(state.entity_id)
|
||||
assert entry
|
||||
assert entry.device_id
|
||||
assert entry == snapshot
|
||||
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
|
||||
# service call
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
@ -185,43 +213,97 @@ async def test_pre_brew_infusion_numbers(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.configure_prebrew.mock_calls) == 1
|
||||
mock_lamarzocco.configure_prebrew.assert_called_once_with(**kwargs)
|
||||
function = getattr(mock_lamarzocco, function_name)
|
||||
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.parametrize(
|
||||
("entity_name", "value", "function_name", "kwargs"),
|
||||
("entity_name", "value", "prebrew_mode", "function_name", "kwargs"),
|
||||
[
|
||||
(
|
||||
"prebrew_off_time",
|
||||
6,
|
||||
"configure_prebrew",
|
||||
{"on_time": 3000, "off_time": 6000},
|
||||
PrebrewMode.PREBREW,
|
||||
"set_prebrew_time",
|
||||
{"prebrew_off_time": 6.0},
|
||||
),
|
||||
(
|
||||
"prebrew_on_time",
|
||||
6,
|
||||
"configure_prebrew",
|
||||
{"on_time": 6000, "off_time": 5000},
|
||||
PrebrewMode.PREBREW,
|
||||
"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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_name: str,
|
||||
value: float,
|
||||
prebrew_mode: PrebrewMode,
|
||||
function_name: str,
|
||||
kwargs: dict[str, float],
|
||||
) -> None:
|
||||
"""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
|
||||
|
||||
@ -230,7 +312,7 @@ async def test_pre_brew_infusion_key_numbers(
|
||||
state = hass.states.get(f"number.{serial_number}_{entity_name}")
|
||||
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}")
|
||||
assert 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
|
||||
|
||||
assert len(func.mock_calls) == key
|
||||
assert len(func.mock_calls) == key.value
|
||||
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(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the La Marzocco prebrew/-infusion sensors for GS3AV model."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
ENTITIES = (
|
||||
"prebrew_off_time",
|
||||
"prebrew_on_time",
|
||||
@ -269,21 +352,22 @@ async def test_disabled_entites(
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
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}")
|
||||
assert state is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"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,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Assert not existing key entities."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
for entity in (
|
||||
@ -292,42 +376,6 @@ async def test_not_existing_key_entites(
|
||||
"preinfusion_time",
|
||||
"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}")
|
||||
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 lmcloud.const import LaMarzoccoModel
|
||||
from lmcloud.const import MachineModel, PrebrewMode, SteamLevel
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -18,7 +18,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
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(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@ -44,18 +44,17 @@ async def test_steam_boiler_level(
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"select.{serial_number}_steam_level",
|
||||
ATTR_OPTION: "1",
|
||||
ATTR_OPTION: "2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.set_steam_level.mock_calls) == 1
|
||||
mock_lamarzocco.set_steam_level.assert_called_once_with(1, None)
|
||||
mock_lamarzocco.set_steam_level.assert_called_once_with(level=SteamLevel.LEVEL_2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"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(
|
||||
hass: HomeAssistant,
|
||||
@ -70,7 +69,7 @@ async def test_steam_boiler_level_none(
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"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(
|
||||
hass: HomeAssistant,
|
||||
@ -97,20 +96,17 @@ async def test_pre_brew_infusion_select(
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: f"select.{serial_number}_prebrew_infusion_mode",
|
||||
ATTR_OPTION: "preinfusion",
|
||||
ATTR_OPTION: "prebrew",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_lamarzocco.select_pre_brew_infusion_mode.mock_calls) == 1
|
||||
mock_lamarzocco.select_pre_brew_infusion_mode.assert_called_once_with(
|
||||
mode="Preinfusion"
|
||||
)
|
||||
mock_lamarzocco.set_prebrew_mode.assert_called_once_with(mode=PrebrewMode.PREBREW)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_fixture",
|
||||
[LaMarzoccoModel.GS3_MP],
|
||||
[MachineModel.GS3_MP],
|
||||
)
|
||||
async def test_pre_brew_infusion_select_none(
|
||||
hass: HomeAssistant,
|
||||
|
@ -5,7 +5,6 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.lamarzocco.const import DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -15,35 +14,39 @@ from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
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(
|
||||
("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",
|
||||
(True,),
|
||||
(False,),
|
||||
"",
|
||||
"set_power",
|
||||
),
|
||||
(
|
||||
"_steam_boiler",
|
||||
"set_steam",
|
||||
),
|
||||
("_steam_boiler", "set_steam", (True, None), (False, None)),
|
||||
],
|
||||
)
|
||||
async def test_switches(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_name: str,
|
||||
method_name: str,
|
||||
args_on: tuple,
|
||||
args_off: tuple,
|
||||
) -> None:
|
||||
"""Test the La Marzocco switches."""
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
serial_number = mock_lamarzocco.serial_number
|
||||
|
||||
control_fn = getattr(mock_lamarzocco, method_name)
|
||||
@ -66,7 +69,7 @@ async def test_switches(
|
||||
)
|
||||
|
||||
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(
|
||||
SWITCH_DOMAIN,
|
||||
@ -78,18 +81,21 @@ async def test_switches(
|
||||
)
|
||||
|
||||
assert len(control_fn.mock_calls) == 2
|
||||
control_fn.assert_called_with(*args_on)
|
||||
control_fn.assert_called_with(True)
|
||||
|
||||
|
||||
async def test_device(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the device for one switch."""
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(f"switch.{mock_lamarzocco.serial_number}")
|
||||
assert state
|
||||
|
||||
@ -100,26 +106,3 @@ async def test_device(
|
||||
device = device_registry.async_get(entry.device_id)
|
||||
assert device
|
||||
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 lmcloud.const import LaMarzoccoUpdateableComponent
|
||||
from lmcloud.const import FirmwareType
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
@ -18,8 +18,8 @@ pytestmark = pytest.mark.usefixtures("init_integration")
|
||||
@pytest.mark.parametrize(
|
||||
("entity_name", "component"),
|
||||
[
|
||||
("machine_firmware", LaMarzoccoUpdateableComponent.MACHINE),
|
||||
("gateway_firmware", LaMarzoccoUpdateableComponent.GATEWAY),
|
||||
("machine_firmware", FirmwareType.MACHINE),
|
||||
("gateway_firmware", FirmwareType.GATEWAY),
|
||||
],
|
||||
)
|
||||
async def test_update_entites(
|
||||
@ -28,7 +28,7 @@ async def test_update_entites(
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_name: str,
|
||||
component: LaMarzoccoUpdateableComponent,
|
||||
component: FirmwareType,
|
||||
) -> None:
|
||||
"""Test the La Marzocco update entities."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user