Integration for IronOS (Pinecil V2) soldering irons (#120802)

* Add Pinecil integration

* Refactor with new library

* Add tests for config flow, remove unused code

* requested changes

* update requirements

* Move some sensor values to diagnostics, add tests for sensors

* User service uuid in discovery

* fix manufacturer name

* Bump pynecil to version 0.2.0

* Rename integration to IronOS

* Recreate snapshot

* Update strings

* type checking

* Update snapshot

* Add async_setup to coordinator

* Show device id with serial number

* Added missing boost to operation mode states

* remove super call

* Refactor

* tests
pull/122765/head
Mr. Bubbles 2024-07-29 11:44:01 +02:00 committed by GitHub
parent 06ee8fdd47
commit 70df4ca461
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1581 additions and 0 deletions

View File

@ -710,6 +710,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/iqvia/ @bachya
/tests/components/iqvia/ @bachya
/homeassistant/components/irish_rail_transport/ @ttroy50
/homeassistant/components/iron_os/ @tr4nt0r
/tests/components/iron_os/ @tr4nt0r
/homeassistant/components/isal/ @bdraco
/tests/components/isal/ @bdraco
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair

View File

@ -0,0 +1,53 @@
"""The IronOS integration."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from pynecil import Pynecil
from homeassistant.components import bluetooth
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import IronOSCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
type IronOSConfigEntry = ConfigEntry[IronOSCoordinator]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: IronOSConfigEntry) -> bool:
"""Set up IronOS from a config entry."""
if TYPE_CHECKING:
assert entry.unique_id
ble_device = bluetooth.async_ble_device_from_address(
hass, entry.unique_id, connectable=True
)
if not ble_device:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="setup_device_unavailable_exception",
translation_placeholders={CONF_NAME: entry.title},
)
device = Pynecil(ble_device)
coordinator = IronOSCoordinator(hass, device)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: IronOSConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -0,0 +1,83 @@
"""Config flow for IronOS integration."""
from __future__ import annotations
from typing import Any
from habluetooth import BluetoothServiceInfoBleak
import voluptuous as vol
from homeassistant.components.bluetooth.api import async_discovered_service_info
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_ADDRESS
from .const import DISCOVERY_SVC_UUID, DOMAIN
class IronOSConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for IronOS."""
def __init__(self) -> None:
"""Initialize the config flow."""
self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_devices: dict[str, str] = {}
async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
) -> ConfigFlowResult:
"""Handle bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address)
self._abort_if_unique_id_configured()
self._discovery_info = discovery_info
return await self.async_step_bluetooth_confirm()
async def async_step_bluetooth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm discovery."""
assert self._discovery_info is not None
discovery_info = self._discovery_info
title = discovery_info.name
if user_input is not None:
return self.async_create_entry(title=title, data={})
self._set_confirm_only()
placeholders = {"name": title}
self.context["title_placeholders"] = placeholders
return self.async_show_form(
step_id="bluetooth_confirm", description_placeholders=placeholders
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the user step to pick discovered device."""
if user_input is not None:
address = user_input[CONF_ADDRESS]
title = self._discovered_devices[address]
await self.async_set_unique_id(address, raise_on_progress=False)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=title, data={})
current_addresses = self._async_current_ids()
for discovery_info in async_discovered_service_info(self.hass, True):
address = discovery_info.address
if (
DISCOVERY_SVC_UUID not in discovery_info.service_uuids
or address in current_addresses
or address in self._discovered_devices
):
continue
self._discovered_devices[address] = discovery_info.name
if not self._discovered_devices:
return self.async_abort(reason="no_devices_found")
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_ADDRESS): vol.In(self._discovered_devices)}
),
)

View File

@ -0,0 +1,10 @@
"""Constants for the IronOS integration."""
DOMAIN = "iron_os"
MANUFACTURER = "PINE64"
MODEL = "Pinecil V2"
OHM = "Ω"
DISCOVERY_SVC_UUID = "9eae1000-9d0d-48c5-aa55-33e27f9bc533"

View File

@ -0,0 +1,49 @@
"""Update coordinator for IronOS Integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from pynecil import CommunicationError, DeviceInfoResponse, LiveDataResponse, Pynecil
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
class IronOSCoordinator(DataUpdateCoordinator[LiveDataResponse]):
"""IronOS coordinator."""
device_info: DeviceInfoResponse
config_entry: ConfigEntry
def __init__(self, hass: HomeAssistant, device: Pynecil) -> None:
"""Initialize IronOS coordinator."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
self.device = device
async def _async_update_data(self) -> LiveDataResponse:
"""Fetch data from Device."""
try:
return await self.device.get_live_data()
except CommunicationError as e:
raise UpdateFailed("Cannot connect to device") from e
async def _async_setup(self) -> None:
"""Set up the coordinator."""
self.device_info = await self.device.get_device_info()

View File

@ -0,0 +1,41 @@
"""Base entity for IronOS integration."""
from __future__ import annotations
from typing import TYPE_CHECKING
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import MANUFACTURER, MODEL
from .coordinator import IronOSCoordinator
class IronOSBaseEntity(CoordinatorEntity[IronOSCoordinator]):
"""Base IronOS entity."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: IronOSCoordinator,
entity_description: EntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = entity_description
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}_{entity_description.key}"
)
if TYPE_CHECKING:
assert coordinator.config_entry.unique_id
self.device_info = DeviceInfo(
connections={(CONNECTION_BLUETOOTH, coordinator.config_entry.unique_id)},
manufacturer=MANUFACTURER,
model=MODEL,
name="Pinecil",
sw_version=coordinator.device_info.build,
serial_number=f"{coordinator.device_info.device_sn} (ID:{coordinator.device_info.device_id})",
)

