183 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
"""Adds config flow for Brother Printer."""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
from typing import Any
 | 
						|
 | 
						|
from brother import Brother, SnmpError, UnsupportedModelError
 | 
						|
import voluptuous as vol
 | 
						|
 | 
						|
from homeassistant.components.snmp import async_get_snmp_engine
 | 
						|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
 | 
						|
from homeassistant.const import CONF_HOST, CONF_TYPE
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.exceptions import HomeAssistantError
 | 
						|
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
 | 
						|
from homeassistant.util.network import is_host_valid
 | 
						|
 | 
						|
from .const import DOMAIN, PRINTER_TYPES
 | 
						|
 | 
						|
DATA_SCHEMA = vol.Schema(
 | 
						|
    {
 | 
						|
        vol.Required(CONF_HOST): str,
 | 
						|
        vol.Optional(CONF_TYPE, default="laser"): vol.In(PRINTER_TYPES),
 | 
						|
    }
 | 
						|
)
 | 
						|
RECONFIGURE_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
 | 
						|
 | 
						|
 | 
						|
async def validate_input(
 | 
						|
    hass: HomeAssistant, user_input: dict[str, Any], expected_mac: str | None = None
 | 
						|
) -> tuple[str, str]:
 | 
						|
    """Validate the user input."""
 | 
						|
    if not is_host_valid(user_input[CONF_HOST]):
 | 
						|
        raise InvalidHost
 | 
						|
 | 
						|
    snmp_engine = await async_get_snmp_engine(hass)
 | 
						|
 | 
						|
    brother = await Brother.create(user_input[CONF_HOST], snmp_engine=snmp_engine)
 | 
						|
    await brother.async_update()
 | 
						|
 | 
						|
    if expected_mac is not None and brother.serial.lower() != expected_mac:
 | 
						|
        raise AnotherDevice
 | 
						|
 | 
						|
    return (brother.model, brother.serial)
 | 
						|
 | 
						|
 | 
						|
