mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Improve type annotations for the Brother integration (#49771)
This commit is contained in:
parent
f7cf82be6d
commit
3210c086ef
@ -6,6 +6,7 @@ homeassistant.components
|
|||||||
homeassistant.components.automation.*
|
homeassistant.components.automation.*
|
||||||
homeassistant.components.binary_sensor.*
|
homeassistant.components.binary_sensor.*
|
||||||
homeassistant.components.bond.*
|
homeassistant.components.bond.*
|
||||||
|
homeassistant.components.brother.*
|
||||||
homeassistant.components.calendar.*
|
homeassistant.components.calendar.*
|
||||||
homeassistant.components.cover.*
|
homeassistant.components.cover.*
|
||||||
homeassistant.components.device_automation.*
|
homeassistant.components.device_automation.*
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
"""The Brother component."""
|
"""The Brother component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from brother import Brother, SnmpError, UnsupportedModel
|
from brother import Brother, DictToObj, SnmpError, UnsupportedModel
|
||||||
|
import pysnmp.hlapi.asyncio as SnmpEngine
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_TYPE
|
from homeassistant.const import CONF_HOST, CONF_TYPE
|
||||||
@ -19,7 +22,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Brother from a config entry."""
|
"""Set up Brother from a config entry."""
|
||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
kind = entry.data[CONF_TYPE]
|
kind = entry.data[CONF_TYPE]
|
||||||
@ -41,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
@ -57,7 +60,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
|
class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""Class to manage fetching Brother data from the printer."""
|
"""Class to manage fetching Brother data from the printer."""
|
||||||
|
|
||||||
def __init__(self, hass, host, kind, snmp_engine):
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, host: str, kind: str, snmp_engine: SnmpEngine
|
||||||
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine)
|
self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine)
|
||||||
|
|
||||||
@ -68,7 +73,7 @@ class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
update_interval=SCAN_INTERVAL,
|
update_interval=SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_update_data(self):
|
async def _async_update_data(self) -> DictToObj:
|
||||||
"""Update data via library."""
|
"""Update data via library."""
|
||||||
try:
|
try:
|
||||||
data = await self.brother.async_update()
|
data = await self.brother.async_update()
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
"""Adds config flow for Brother Printer."""
|
"""Adds config flow for Brother Printer."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from brother import Brother, SnmpError, UnsupportedModel
|
from brother import Brother, SnmpError, UnsupportedModel
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, exceptions
|
from homeassistant import config_entries, exceptions
|
||||||
from homeassistant.const import CONF_HOST, CONF_TYPE
|
from homeassistant.const import CONF_HOST, CONF_TYPE
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
|
||||||
from .const import DOMAIN, PRINTER_TYPES
|
from .const import DOMAIN, PRINTER_TYPES
|
||||||
from .utils import get_snmp_engine
|
from .utils import get_snmp_engine
|
||||||
@ -19,14 +24,15 @@ DATA_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def host_valid(host):
|
def host_valid(host: str) -> bool:
|
||||||
"""Return True if hostname or IP address is valid."""
|
"""Return True if hostname or IP address is valid."""
|
||||||
try:
|
try:
|
||||||
if ipaddress.ip_address(host).version == (4 or 6):
|
if ipaddress.ip_address(host).version in [4, 6]:
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
disallowed = re.compile(r"[^a-zA-Z\d\-]")
|
disallowed = re.compile(r"[^a-zA-Z\d\-]")
|
||||||
return all(x and not disallowed.search(x) for x in host.split("."))
|
return all(x and not disallowed.search(x) for x in host.split("."))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -35,12 +41,14 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.brother = None
|
self.brother: Brother = None
|
||||||
self.host = None
|
self.host: str | None = None
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
@ -72,11 +80,10 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_zeroconf(self, discovery_info):
|
async def async_step_zeroconf(
|
||||||
|
self, discovery_info: DiscoveryInfoType
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle zeroconf discovery."""
|
"""Handle zeroconf discovery."""
|
||||||
if discovery_info is None:
|
|
||||||
return self.async_abort(reason="cannot_connect")
|
|
||||||
|
|
||||||
if not discovery_info.get("name") or not discovery_info["name"].startswith(
|
if not discovery_info.get("name") or not discovery_info["name"].startswith(
|
||||||
"Brother"
|
"Brother"
|
||||||
):
|
):
|
||||||
@ -107,7 +114,9 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
return await self.async_step_zeroconf_confirm()
|
return await self.async_step_zeroconf_confirm()
|
||||||
|
|
||||||
async def async_step_zeroconf_confirm(self, user_input=None):
|
async def async_step_zeroconf_confirm(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
"""Handle a flow initiated by zeroconf."""
|
"""Handle a flow initiated by zeroconf."""
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
title = f"{self.brother.model} {self.brother.serial}"
|
title = f"{self.brother.model} {self.brother.serial}"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
"""Constants for Brother integration."""
|
"""Constants for Brother integration."""
|
||||||
from homeassistant.const import ATTR_ICON, PERCENTAGE
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from homeassistant.const import PERCENTAGE
|
||||||
|
|
||||||
ATTR_BELT_UNIT_REMAINING_LIFE = "belt_unit_remaining_life"
|
ATTR_BELT_UNIT_REMAINING_LIFE = "belt_unit_remaining_life"
|
||||||
ATTR_BLACK_DRUM_COUNTER = "black_drum_counter"
|
ATTR_BLACK_DRUM_COUNTER = "black_drum_counter"
|
||||||
@ -18,9 +22,7 @@ ATTR_DRUM_COUNTER = "drum_counter"
|
|||||||
ATTR_DRUM_REMAINING_LIFE = "drum_remaining_life"
|
ATTR_DRUM_REMAINING_LIFE = "drum_remaining_life"
|
||||||
ATTR_DRUM_REMAINING_PAGES = "drum_remaining_pages"
|
ATTR_DRUM_REMAINING_PAGES = "drum_remaining_pages"
|
||||||
ATTR_DUPLEX_COUNTER = "duplex_unit_pages_counter"
|
ATTR_DUPLEX_COUNTER = "duplex_unit_pages_counter"
|
||||||
ATTR_ENABLED = "enabled"
|
|
||||||
ATTR_FUSER_REMAINING_LIFE = "fuser_remaining_life"
|
ATTR_FUSER_REMAINING_LIFE = "fuser_remaining_life"
|
||||||
ATTR_LABEL = "label"
|
|
||||||
ATTR_LASER_REMAINING_LIFE = "laser_remaining_life"
|
ATTR_LASER_REMAINING_LIFE = "laser_remaining_life"
|
||||||
ATTR_MAGENTA_DRUM_COUNTER = "magenta_drum_counter"
|
ATTR_MAGENTA_DRUM_COUNTER = "magenta_drum_counter"
|
||||||
ATTR_MAGENTA_DRUM_REMAINING_LIFE = "magenta_drum_remaining_life"
|
ATTR_MAGENTA_DRUM_REMAINING_LIFE = "magenta_drum_remaining_life"
|
||||||
@ -32,7 +34,6 @@ ATTR_PAGE_COUNTER = "page_counter"
|
|||||||
ATTR_PF_KIT_1_REMAINING_LIFE = "pf_kit_1_remaining_life"
|
ATTR_PF_KIT_1_REMAINING_LIFE = "pf_kit_1_remaining_life"
|
||||||
ATTR_PF_KIT_MP_REMAINING_LIFE = "pf_kit_mp_remaining_life"
|
ATTR_PF_KIT_MP_REMAINING_LIFE = "pf_kit_mp_remaining_life"
|
||||||
ATTR_STATUS = "status"
|
ATTR_STATUS = "status"
|
||||||
ATTR_UNIT = "unit"
|
|
||||||
ATTR_UPTIME = "uptime"
|
ATTR_UPTIME = "uptime"
|
||||||
ATTR_YELLOW_DRUM_COUNTER = "yellow_drum_counter"
|
ATTR_YELLOW_DRUM_COUNTER = "yellow_drum_counter"
|
||||||
ATTR_YELLOW_DRUM_REMAINING_LIFE = "yellow_drum_remaining_life"
|
ATTR_YELLOW_DRUM_REMAINING_LIFE = "yellow_drum_remaining_life"
|
||||||
@ -50,7 +51,7 @@ PRINTER_TYPES = ["laser", "ink"]
|
|||||||
|
|
||||||
SNMP = "snmp"
|
SNMP = "snmp"
|
||||||
|
|
||||||
ATTRS_MAP = {
|
ATTRS_MAP: dict[str, tuple[str, str]] = {
|
||||||
ATTR_DRUM_REMAINING_LIFE: (ATTR_DRUM_REMAINING_PAGES, ATTR_DRUM_COUNTER),
|
ATTR_DRUM_REMAINING_LIFE: (ATTR_DRUM_REMAINING_PAGES, ATTR_DRUM_COUNTER),
|
||||||
ATTR_BLACK_DRUM_REMAINING_LIFE: (
|
ATTR_BLACK_DRUM_REMAINING_LIFE: (
|
||||||
ATTR_BLACK_DRUM_REMAINING_PAGES,
|
ATTR_BLACK_DRUM_REMAINING_PAGES,
|
||||||
@ -70,149 +71,158 @@ ATTRS_MAP = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES: dict[str, SensorDescription] = {
|
||||||
ATTR_STATUS: {
|
ATTR_STATUS: {
|
||||||
ATTR_ICON: "mdi:printer",
|
"icon": "mdi:printer",
|
||||||
ATTR_LABEL: ATTR_STATUS.title(),
|
"label": ATTR_STATUS.title(),
|
||||||
ATTR_UNIT: None,
|
"unit": None,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_PAGE_COUNTER: {
|
ATTR_PAGE_COUNTER: {
|
||||||
ATTR_ICON: "mdi:file-document-outline",
|
"icon": "mdi:file-document-outline",
|
||||||
ATTR_LABEL: ATTR_PAGE_COUNTER.replace("_", " ").title(),
|
"label": ATTR_PAGE_COUNTER.replace("_", " ").title(),
|
||||||
ATTR_UNIT: UNIT_PAGES,
|
"unit": UNIT_PAGES,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_BW_COUNTER: {
|
ATTR_BW_COUNTER: {
|
||||||
ATTR_ICON: "mdi:file-document-outline",
|
"icon": "mdi:file-document-outline",
|
||||||
ATTR_LABEL: ATTR_BW_COUNTER.replace("_", " ").title(),
|
"label": ATTR_BW_COUNTER.replace("_", " ").title(),
|
||||||
ATTR_UNIT: UNIT_PAGES,
|
"unit": UNIT_PAGES,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_COLOR_COUNTER: {
|
ATTR_COLOR_COUNTER: {
|
||||||
ATTR_ICON: "mdi:file-document-outline",
|
"icon": "mdi:file-document-outline",
|
||||||
ATTR_LABEL: ATTR_COLOR_COUNTER.replace("_", " ").title(),
|
"label": ATTR_COLOR_COUNTER.replace("_", " ").title(),
|
||||||
ATTR_UNIT: UNIT_PAGES,
|
"unit": UNIT_PAGES,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_DUPLEX_COUNTER: {
|
ATTR_DUPLEX_COUNTER: {
|
||||||
ATTR_ICON: "mdi:file-document-outline",
|
"icon": "mdi:file-document-outline",
|
||||||
ATTR_LABEL: ATTR_DUPLEX_COUNTER.replace("_", " ").title(),
|
"label": ATTR_DUPLEX_COUNTER.replace("_", " ").title(),
|
||||||
ATTR_UNIT: UNIT_PAGES,
|
"unit": UNIT_PAGES,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_DRUM_REMAINING_LIFE: {
|
ATTR_DRUM_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:chart-donut",
|
"icon": "mdi:chart-donut",
|
||||||
ATTR_LABEL: ATTR_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_BLACK_DRUM_REMAINING_LIFE: {
|
ATTR_BLACK_DRUM_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:chart-donut",
|
"icon": "mdi:chart-donut",
|
||||||
ATTR_LABEL: ATTR_BLACK_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_BLACK_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_CYAN_DRUM_REMAINING_LIFE: {
|
ATTR_CYAN_DRUM_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:chart-donut",
|
"icon": "mdi:chart-donut",
|
||||||
ATTR_LABEL: ATTR_CYAN_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_CYAN_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_MAGENTA_DRUM_REMAINING_LIFE: {
|
ATTR_MAGENTA_DRUM_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:chart-donut",
|
"icon": "mdi:chart-donut",
|
||||||
ATTR_LABEL: ATTR_MAGENTA_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_MAGENTA_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_YELLOW_DRUM_REMAINING_LIFE: {
|
ATTR_YELLOW_DRUM_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:chart-donut",
|
"icon": "mdi:chart-donut",
|
||||||
ATTR_LABEL: ATTR_YELLOW_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_YELLOW_DRUM_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_BELT_UNIT_REMAINING_LIFE: {
|
ATTR_BELT_UNIT_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:current-ac",
|
"icon": "mdi:current-ac",
|
||||||
ATTR_LABEL: ATTR_BELT_UNIT_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_BELT_UNIT_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_FUSER_REMAINING_LIFE: {
|
ATTR_FUSER_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:water-outline",
|
"icon": "mdi:water-outline",
|
||||||
ATTR_LABEL: ATTR_FUSER_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_FUSER_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_LASER_REMAINING_LIFE: {
|
ATTR_LASER_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:spotlight-beam",
|
"icon": "mdi:spotlight-beam",
|
||||||
ATTR_LABEL: ATTR_LASER_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_LASER_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_PF_KIT_1_REMAINING_LIFE: {
|
ATTR_PF_KIT_1_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:printer-3d",
|
"icon": "mdi:printer-3d",
|
||||||
ATTR_LABEL: ATTR_PF_KIT_1_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_PF_KIT_1_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_PF_KIT_MP_REMAINING_LIFE: {
|
ATTR_PF_KIT_MP_REMAINING_LIFE: {
|
||||||
ATTR_ICON: "mdi:printer-3d",
|
"icon": "mdi:printer-3d",
|
||||||
ATTR_LABEL: ATTR_PF_KIT_MP_REMAINING_LIFE.replace("_", " ").title(),
|
"label": ATTR_PF_KIT_MP_REMAINING_LIFE.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_BLACK_TONER_REMAINING: {
|
ATTR_BLACK_TONER_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_BLACK_TONER_REMAINING.replace("_", " ").title(),
|
"label": ATTR_BLACK_TONER_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_CYAN_TONER_REMAINING: {
|
ATTR_CYAN_TONER_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_CYAN_TONER_REMAINING.replace("_", " ").title(),
|
"label": ATTR_CYAN_TONER_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_MAGENTA_TONER_REMAINING: {
|
ATTR_MAGENTA_TONER_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_MAGENTA_TONER_REMAINING.replace("_", " ").title(),
|
"label": ATTR_MAGENTA_TONER_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_YELLOW_TONER_REMAINING: {
|
ATTR_YELLOW_TONER_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_YELLOW_TONER_REMAINING.replace("_", " ").title(),
|
"label": ATTR_YELLOW_TONER_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_BLACK_INK_REMAINING: {
|
ATTR_BLACK_INK_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_BLACK_INK_REMAINING.replace("_", " ").title(),
|
"label": ATTR_BLACK_INK_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_CYAN_INK_REMAINING: {
|
ATTR_CYAN_INK_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_CYAN_INK_REMAINING.replace("_", " ").title(),
|
"label": ATTR_CYAN_INK_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_MAGENTA_INK_REMAINING: {
|
ATTR_MAGENTA_INK_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_MAGENTA_INK_REMAINING.replace("_", " ").title(),
|
"label": ATTR_MAGENTA_INK_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_YELLOW_INK_REMAINING: {
|
ATTR_YELLOW_INK_REMAINING: {
|
||||||
ATTR_ICON: "mdi:printer-3d-nozzle",
|
"icon": "mdi:printer-3d-nozzle",
|
||||||
ATTR_LABEL: ATTR_YELLOW_INK_REMAINING.replace("_", " ").title(),
|
"label": ATTR_YELLOW_INK_REMAINING.replace("_", " ").title(),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"unit": PERCENTAGE,
|
||||||
ATTR_ENABLED: True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
ATTR_UPTIME: {
|
ATTR_UPTIME: {
|
||||||
ATTR_ICON: None,
|
"icon": None,
|
||||||
ATTR_LABEL: ATTR_UPTIME.title(),
|
"label": ATTR_UPTIME.title(),
|
||||||
ATTR_UNIT: None,
|
"unit": None,
|
||||||
ATTR_ENABLED: False,
|
"enabled": False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDescription(TypedDict):
|
||||||
|
"""Sensor description class."""
|
||||||
|
|
||||||
|
icon: str | None
|
||||||
|
label: str
|
||||||
|
unit: str | None
|
||||||
|
enabled: bool
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
"""Support for the Brother service."""
|
"""Support for the Brother service."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import BrotherDataUpdateCoordinator
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ENABLED,
|
|
||||||
ATTR_ICON,
|
|
||||||
ATTR_LABEL,
|
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_UNIT,
|
|
||||||
ATTR_UPTIME,
|
ATTR_UPTIME,
|
||||||
ATTRS_MAP,
|
ATTRS_MAP,
|
||||||
DATA_CONFIG_ENTRY,
|
DATA_CONFIG_ENTRY,
|
||||||
@ -20,9 +24,11 @@ ATTR_COUNTER = "counter"
|
|||||||
ATTR_REMAINING_PAGES = "remaining_pages"
|
ATTR_REMAINING_PAGES = "remaining_pages"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
"""Add Brother entities from a config_entry."""
|
"""Add Brother entities from a config_entry."""
|
||||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id]
|
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
@ -43,36 +49,42 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class BrotherPrinterSensor(CoordinatorEntity, SensorEntity):
|
class BrotherPrinterSensor(CoordinatorEntity, SensorEntity):
|
||||||
"""Define an Brother Printer sensor."""
|
"""Define an Brother Printer sensor."""
|
||||||
|
|
||||||
def __init__(self, coordinator, kind, device_info):
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: BrotherDataUpdateCoordinator,
|
||||||
|
kind: str,
|
||||||
|
device_info: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._name = f"{coordinator.data.model} {SENSOR_TYPES[kind][ATTR_LABEL]}"
|
self._description = SENSOR_TYPES[kind]
|
||||||
|
self._name = f"{coordinator.data.model} {self._description['label']}"
|
||||||
self._unique_id = f"{coordinator.data.serial.lower()}_{kind}"
|
self._unique_id = f"{coordinator.data.serial.lower()}_{kind}"
|
||||||
self._device_info = device_info
|
self._device_info = device_info
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self._attrs = {}
|
self._attrs: dict[str, Any] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name."""
|
"""Return the name."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> Any:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
if self.kind == ATTR_UPTIME:
|
if self.kind == ATTR_UPTIME:
|
||||||
return getattr(self.coordinator.data, self.kind).isoformat()
|
return getattr(self.coordinator.data, self.kind).isoformat()
|
||||||
return getattr(self.coordinator.data, self.kind)
|
return getattr(self.coordinator.data, self.kind)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> str | None:
|
||||||
"""Return the class of this sensor."""
|
"""Return the class of this sensor."""
|
||||||
if self.kind == ATTR_UPTIME:
|
if self.kind == ATTR_UPTIME:
|
||||||
return DEVICE_CLASS_TIMESTAMP
|
return DEVICE_CLASS_TIMESTAMP
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
remaining_pages, drum_counter = ATTRS_MAP.get(self.kind, (None, None))
|
remaining_pages, drum_counter = ATTRS_MAP.get(self.kind, (None, None))
|
||||||
if remaining_pages and drum_counter:
|
if remaining_pages and drum_counter:
|
||||||
@ -83,26 +95,26 @@ class BrotherPrinterSensor(CoordinatorEntity, SensorEntity):
|
|||||||
return self._attrs
|
return self._attrs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self) -> str | None:
|
||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
return SENSOR_TYPES[self.kind][ATTR_ICON]
|
return self._description["icon"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self) -> str:
|
||||||
"""Return a unique_id for this entity."""
|
"""Return a unique_id for this entity."""
|
||||||
return self._unique_id
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit the value is expressed in."""
|
"""Return the unit the value is expressed in."""
|
||||||
return SENSOR_TYPES[self.kind][ATTR_UNIT]
|
return self._description["unit"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self) -> dict[str, Any]:
|
||||||
"""Return the device info."""
|
"""Return the device info."""
|
||||||
return self._device_info
|
return self._device_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_registry_enabled_default(self):
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
return SENSOR_TYPES[self.kind][ATTR_ENABLED]
|
return self._description["enabled"]
|
||||||
|
@ -5,7 +5,7 @@ import pysnmp.hlapi.asyncio as hlapi
|
|||||||
from pysnmp.hlapi.asyncio.cmdgen import lcd
|
from pysnmp.hlapi.asyncio.cmdgen import lcd
|
||||||
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import singleton
|
from homeassistant.helpers import singleton
|
||||||
|
|
||||||
from .const import DOMAIN, SNMP
|
from .const import DOMAIN, SNMP
|
||||||
@ -14,13 +14,13 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@singleton.singleton("snmp_engine")
|
@singleton.singleton("snmp_engine")
|
||||||
def get_snmp_engine(hass):
|
def get_snmp_engine(hass: HomeAssistant) -> hlapi.SnmpEngine:
|
||||||
"""Get SNMP engine."""
|
"""Get SNMP engine."""
|
||||||
_LOGGER.debug("Creating SNMP engine")
|
_LOGGER.debug("Creating SNMP engine")
|
||||||
snmp_engine = hlapi.SnmpEngine()
|
snmp_engine = hlapi.SnmpEngine()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def shutdown_listener(ev):
|
def shutdown_listener(ev: Event) -> None:
|
||||||
if hass.data.get(DOMAIN):
|
if hass.data.get(DOMAIN):
|
||||||
_LOGGER.debug("Unconfiguring SNMP engine")
|
_LOGGER.debug("Unconfiguring SNMP engine")
|
||||||
lcd.unconfigure(hass.data[DOMAIN][SNMP], None)
|
lcd.unconfigure(hass.data[DOMAIN][SNMP], None)
|
||||||
|
2
mypy.ini
2
mypy.ini
@ -35,7 +35,7 @@ warn_return_any = false
|
|||||||
warn_unreachable = false
|
warn_unreachable = false
|
||||||
warn_unused_ignores = false
|
warn_unused_ignores = false
|
||||||
|
|
||||||
[mypy-homeassistant.components,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sonos.media_player,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*]
|
[mypy-homeassistant.components,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.brother.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sonos.media_player,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
disallow_subclassing_any = true
|
disallow_subclassing_any = true
|
||||||
|
@ -40,8 +40,8 @@ async def test_create_entry_with_hostname(hass):
|
|||||||
assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE]
|
assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE]
|
||||||
|
|
||||||
|
|
||||||
async def test_create_entry_with_ip_address(hass):
|
async def test_create_entry_with_ipv4_address(hass):
|
||||||
"""Test that the user step works with printer IP address."""
|
"""Test that the user step works with printer IPv4 address."""
|
||||||
with patch(
|
with patch(
|
||||||
"brother.Brother._get_data",
|
"brother.Brother._get_data",
|
||||||
return_value=json.loads(load_fixture("brother_printer_data.json")),
|
return_value=json.loads(load_fixture("brother_printer_data.json")),
|
||||||
@ -58,6 +58,24 @@ async def test_create_entry_with_ip_address(hass):
|
|||||||
assert result["data"][CONF_TYPE] == "laser"
|
assert result["data"][CONF_TYPE] == "laser"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_entry_with_ipv6_address(hass):
|
||||||
|
"""Test that the user step works with printer IPv6 address."""
|
||||||
|
with patch(
|
||||||
|
"brother.Brother._get_data",
|
||||||
|
return_value=json.loads(load_fixture("brother_printer_data.json")),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_HOST: "2001:db8::1428:57ab", CONF_TYPE: "laser"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "HL-L2340DW 0123456789"
|
||||||
|
assert result["data"][CONF_HOST] == "2001:db8::1428:57ab"
|
||||||
|
assert result["data"][CONF_TYPE] == "laser"
|
||||||
|
|
||||||
|
|
||||||
async def test_invalid_hostname(hass):
|
async def test_invalid_hostname(hass):
|
||||||
"""Test invalid hostname in user_input."""
|
"""Test invalid hostname in user_input."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -118,16 +136,6 @@ async def test_device_exists_abort(hass):
|
|||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_no_data(hass):
|
|
||||||
"""Test we abort if zeroconf provides no data."""
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN, context={"source": SOURCE_ZEROCONF}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
|
||||||
assert result["reason"] == "cannot_connect"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_not_brother_printer_error(hass):
|
async def test_zeroconf_not_brother_printer_error(hass):
|
||||||
"""Test we abort zeroconf flow if printer isn't Brother."""
|
"""Test we abort zeroconf flow if printer isn't Brother."""
|
||||||
with patch(
|
with patch(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user