View File

@ -0,0 +1,61 @@
{
"entity": {
"sensor": {
"live_temperature": {
"default": "mdi:soldering-iron"
},
"setpoint_temperature": {
"default": "mdi:thermostat"
},
"voltage": {
"default": "mdi:current-dc"
},
"handle_temperature": {
"default": "mdi:grease-pencil"
},
"power_pwm_level": {
"default": "mdi:square-wave"
},
"power_source": {
"default": "mdi:power-plug",
"state": {
"dc": "mdi:record-circle-outline",
"qc": "mdi:usb-port",
"pd_vbus": "mdi:usb-c-port",
"pd": "mdi:usb-c-port"
}
},
"tip_resistance": {
"default": "mdi:omega"
},
"hall_sensor": {
"default": "mdi:leak"
},
"movement_time": {
"default": "mdi:clock-fast"
},
"max_tip_temp_ability": {
"default": "mdi:thermometer-chevron-up"
},
"uptime": {
"default": "mdi:progress-clock"
},
"tip_voltage": {
"default": "mdi:sine-wave"
},
"operating_mode": {
"default": "mdi:format-list-bulleted",
"state": {
"boost": "mdi:rocket-launch",
"soldering": "mdi:soldering-iron",
"sleeping": "mdi:sleep",
"settings": "mdi:menu-open",
"debug": "mdi:bug-play"
}
},
"estimated_power": {
"default": "mdi:flash"
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"domain": "iron_os",
"name": "IronOS",
"bluetooth": [
{
"service_uuid": "9eae1000-9d0d-48c5-aa55-33e27f9bc533",
"connectable": true
}
],
"codeowners": ["@tr4nt0r"],
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/iron_os",
"iot_class": "local_polling",
"loggers": ["pynecil"],
"requirements": ["pynecil==0.2.0"]
}

View File

@ -0,0 +1,199 @@
"""Sensor platform for IronOS integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from pynecil import LiveDataResponse, OperatingMode, PowerSource
from homeassistant.components.sensor import (
EntityCategory,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
PERCENTAGE,
UnitOfElectricPotential,
UnitOfPower,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import IronOSConfigEntry
from .const import OHM
from .entity import IronOSBaseEntity
class PinecilSensor(StrEnum):
"""Pinecil Sensors."""
LIVE_TEMP = "live_temperature"
SETPOINT_TEMP = "setpoint_temperature"
DC_VOLTAGE = "voltage"
HANDLETEMP = "handle_temperature"
PWMLEVEL = "power_pwm_level"
POWER_SRC = "power_source"
TIP_RESISTANCE = "tip_resistance"
UPTIME = "uptime"
MOVEMENT_TIME = "movement_time"
MAX_TIP_TEMP_ABILITY = "max_tip_temp_ability"
TIP_VOLTAGE = "tip_voltage"
HALL_SENSOR = "hall_sensor"
OPERATING_MODE = "operating_mode"
ESTIMATED_POWER = "estimated_power"
@dataclass(frozen=True, kw_only=True)
class IronOSSensorEntityDescription(SensorEntityDescription):
"""IronOS sensor entity descriptions."""
value_fn: Callable[[LiveDataResponse], StateType]
PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
IronOSSensorEntityDescription(
key=PinecilSensor.LIVE_TEMP,
translation_key=PinecilSensor.LIVE_TEMP,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.live_temp,
),
IronOSSensorEntityDescription(
key=PinecilSensor.DC_VOLTAGE,
translation_key=PinecilSensor.DC_VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.dc_voltage,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.HANDLETEMP,
translation_key=PinecilSensor.HANDLETEMP,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.handle_temp,
),
IronOSSensorEntityDescription(
key=PinecilSensor.PWMLEVEL,
translation_key=PinecilSensor.PWMLEVEL,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.pwm_level,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.POWER_SRC,
translation_key=PinecilSensor.POWER_SRC,
device_class=SensorDeviceClass.ENUM,
options=[item.name.lower() for item in PowerSource],
value_fn=lambda data: data.power_src.name.lower() if data.power_src else None,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.TIP_RESISTANCE,
translation_key=PinecilSensor.TIP_RESISTANCE,
native_unit_of_measurement=OHM,
value_fn=lambda data: data.tip_resistance,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.UPTIME,
translation_key=PinecilSensor.UPTIME,
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda data: data.uptime,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.MOVEMENT_TIME,
translation_key=PinecilSensor.MOVEMENT_TIME,
native_unit_of_measurement=UnitOfTime.SECONDS,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.movement_time,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.MAX_TIP_TEMP_ABILITY,
translation_key=PinecilSensor.MAX_TIP_TEMP_ABILITY,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
value_fn=lambda data: data.max_tip_temp_ability,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.TIP_VOLTAGE,
translation_key=PinecilSensor.TIP_VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=3,
value_fn=lambda data: data.tip_voltage,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.HALL_SENSOR,
translation_key=PinecilSensor.HALL_SENSOR,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda data: data.hall_sensor,
entity_category=EntityCategory.DIAGNOSTIC,
),
IronOSSensorEntityDescription(
key=PinecilSensor.OPERATING_MODE,
translation_key=PinecilSensor.OPERATING_MODE,
device_class=SensorDeviceClass.ENUM,
options=[item.name.lower() for item in OperatingMode],
value_fn=(
lambda data: data.operating_mode.name.lower()
if data.operating_mode
else None
),
),
IronOSSensorEntityDescription(
key=PinecilSensor.ESTIMATED_POWER,
translation_key=PinecilSensor.ESTIMATED_POWER,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.estimated_power,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: IronOSConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensors from a config entry."""
coordinator = entry.runtime_data
async_add_entities(
IronOSSensorEntity(coordinator, description)
for description in PINECIL_SENSOR_DESCRIPTIONS
)
class IronOSSensorEntity(IronOSBaseEntity, SensorEntity):
"""Representation of a IronOS sensor entity."""
entity_description: IronOSSensorEntityDescription
@property
def native_value(self) -> StateType:
"""Return sensor state."""
return self.entity_description.value_fn(self.coordinator.data)

View File

@ -0,0 +1,84 @@
{
"config": {
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",
"data": {
"address": "[%key:common::config_flow::data::device%]"
}
},
"bluetooth_confirm": {
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
}
},
"abort": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"entity": {
"sensor": {
"live_temperature": {
"name": "Tip temperature"
},
"voltage": {
"name": "DC input voltage"
},
"handle_temperature": {
"name": "Handle temperature"
},
"power_pwm_level": {
"name": "Power level"
},
"power_source": {
"name": "Power source",
"state": {
"dc": "DC input",
"qc": "USB Quick Charge",
"pd_vbus": "USB PD VBUS",
"pd": "USB Power Delivery"
}
},
"tip_resistance": {
"name": "Tip resistance"
},
"uptime": {
"name": "Uptime"
},
"movement_time": {
"name": "Last movement time"
},
"max_tip_temp_ability": {
"name": "Max tip temperature"
},
"tip_voltage": {
"name": "Raw tip voltage"
},
"hall_sensor": {
"name": "Hall effect strength"
},
"operating_mode": {
"name": "Operating mode",
"state": {
"idle": "[%key:common::state::idle%]",
"soldering": "Soldering",
"sleeping": "Sleeping",
"settings": "Settings",
"debug": "Debug",
"boost": "Boost"
}
},
"estimated_power": {
"name": "Estimated power"
}
}
},
"exceptions": {
"setup_device_unavailable_exception": {
"message": "Device {name} is not reachable"
},
"setup_device_connection_error_exception": {
"message": "Connection to device {name} failed, try again later"
}
}
}

View File

@ -321,6 +321,11 @@ BLUETOOTH: Final[list[dict[str, bool | str | int | list[int]]]] = [
"domain": "inkbird",
"local_name": "tps",
},
{
"connectable": True,
"domain": "iron_os",
"service_uuid": "9eae1000-9d0d-48c5-aa55-33e27f9bc533",
},
{
"connectable": False,
"domain": "kegtron",

View File

@ -276,6 +276,7 @@ FLOWS = {
"ipma",
"ipp",
"iqvia",
"iron_os",
"islamic_prayer_times",
"israel_rail",
"iss",

View File

@ -2899,6 +2899,12 @@
"config_flow": false,
"iot_class": "cloud_polling"
},
"iron_os": {
"name": "IronOS",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_polling"
},
"islamic_prayer_times": {
"integration_type": "hub",
"config_flow": true,

View File

@ -2025,6 +2025,9 @@ pymsteams==0.1.12
# homeassistant.components.mysensors
pymysensors==0.24.0
# homeassistant.components.iron_os
pynecil==0.2.0
# homeassistant.components.netgear
pynetgear==0.10.10

View File

@ -1615,6 +1615,9 @@ pymonoprice==0.4
# homeassistant.components.mysensors
pymysensors==0.24.0
# homeassistant.components.iron_os
pynecil==0.2.0
# homeassistant.components.netgear
pynetgear==0.10.10

View File

@ -0,0 +1 @@
"""Tests for the Pinecil integration."""

View File

@ -0,0 +1,141 @@
"""Fixtures for Pinecil tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch
from bleak.backends.device import BLEDevice
from habluetooth import BluetoothServiceInfoBleak
from pynecil import DeviceInfoResponse, LiveDataResponse, OperatingMode, PowerSource
import pytest
from homeassistant.components.iron_os import DOMAIN
from homeassistant.const import CONF_ADDRESS
from tests.common import MockConfigEntry
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
USER_INPUT = {CONF_ADDRESS: "c0:ff:ee:c0:ff:ee"}
DEFAULT_NAME = "Pinecil-C0FFEEE"
PINECIL_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Pinecil-C0FFEEE",
address="c0:ff:ee:c0:ff:ee",
device=generate_ble_device(
address="c0:ff:ee:c0:ff:ee",
name="Pinecil-C0FFEEE",
),
rssi=-61,
manufacturer_data={},
service_data={},
service_uuids=["9eae1000-9d0d-48c5-aa55-33e27f9bc533"],
source="local",
advertisement=generate_advertisement_data(
manufacturer_data={},
service_uuids=["9eae1000-9d0d-48c5-aa55-33e27f9bc533"],
),
connectable=True,
time=0,
tx_power=None,
)
UNKNOWN_SERVICE_INFO = BluetoothServiceInfoBleak(
name="",
address="c0:ff:ee:c0:ff:ee",
device=generate_ble_device(
address="c0:ff:ee:c0:ff:ee",
name="",
),
rssi=-61,
manufacturer_data={},
service_data={},
service_uuids=[],
source="local",
advertisement=generate_advertisement_data(
manufacturer_data={},
service_uuids=[],
),
connectable=True,
time=0,
tx_power=None,
)
@pytest.fixture(autouse=True)
def mock_bluetooth(enable_bluetooth: None) -> None:
"""Auto mock bluetooth."""
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.iron_os.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture(name="discovery")
def mock_async_discovered_service_info() -> Generator[MagicMock]:
"""Mock service discovery."""
with patch(
"homeassistant.components.iron_os.config_flow.async_discovered_service_info",
return_value=[PINECIL_SERVICE_INFO, UNKNOWN_SERVICE_INFO],
) as discovery:
yield discovery
@pytest.fixture(name="config_entry")
def mock_config_entry() -> MockConfigEntry:
"""Mock Pinecil configuration entry."""
return MockConfigEntry(
domain=DOMAIN,
title=DEFAULT_NAME,
data={},
unique_id="c0:ff:ee:c0:ff:ee",
entry_id="1234567890",
)
@pytest.fixture(name="ble_device")
def mock_ble_device() -> Generator[MagicMock]:
"""Mock BLEDevice."""
with patch(
"homeassistant.components.bluetooth.async_ble_device_from_address",
return_value=BLEDevice(
address="c0:ff:ee:c0:ff:ee", name=DEFAULT_NAME, rssi=-50, details={}
),
) as ble_device:
yield ble_device
@pytest.fixture
def mock_pynecil() -> Generator[AsyncMock, None, None]:
"""Mock Pynecil library."""
with patch(
"homeassistant.components.iron_os.Pynecil", autospec=True
) as mock_client:
client = mock_client.return_value
client.get_device_info.return_value = DeviceInfoResponse(
build="v2.22",
device_id="c0ffeeC0",
address="c0:ff:ee:c0:ff:ee",
device_sn="0000c0ffeec0ffee",
name=DEFAULT_NAME,
)
client.get_live_data.return_value = LiveDataResponse(
live_temp=298,
setpoint_temp=300,
dc_voltage=20.6,
handle_temp=36.3,
pwm_level=41,
power_src=PowerSource.PD,
tip_resistance=6.2,
uptime=1671,
movement_time=10000,
max_tip_temp_ability=460,
tip_voltage=2212,
hall_sensor=0,
operating_mode=OperatingMode.SOLDERING,
estimated_power=24.8,
)
yield client

View File

@ -0,0 +1,683 @@
# serializer version: 1
# name: test_sensors[sensor.pinecil_dc_input_voltage-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_dc_input_voltage',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'DC input voltage',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.DC_VOLTAGE: 'voltage'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_voltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
})
# ---
# name: test_sensors[sensor.pinecil_dc_input_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'voltage',
'friendly_name': 'Pinecil DC input voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_dc_input_voltage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '20.6',
})
# ---
# name: test_sensors[sensor.pinecil_estimated_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.pinecil_estimated_power',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Estimated power',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.ESTIMATED_POWER: 'estimated_power'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_estimated_power',
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
# name: test_sensors[sensor.pinecil_estimated_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Pinecil Estimated power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_estimated_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '24.8',
})
# ---
# name: test_sensors[sensor.pinecil_hall_effect_strength-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_hall_effect_strength',
'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': 'Hall effect strength',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.HALL_SENSOR: 'hall_sensor'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_hall_sensor',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.pinecil_hall_effect_strength-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Pinecil Hall effect strength',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_hall_effect_strength',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_sensors[sensor.pinecil_handle_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.pinecil_handle_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Handle temperature',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.HANDLETEMP: 'handle_temperature'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_handle_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[sensor.pinecil_handle_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Pinecil Handle temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_handle_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '36.3',
})
# ---
# name: test_sensors[sensor.pinecil_last_movement_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_last_movement_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Last movement time',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.MOVEMENT_TIME: 'movement_time'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_movement_time',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_sensors[sensor.pinecil_last_movement_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Pinecil Last movement time',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_last_movement_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '10000',
})
# ---
# name: test_sensors[sensor.pinecil_max_tip_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_max_tip_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Max tip temperature',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.MAX_TIP_TEMP_ABILITY: 'max_tip_temp_ability'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_max_tip_temp_ability',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[sensor.pinecil_max_tip_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Pinecil Max tip temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_max_tip_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '460',
})
# ---
# name: test_sensors[sensor.pinecil_operating_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'idle',
'soldering',
'boost',
'sleeping',
'settings',
'debug',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.pinecil_operating_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operating mode',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.OPERATING_MODE: 'operating_mode'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_operating_mode',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.pinecil_operating_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Pinecil Operating mode',
'options': list([
'idle',
'soldering',
'boost',
'sleeping',
'settings',
'debug',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_operating_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'soldering',
})
# ---
# name: test_sensors[sensor.pinecil_power_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_power_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': <SensorDeviceClass.POWER_FACTOR: 'power_factor'>,
'original_icon': None,
'original_name': 'Power level',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.PWMLEVEL: 'power_pwm_level'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_power_pwm_level',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[sensor.pinecil_power_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power_factor',
'friendly_name': 'Pinecil Power level',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_power_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '41',
})
# ---
# name: test_sensors[sensor.pinecil_power_source-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'dc',
'qc',
'pd_vbus',
'pd',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_power_source',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Power source',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.POWER_SRC: 'power_source'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_power_source',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.pinecil_power_source-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Pinecil Power source',
'options': list([
'dc',
'qc',
'pd_vbus',
'pd',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_power_source',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'pd',
})
# ---
# name: test_sensors[sensor.pinecil_raw_tip_voltage-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_raw_tip_voltage',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 3,
}),
}),
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'Raw tip voltage',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.TIP_VOLTAGE: 'tip_voltage'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_tip_voltage',
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
})
# ---
# name: test_sensors[sensor.pinecil_raw_tip_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'voltage',
'friendly_name': 'Pinecil Raw tip voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_raw_tip_voltage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2212',
})
# ---
# name: test_sensors[sensor.pinecil_tip_resistance-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_tip_resistance',
'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': 'Tip resistance',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.TIP_RESISTANCE: 'tip_resistance'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_tip_resistance',
'unit_of_measurement': 'Ω',
})
# ---
# name: test_sensors[sensor.pinecil_tip_resistance-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Pinecil Tip resistance',
'unit_of_measurement': 'Ω',
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_tip_resistance',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '6.2',
})
# ---
# name: test_sensors[sensor.pinecil_tip_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.pinecil_tip_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Tip temperature',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.LIVE_TEMP: 'live_temperature'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_live_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[sensor.pinecil_tip_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Pinecil Tip temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_tip_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '298',
})
# ---
# name: test_sensors[sensor.pinecil_uptime-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.pinecil_uptime',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': None,
'original_name': 'Uptime',
'platform': 'iron_os',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <PinecilSensor.UPTIME: 'uptime'>,
'unique_id': 'c0:ff:ee:c0:ff:ee_uptime',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_sensors[sensor.pinecil_uptime-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Pinecil Uptime',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pinecil_uptime',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1671',
})
# ---

View File

@ -0,0 +1,66 @@
"""Tests for the Pinecil config flow."""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
from homeassistant.components.iron_os import DOMAIN
from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .conftest import DEFAULT_NAME, PINECIL_SERVICE_INFO, USER_INPUT
async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, discovery: MagicMock
) -> None:
"""Test the user config flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT,
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == DEFAULT_NAME
assert result["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_no_device_discovered(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
discovery: MagicMock,
) -> None:
"""Test setup with no device discoveries."""
discovery.return_value = []
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "no_devices_found"
async def test_async_step_bluetooth(hass: HomeAssistant) -> None:
"""Test discovery via bluetooth.."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=PINECIL_SERVICE_INFO,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == DEFAULT_NAME
assert result["data"] == {}
assert result["result"].unique_id == "c0:ff:ee:c0:ff:ee"

View File

@ -0,0 +1,73 @@
"""Tests for the Pinecil Sensors."""
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from pynecil import CommunicationError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.iron_os.coordinator import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@pytest.fixture(autouse=True)
async def sensor_only() -> AsyncGenerator[None, None]:
"""Enable only the sensor platform."""
with patch(
"homeassistant.components.iron_os.PLATFORMS",
[Platform.SENSOR],
):
yield
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_pynecil: AsyncMock,
ble_device: MagicMock,
) -> None:
"""Test the Pinecil sensor platform."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensors_unavailable(
hass: HomeAssistant,
config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_pynecil: AsyncMock,
ble_device: MagicMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the sensors when device disconnects."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
mock_pynecil.get_live_data.side_effect = CommunicationError
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
entity_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
for entity_entry in entity_entries:
assert hass.states.get(entity_entry.entity_id).state == STATE_UNAVAILABLE