class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
 | 
						|
    """Handle a config flow for Brother Printer."""
 | 
						|
 | 
						|
    VERSION = 1
 | 
						|
 | 
						|
    def __init__(self) -> None:
 | 
						|
        """Initialize."""
 | 
						|
        self.brother: Brother
 | 
						|
        self.host: str | None = None
 | 
						|
 | 
						|
    async def async_step_user(
 | 
						|
        self, user_input: dict[str, Any] | None = None
 | 
						|
    ) -> ConfigFlowResult:
 | 
						|
        """Handle the initial step."""
 | 
						|
        errors = {}
 | 
						|
 | 
						|
        if user_input is not None:
 | 
						|
            try:
 | 
						|
                model, serial = await validate_input(self.hass, user_input)
 | 
						|
            except InvalidHost:
 | 
						|
                errors[CONF_HOST] = "wrong_host"
 | 
						|
            except (ConnectionError, TimeoutError):
 | 
						|
                errors["base"] = "cannot_connect"
 | 
						|
            except SnmpError:
 | 
						|
                errors["base"] = "snmp_error"
 | 
						|
            except UnsupportedModelError:
 | 
						|
                return self.async_abort(reason="unsupported_model")
 | 
						|
            else:
 | 
						|
                await self.async_set_unique_id(serial.lower())
 | 
						|
                self._abort_if_unique_id_configured()
 | 
						|
 | 
						|
                title = f"{model} {serial}"
 | 
						|
                return self.async_create_entry(title=title, data=user_input)
 | 
						|
 | 
						|
        return self.async_show_form(
 | 
						|
            step_id="user", data_schema=DATA_SCHEMA, errors=errors
 | 
						|
        )
 | 
						|
 | 
						|
    async def async_step_zeroconf(
 | 
						|
        self, discovery_info: ZeroconfServiceInfo
 | 
						|
    ) -> ConfigFlowResult:
 | 
						|
        """Handle zeroconf discovery."""
 | 
						|
        self.host = discovery_info.host
 | 
						|
 | 
						|
        # Do not probe the device if the host is already configured
 | 
						|
        self._async_abort_entries_match({CONF_HOST: self.host})
 | 
						|
 | 
						|
        snmp_engine = await async_get_snmp_engine(self.hass)
 | 
						|
        model = discovery_info.properties.get("product")
 | 
						|
 | 
						|
        try:
 | 
						|
            self.brother = await Brother.create(
 | 
						|
                self.host, snmp_engine=snmp_engine, model=model
 | 
						|
            )
 | 
						|
            await self.brother.async_update()
 | 
						|
        except UnsupportedModelError:
 | 
						|
            return self.async_abort(reason="unsupported_model")
 | 
						|
        except (ConnectionError, SnmpError, TimeoutError):
 | 
						|
            return self.async_abort(reason="cannot_connect")
 | 
						|
 | 
						|
        # Check if already configured
 | 
						|
        await self.async_set_unique_id(self.brother.serial.lower())
 | 
						|
        self._abort_if_unique_id_configured({CONF_HOST: self.host})
 | 
						|
 | 
						|
        self.context.update(
 | 
						|
            {
 | 
						|
                "title_placeholders": {
 | 
						|
                    "serial_number": self.brother.serial,
 | 
						|
                    "model": self.brother.model,
 | 
						|
                }
 | 
						|
            }
 | 
						|
        )
 | 
						|
        return await self.async_step_zeroconf_confirm()
 | 
						|
 | 
						|
    async def async_step_zeroconf_confirm(
 | 
						|
        self, user_input: dict[str, Any] | None = None
 | 
						|
    ) -> ConfigFlowResult:
 | 
						|
        """Handle a flow initiated by zeroconf."""
 | 
						|
        if user_input is not None:
 | 
						|
            title = f"{self.brother.model} {self.brother.serial}"
 | 
						|
            return self.async_create_entry(
 | 
						|
                title=title,
 | 
						|
                data={CONF_HOST: self.host, CONF_TYPE: user_input[CONF_TYPE]},
 | 
						|
            )
 | 
						|
        return self.async_show_form(
 | 
						|
            step_id="zeroconf_confirm",
 | 
						|
            data_schema=vol.Schema(
 | 
						|
                {vol.Optional(CONF_TYPE, default="laser"): vol.In(PRINTER_TYPES)}
 | 
						|
            ),
 | 
						|
            description_placeholders={
 | 
						|
                "serial_number": self.brother.serial,
 | 
						|
                "model": self.brother.model,
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
    async def async_step_reconfigure(
 | 
						|
        self, user_input: dict[str, Any] | None = None
 | 
						|
    ) -> ConfigFlowResult:
 | 
						|
        """Handle a reconfiguration flow initialized by the user."""
 | 
						|
        entry = self._get_reconfigure_entry()
 | 
						|
        errors = {}
 | 
						|
 | 
						|
        if user_input is not None:
 | 
						|
            try:
 | 
						|
                await validate_input(self.hass, user_input, entry.unique_id)
 | 
						|
            except InvalidHost:
 | 
						|
                errors[CONF_HOST] = "wrong_host"
 | 
						|
            except (ConnectionError, TimeoutError):
 | 
						|
                errors["base"] = "cannot_connect"
 | 
						|
            except SnmpError:
 | 
						|
                errors["base"] = "snmp_error"
 | 
						|
            except AnotherDevice:
 | 
						|
                errors["base"] = "another_device"
 | 
						|
            else:
 | 
						|
                return self.async_update_reload_and_abort(
 | 
						|
                    entry,
 | 
						|
                    data_updates={CONF_HOST: user_input[CONF_HOST]},
 | 
						|
                )
 | 
						|
 | 
						|
        return self.async_show_form(
 | 
						|
            step_id="reconfigure",
 | 
						|
            data_schema=self.add_suggested_values_to_schema(
 | 
						|
                data_schema=RECONFIGURE_SCHEMA,
 | 
						|
                suggested_values=entry.data | (user_input or {}),
 | 
						|
            ),
 | 
						|
            description_placeholders={"printer_name": entry.title},
 | 
						|
            errors=errors,
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class InvalidHost(HomeAssistantError):
 | 
						|
    """Error to indicate that hostname/IP address is invalid."""
 | 
						|
 | 
						|
 | 
						|
class AnotherDevice(HomeAssistantError):
 | 
						|
    """Error to indicate that hostname/IP address belongs to another device."""
 |