<commit_before>"""Support for Minut Point."""
import asyncio
import logging

from httpx import ConnectTimeout
from pypoint import PointSession
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    CONF_TOKEN,
    CONF_WEBHOOK_ID,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry
from homeassistant.helpers.dispatcher import (
    async_dispatcher_connect,
    async_dispatcher_send,
)
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.dt import as_local, parse_datetime, utc_from_timestamp

from . import config_flow
from .const import (
    CONF_WEBHOOK_URL,
    DOMAIN,
    EVENT_RECEIVED,
    POINT_DISCOVERY_NEW,
    SCAN_INTERVAL,
    SIGNAL_UPDATE_ENTITY,
    SIGNAL_WEBHOOK,
)

_LOGGER = logging.getLogger(__name__)

DATA_CONFIG_ENTRY_LOCK = "point_config_entry_lock"
CONFIG_ENTRY_IS_SETUP = "point_config_entry_is_setup"

PLATFORMS = ["binary_sensor", "sensor"]

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {
                vol.Required(CONF_CLIENT_ID): cv.string,
                vol.Required(CONF_CLIENT_SECRET): cv.string,
            }
        )
    },
    extra=vol.ALLOW_EXTRA,
)

async def async_setup(hass, config):
    """Set up the Minut Point component."""
    if DOMAIN not in config:
        return True

    conf = config[DOMAIN]

    config_flow.register_flow_implementation(
        hass, DOMAIN, conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET]
    )

    hass.async_create_task(
        hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
        )
    )

    return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Point from a config entry."""

    async def token_saver(token, **kwargs):
        _LOGGER.debug("Saving updated token %s", token)
        hass.config_entries.async_update_entry(
            entry, data={**entry.data, CONF_TOKEN: token}
        )

    session = PointSession(
        hass.helpers.aiohttp_client.async_get_clientsession(),
        entry.data["refresh_args"][CONF_CLIENT_ID],
        entry.data["refresh_args"][CONF_CLIENT_SECRET],
        token=entry.data[CONF_TOKEN],
        token_saver=token_saver,
    )
    try:
        await session.ensure_active_token()
    except ConnectTimeout as err:
        _LOGGER.debug("Connection Timeout")
        raise ConfigEntryNotReady from err
    except Exception:  # pylint: disable=broad-except
        _LOGGER.error("Authentication Error")
        return False

    hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
    hass.data[CONFIG_ENTRY_IS_SETUP] = set()

    await async_setup_webhook(hass, entry, session)
    client = MinutPointClient(hass, entry, session)
    hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: client})
    hass.async_create_task(client.update())

    return True

async def async_setup_webhook(hass: HomeAssistant, entry: ConfigEntry, session):
    """Set up a webhook to handle binary sensor events."""
    if CONF_WEBHOOK_ID not in entry.data:
        webhook_id = hass.components.webhook.async_generate_id()
        webhook_url = hass.components.webhook.async_generate_url(webhook_id)
        _LOGGER.info("Registering new webhook at: %s", webhook_url)

        hass.config_entries.async_update_entry(
            entry,
            data={
                **entry.data,
                CONF_WEBHOOK_ID: webhook_id,
                CONF_WEBHOOK_URL: webhook_url,
            },
        )
    await session.update_webhook(
        entry.data[CONF_WEBHOOK_URL],
        entry.data[CONF_WEBHOOK_ID],
        ["*"],
    )

    hass.components.webhook.async_register(
        DOMAIN, "Point", entry.data[CONF_WEBHOOK_ID], handle_webhook
    )

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
    session = hass.data[DOMAIN].pop(entry.entry_id)
    await session.remove_webhook()

    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
    if not hass.data[DOMAIN]:
        hass.data.pop(DOMAIN)

    return unload_ok

async def handle_webhook(hass, webhook_id, request):
    """Handle webhook callback."""
    try:
        data = await request.json()
        _LOGGER.debug("Webhook %s: %s", webhook_id, data)
    except ValueError:
        return None

    if isinstance(data, dict):
        data["webhook_id"] = webhook_id
        async_dispatcher_send(hass, SIGNAL_WEBHOOK, data, data.get("hook_id"))
    hass.bus.async_fire(EVENT_RECEIVED, data)

class MinutPointClient:
    """Get the latest data and update the states."""

    def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry, session):
        """Initialize the Minut data object."""
        self._known_devices = set()
        self._known_homes = set()
        self._hass = hass
        self._config_entry = config_entry
        self._is_available = True
        self._client = session

        async_track_time_interval(self._hass, self.update, SCAN_INTERVAL)

    async def update(self, *args):
        """Periodically poll the cloud for current state."""
        await self._sync()

    async def _sync(self):
        """Update local list of devices."""
        if not await self._client.update() and self._is_available:
            self._is_available = False
            _LOGGER.warning("Device is unavailable")
            async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
            return

        async def new_device(device_id, platform):
            """Load new device."""
            config_entries_key = f"{platform}.{DOMAIN}"
            async with self._hass.data[DATA_CONFIG_ENTRY_LOCK]:
                if config_entries_key not in self._hass.data[CONFIG_ENTRY_IS_SETUP]:
                    await self._hass.config_entries.async_forward_entry_setup(
                        self._config_entry, platform
                    )
                    self._hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key)

            async_dispatcher_send(
                self._hass, POINT_DISCOVERY_NEW.format(platform, DOMAIN), device_id
            )

        self._is_available = True
        for home_id in self._client.homes:
            if home_id not in self._known_homes:
                await new_device(home_id, "alarm_control_panel")
                self._known_homes.add(home_id)
        for device in self._client.devices:
            if device.device_id not in self._known_devices:
                for platform in PLATFORMS:
                    await new_device(device.device_id, platform)
                self._known_devices.add(device.device_id)
        async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)

    def device(self, device_id):
        """Return device representation."""
        return self._client.device(device_id)

    def is_available(self, device_id):
        """Return device availability."""
        if not self._is_available:
            return False
        return device_id in self._client.device_ids

    async def remove_webhook(self):
        """Remove the session webhook."""
        return await self._client.remove_webhook()

    @property
    def homes(self):
        """Return known homes."""
        return self._client.homes

    async def async_alarm_disarm(self, home_id):
        """Send alarm disarm command."""
        return await self._client.alarm_disarm(home_id)

    async def async_alarm_arm(self, home_id):
        """Send alarm arm command."""
        return await self._client.alarm_arm(home_id)

class MinutPointEntity(Entity):
    """Base Entity used by the sensors."""

    def __init__(self, point_client, device_id, device_class):
        """Initialize the entity."""
        self._async_unsub_dispatcher_connect = None
        self._client = point_client
        self._id = device_id
        self._name = self.device.name
        self._device_class = device_class
        self._updated = utc_from_timestamp(0)
        self._value = None

    def __str__(self):
        """Return string representation of device."""
        return f"MinutPoint {self.name}"

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        _LOGGER.debug("Created device %s", self)
        self._async_unsub_dispatcher_connect = async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
        )
        await self._update_callback()

    async def async_will_remove_from_hass(self):
        """Disconnect dispatcher listener when removed."""
        if self._async_unsub_dispatcher_connect:
            self._async_unsub_dispatcher_connect()

    async def _update_callback(self):
        """Update the value of the sensor."""

    @property
    def available(self):
        """Return true if device is not offline."""
        return self._client.is_available(self.device_id)

    @property
    def device(self):
        """Return the representation of the device."""
        return self._client.device(self.device_id)

    @property
    def device_class(self):
        """Return the device class."""
        return self._device_class

    @property
    def device_id(self):
        """Return the id of the device."""
        return self._id

    @property
    def extra_state_attributes(self):
        """Return status of device."""
        attrs = self.device.device_status
        attrs["last_heard_from"] = as_local(self.last_update).strftime(
            "%Y-%m-%d %H:%M:%S"
        )
        return attrs

    @property
    def device_info(self) -> DeviceInfo:
        """Return a device description for device registry."""
        device = self.device.device
        return DeviceInfo(
            connections={
                (device_registry.CONNECTION_NETWORK_MAC, device["device_mac"])
            },
            identifiers=device["device_id"],
            manufacturer="Minut",
            model=f"Point v{device['hardware_version']}",
            name=device["description"],
            sw_version=device["firmware"]["installed"],
            via_device=(DOMAIN, device["home"]),
        )

    @property
    def name(self):
        """Return the display name of this device."""
        return f"{self._name} {self.device_class.capitalize()}"

    @property
    def is_updated(self):
        """Return true if sensor have been updated."""
        return self.last_update > self._updated

    @property
    def last_update(self):
        """Return the last_update time for the device."""
        last_update = parse_datetime(self.device.last_update)
        return last_update

    @property
    def should_poll(self):
        """No polling needed for point."""
        return False

    @property
    def unique_id(self):
        """Return the unique id of the sensor."""
        return f"point.{self._id}-{self.device_class}"

    @property
    def value(self):
        """Return the sensor value."""
        return self._value
<commit_msg>Fix point device identifiers (#60719)<commit_after>"""Support for Minut Point."""
import asyncio
import logging

from httpx import ConnectTimeout
from pypoint import PointSession
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    CONF_TOKEN,
    CONF_WEBHOOK_ID,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry
from homeassistant.helpers.dispatcher import (
    async_dispatcher_connect,
    async_dispatcher_send,
)
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.dt import as_local, parse_datetime, utc_from_timestamp

from . import config_flow
from .const import (
    CONF_WEBHOOK_URL,
    DOMAIN,
    EVENT_RECEIVED,
    POINT_DISCOVERY_NEW,
    SCAN_INTERVAL,
    SIGNAL_UPDATE_ENTITY,
    SIGNAL_WEBHOOK,
)

_LOGGER = logging.getLogger(__name__)

DATA_CONFIG_ENTRY_LOCK = "point_config_entry_lock"
CONFIG_ENTRY_IS_SETUP = "point_config_entry_is_setup"

PLATFORMS = ["binary_sensor", "sensor"]

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {
                vol.Required(CONF_CLIENT_ID): cv.string,
                vol.Required(CONF_CLIENT_SECRET): cv.string,
            }
        )
    },
    extra=vol.ALLOW_EXTRA,
)

async def async_setup(hass, config):
    """Set up the Minut Point component."""
    if DOMAIN not in config:
        return True

    conf = config[DOMAIN]

    config_flow.register_flow_implementation(
        hass, DOMAIN, conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET]
    )

    hass.async_create_task(
        hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
        )
    )

    return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Point from a config entry."""

    async def token_saver(token, **kwargs):
        _LOGGER.debug("Saving updated token %s", token)
        hass.config_entries.async_update_entry(
            entry, data={**entry.data, CONF_TOKEN: token}
        )

    session = PointSession(
        hass.helpers.aiohttp_client.async_get_clientsession(),
        entry.data["refresh_args"][CONF_CLIENT_ID],
        entry.data["refresh_args"][CONF_CLIENT_SECRET],
        token=entry.data[CONF_TOKEN],
        token_saver=token_saver,
    )
    try:
        await session.ensure_active_token()
    except ConnectTimeout as err:
        _LOGGER.debug("Connection Timeout")
        raise ConfigEntryNotReady from err
    except Exception:  # pylint: disable=broad-except
        _LOGGER.error("Authentication Error")
        return False

    hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
    hass.data[CONFIG_ENTRY_IS_SETUP] = set()

    await async_setup_webhook(hass, entry, session)
    client = MinutPointClient(hass, entry, session)
    hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: client})
    hass.async_create_task(client.update())

    return True

async def async_setup_webhook(hass: HomeAssistant, entry: ConfigEntry, session):
    """Set up a webhook to handle binary sensor events."""
    if CONF_WEBHOOK_ID not in entry.data:
        webhook_id = hass.components.webhook.async_generate_id()
        webhook_url = hass.components.webhook.async_generate_url(webhook_id)
        _LOGGER.info("Registering new webhook at: %s", webhook_url)

        hass.config_entries.async_update_entry(
            entry,
            data={
                **entry.data,
                CONF_WEBHOOK_ID: webhook_id,
                CONF_WEBHOOK_URL: webhook_url,
            },
        )
    await session.update_webhook(
        entry.data[CONF_WEBHOOK_URL],
        entry.data[CONF_WEBHOOK_ID],
        ["*"],
    )

    hass.components.webhook.async_register(
        DOMAIN, "Point", entry.data[CONF_WEBHOOK_ID], handle_webhook
    )

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
    session = hass.data[DOMAIN].pop(entry.entry_id)
    await session.remove_webhook()

    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
    if not hass.data[DOMAIN]:
        hass.data.pop(DOMAIN)

    return unload_ok

async def handle_webhook(hass, webhook_id, request):
    """Handle webhook callback."""
    try:
        data = await request.json()
        _LOGGER.debug("Webhook %s: %s", webhook_id, data)
    except ValueError:
        return None

    if isinstance(data, dict):
        data["webhook_id"] = webhook_id
        async_dispatcher_send(hass, SIGNAL_WEBHOOK, data, data.get("hook_id"))
    hass.bus.async_fire(EVENT_RECEIVED, data)

class MinutPointClient:
    """Get the latest data and update the states."""

    def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry, session):
        """Initialize the Minut data object."""
        self._known_devices = set()
        self._known_homes = set()
        self._hass = hass
        self._config_entry = config_entry
        self._is_available = True
        self._client = session

        async_track_time_interval(self._hass, self.update, SCAN_INTERVAL)

    async def update(self, *args):
        """Periodically poll the cloud for current state."""
        await self._sync()

    async def _sync(self):
        """Update local list of devices."""
        if not await self._client.update() and self._is_available:
            self._is_available = False
            _LOGGER.warning("Device is unavailable")
            async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
            return

        async def new_device(device_id, platform):
            """Load new device."""
            config_entries_key = f"{platform}.{DOMAIN}"
            async with self._hass.data[DATA_CONFIG_ENTRY_LOCK]:
                if config_entries_key not in self._hass.data[CONFIG_ENTRY_IS_SETUP]:
                    await self._hass.config_entries.async_forward_entry_setup(
                        self._config_entry, platform
                    )
                    self._hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key)

            async_dispatcher_send(
                self._hass, POINT_DISCOVERY_NEW.format(platform, DOMAIN), device_id
            )

        self._is_available = True
        for home_id in self._client.homes:
            if home_id not in self._known_homes:
                await new_device(home_id, "alarm_control_panel")
                self._known_homes.add(home_id)
        for device in self._client.devices:
            if device.device_id not in self._known_devices:
                for platform in PLATFORMS:
                    await new_device(device.device_id, platform)
                self._known_devices.add(device.device_id)
        async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)

    def device(self, device_id):
        """Return device representation."""
        return self._client.device(device_id)

    def is_available(self, device_id):
        """Return device availability."""
        if not self._is_available:
            return False
        return device_id in self._client.device_ids

    async def remove_webhook(self):
        """Remove the session webhook."""
        return await self._client.remove_webhook()

    @property
    def homes(self):
        """Return known homes."""
        return self._client.homes

    async def async_alarm_disarm(self, home_id):
        """Send alarm disarm command."""
        return await self._client.alarm_disarm(home_id)

    async def async_alarm_arm(self, home_id):
        """Send alarm arm command."""
        return await self._client.alarm_arm(home_id)

class MinutPointEntity(Entity):
    """Base Entity used by the sensors."""

    def __init__(self, point_client, device_id, device_class):
        """Initialize the entity."""
        self._async_unsub_dispatcher_connect = None
        self._client = point_client
        self._id = device_id
        self._name = self.device.name
        self._device_class = device_class
        self._updated = utc_from_timestamp(0)
        self._value = None

    def __str__(self):
        """Return string representation of device."""
        return f"MinutPoint {self.name}"

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        _LOGGER.debug("Created device %s", self)
        self._async_unsub_dispatcher_connect = async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
        )
        await self._update_callback()

    async def async_will_remove_from_hass(self):
        """Disconnect dispatcher listener when removed."""
        if self._async_unsub_dispatcher_connect:
            self._async_unsub_dispatcher_connect()

    async def _update_callback(self):
        """Update the value of the sensor."""

    @property
    def available(self):
        """Return true if device is not offline."""
        return self._client.is_available(self.device_id)

    @property
    def device(self):
        """Return the representation of the device."""
        return self._client.device(self.device_id)

    @property
    def device_class(self):
        """Return the device class."""
        return self._device_class

    @property
    def device_id(self):
        """Return the id of the device."""
        return self._id

    @property
    def extra_state_attributes(self):
        """Return status of device."""
        attrs = self.device.device_status
        attrs["last_heard_from"] = as_local(self.last_update).strftime(
            "%Y-%m-%d %H:%M:%S"
        )
        return attrs

    @property
    def device_info(self) -> DeviceInfo:
        """Return a device description for device registry."""
        device = self.device.device
        return DeviceInfo(
            connections={
                (device_registry.CONNECTION_NETWORK_MAC, device["device_mac"])
            },
            identifiers={(DOMAIN, device["device_id"])},
            manufacturer="Minut",
            model=f"Point v{device['hardware_version']}",
            name=device["description"],
            sw_version=device["firmware"]["installed"],
            via_device=(DOMAIN, device["home"]),
        )

    @property
    def name(self):
        """Return the display name of this device."""
        return f"{self._name} {self.device_class.capitalize()}"

    @property
    def is_updated(self):
        """Return true if sensor have been updated."""
        return self.last_update > self._updated

    @property
    def last_update(self):
        """Return the last_update time for the device."""
        last_update = parse_datetime(self.device.last_update)
        return last_update

    @property
    def should_poll(self):
        """No polling needed for point."""
        return False

    @property
    def unique_id(self):
        """Return the unique id of the sensor."""
        return f"point.{self._id}-{self.device_class}"

    @property
    def value(self):
        """Return the sensor value."""
        return self._value
<|endoftext|>


<commit_before>"""
Support for Harmony Hub devices.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.harmony/
"""
import asyncio
import json
import logging

import voluptuous as vol

from homeassistant.components import remote
from homeassistant.components.remote import (
    ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_NUM_REPEATS,
    DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA
)
from homeassistant.const import (
    ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP
)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
from homeassistant.util import slugify

REQUIREMENTS = ['aioharmony==0.1.2']

_LOGGER = logging.getLogger(__name__)

ATTR_CURRENT_ACTIVITY = 'current_activity'

DEFAULT_PORT = 8088
DEVICES = []
CONF_DEVICE_CACHE = 'harmony_device_cache'

SERVICE_SYNC = 'harmony_sync'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(ATTR_ACTIVITY): cv.string,
    vol.Required(CONF_NAME): cv.string,
    vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS):
        vol.Coerce(float),
    vol.Optional(CONF_HOST): cv.string,
    vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})

HARMONY_SYNC_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

async def async_setup_platform(hass, config, async_add_entities,
                               discovery_info=None):
    """Set up the Harmony platform."""
    activity = None

    if CONF_DEVICE_CACHE not in hass.data:
        hass.data[CONF_DEVICE_CACHE] = []

    if discovery_info:
        # Find the discovered device in the list of user configurations
        override = next((c for c in hass.data[CONF_DEVICE_CACHE]
                         if c.get(CONF_NAME) == discovery_info.get(CONF_NAME)),
                        None)

        port = DEFAULT_PORT
        delay_secs = DEFAULT_DELAY_SECS
        if override is not None:
            activity = override.get(ATTR_ACTIVITY)
            delay_secs = override.get(ATTR_DELAY_SECS)
            port = override.get(CONF_PORT, DEFAULT_PORT)

        host = (
            discovery_info.get(CONF_NAME),
            discovery_info.get(CONF_HOST),
            port)

        # Ignore hub name when checking if this hub is known - ip and port only
        if host[1:] in ((h.host, h.port) for h in DEVICES):
            _LOGGER.debug("Discovered host already known: %s", host)
            return
    elif CONF_HOST in config:
        host = (
            config.get(CONF_NAME),
            config.get(CONF_HOST),
            config.get(CONF_PORT),
        )
        activity = config.get(ATTR_ACTIVITY)
        delay_secs = config.get(ATTR_DELAY_SECS)
    else:
        hass.data[CONF_DEVICE_CACHE].append(config)
        return

    name, address, port = host
    _LOGGER.info("Loading Harmony Platform: %s at %s:%s, startup activity: %s",
                 name, address, port, activity)

    harmony_conf_file = hass.config.path(
        '{}{}{}'.format('harmony_', slugify(name), '.conf'))
    try:
        device = HarmonyRemote(
            name, address, port, activity, harmony_conf_file, delay_secs)
        DEVICES.append(device)
        async_add_entities([device])
        register_services(hass)
    except (ValueError, AttributeError):
        raise PlatformNotReady

def register_services(hass):
    """Register all services for harmony devices."""
    hass.services.async_register(
        DOMAIN, SERVICE_SYNC, _sync_service,
        schema=HARMONY_SYNC_SCHEMA)

async def _apply_service(service, service_func, *service_func_args):
    """Handle services to apply."""
    entity_ids = service.data.get('entity_id')

    if entity_ids:
        _devices = [device for device in DEVICES
                    if device.entity_id in entity_ids]
    else:
        _devices = DEVICES

    for device in _devices:
        await service_func(device, *service_func_args)

async def _sync_service(service):
    await _apply_service(service, HarmonyRemote.sync)

class HarmonyRemote(remote.RemoteDevice):
    """Remote representation used to control a Harmony device."""

    def __init__(self, name, host, port, activity, out_path, delay_secs):
        """Initialize HarmonyRemote class."""
        from aioharmony.harmonyapi import (
            HarmonyAPI as HarmonyClient, ClientCallbackType
        )

        _LOGGER.debug("%s: Device init started", name)
        self._name = name
        self.host = host
        self.port = port
        self._state = None
        self._current_activity = None
        self._default_activity = activity
        self._client = HarmonyClient(
            ip_address=host,
            callbacks=ClientCallbackType(
                new_activity=self.new_activity,
                config_updated=self.new_config,
                connect=self.got_connected,
                disconnect=self.got_disconnected
            )
        )
        self._config_path = out_path
        self._delay_secs = delay_secs
        self._available = False

    async def async_added_to_hass(self):
        """Complete the initialization."""
        _LOGGER.debug("%s: Harmony Hub added", self._name)
        import aioharmony.exceptions as aioexc

        async def shutdown(_):
            """Close connection on shutdown."""
            _LOGGER.debug("%s: Closing Harmony Hub", self._name)
            try:
                await self._client.close()
            except aioexc.TimeOut:
                _LOGGER.warning("%s: Disconnect timed-out", self._name)

        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)

        _LOGGER.debug("%s: Connecting", self._name)
        try:
            await self._client.connect()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Connection timed-out", self._name)
        else:
            # Set initial state
            self.new_activity(self._client.current_activity)

    @property
    def name(self):
        """Return the Harmony device's name."""
        return self._name

    @property
    def should_poll(self):
        """Return the fact that we should not be polled."""
        return False

    @property
    def device_state_attributes(self):
        """Add platform specific attributes."""
        return {ATTR_CURRENT_ACTIVITY: self._current_activity}

    @property
    def is_on(self):
        """Return False if PowerOff is the current activity, otherwise True."""
        return self._current_activity not in [None, 'PowerOff']

    @property
    def available(self):
        """Return True if connected to Hub, otherwise False."""
        return self._available

    def new_activity(self, activity_info: tuple) -> None:
        """Call for updating the current activity."""
        activity_id, activity_name = activity_info
        _LOGGER.debug("%s: activity reported as: %s", self._name,
                      activity_name)
        self._current_activity = activity_name
        self._state = bool(activity_id != -1)
        self._available = True
        self.async_schedule_update_ha_state()

    async def new_config(self, _=None):
        """Call for updating the current activity."""
        _LOGGER.debug("%s: configuration has been updated", self._name)
        self.new_activity(self._client.current_activity)
        await self.hass.async_add_executor_job(self.write_config_file)

    async def got_connected(self, _=None):
        """Notification that we're connected to the HUB."""
        _LOGGER.debug("%s: connected to the HUB.", self._name)
        if not self._available:
            # We were disconnected before.
            await self.new_config()

    async def got_disconnected(self, _=None):
        """Notification that we're disconnected from the HUB."""
        _LOGGER.debug("%s: disconnected from the HUB.", self._name)
        self._available = False
        # We're going to wait for 10 seconds before announcing we're
        # unavailable, this to allow a reconnection to happen.
        await asyncio.sleep(10)

        if not self._available:
            # Still disconnected. Let the state engine know.
            self.async_schedule_update_ha_state()

    async def async_turn_on(self, **kwargs):
        """Start an activity from the Harmony device."""
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Turn On", self.name)

        activity = kwargs.get(ATTR_ACTIVITY, self._default_activity)

        if activity:
            activity_id = None
            if activity.isdigit() or activity == '-1':
                _LOGGER.debug("%s: Activity is numeric", self.name)
                if self._client.get_activity_name(int(activity)):
                    activity_id = activity

            if activity_id is None:
                _LOGGER.debug("%s: Find activity ID based on name", self.name)
                activity_id = self._client.get_activity_id(
                    str(activity).strip())

            if activity_id is None:
                _LOGGER.error("%s: Activity %s is invalid",
                              self.name, activity)
                return

            try:
                await self._client.start_activity(activity_id)
            except aioexc.TimeOut:
                _LOGGER.error("%s: Starting activity %s timed-out",
                              self.name,
                              activity)
        else:
            _LOGGER.error("%s: No activity specified with turn_on service",
                          self.name)

    async def async_turn_off(self, **kwargs):
        """Start the PowerOff activity."""
        import aioharmony.exceptions as aioexc
        _LOGGER.debug("%s: Turn Off", self.name)
        try:
            await self._client.power_off()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Powering off timed-out", self.name)

    # pylint: disable=arguments-differ
    async def async_send_command(self, command, **kwargs):
        """Send a list of commands to one device."""
        from aioharmony.harmonyapi import SendCommandDevice
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Send Command", self.name)
        device = kwargs.get(ATTR_DEVICE)
        if device is None:
            _LOGGER.error("%s: Missing required argument: device", self.name)
            return

        device_id = None
        if device.isdigit():
            _LOGGER.debug("%s: Device %s is numeric",
                          self.name, device)
            if self._client.get_device_name(int(device)):
                device_id = device

        if device_id is None:
            _LOGGER.debug("%s: Find device ID %s based on device name",
                          self.name, device)
            device_id = self._client.get_device_id(str(device).strip())

        if device_id is None:
            _LOGGER.error("%s: Device %s is invalid", self.name, device)
            return

        num_repeats = kwargs.get(ATTR_NUM_REPEATS)
        delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs)

        # Creating list of commands to send.
        snd_cmnd_list = []
        for _ in range(num_repeats):
            for single_command in command:
                send_command = SendCommandDevice(
                    device=device,
                    command=single_command,
                    delay=0
                )
                snd_cmnd_list.append(send_command)
                if delay_secs > 0:
                    snd_cmnd_list.append(float(delay_secs))

        _LOGGER.debug("%s: Sending commands", self.name)
        try:
            result_list = await self._client.send_commands(snd_cmnd_list)
        except aioexc.TimeOut:
            _LOGGER.error("%s: Sending commands timed-out", self.name)
            return

        for result in result_list:
            _LOGGER.error("Sending command %s to device %s failed with code "
                          "%s: %s",
                          result.command.command,
                          result.command.device,
                          result.command.code,
                          result.command.msg
                          )

    async def sync(self):
        """Sync the Harmony device with the web service."""
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name)
        try:
            await self._client.sync()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out",
                          self.name)
        else:
            await self.hass.async_add_executor_job(self.write_config_file)

    def write_config_file(self):
        """Write Harmony configuration file."""
        _LOGGER.debug("%s: Writing hub config to file: %s",
                      self.name,
                      self._config_path)
        if self._client.config is None:
            _LOGGER.warning("%s: No configuration received from hub",
                            self.name)
            return

        try:
            with open(self._config_path, 'w+', encoding='utf-8') as file_out:
                json.dump(self._client.json_config, file_out,
                          sort_keys=True, indent=4)
        except IOError as exc:
            _LOGGER.error("%s: Unable to write HUB configuration to %s: %s",
                          self.name, self._config_path, exc)
<commit_msg>Add service change_channel to Harmony component (#19649)<commit_after>"""
Support for Harmony Hub devices.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.harmony/
"""
import asyncio
import json
import logging

import voluptuous as vol

from homeassistant.components import remote
from homeassistant.components.remote import (
    ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_NUM_REPEATS,
    DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA
)
from homeassistant.const import (
    ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP
)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
from homeassistant.util import slugify

REQUIREMENTS = ['aioharmony==0.1.2']

_LOGGER = logging.getLogger(__name__)

ATTR_CHANNEL = 'channel'
ATTR_CURRENT_ACTIVITY = 'current_activity'

DEFAULT_PORT = 8088
DEVICES = []
CONF_DEVICE_CACHE = 'harmony_device_cache'

SERVICE_SYNC = 'harmony_sync'
SERVICE_CHANGE_CHANNEL = 'harmony_change_channel'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(ATTR_ACTIVITY): cv.string,
    vol.Required(CONF_NAME): cv.string,
    vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS):
        vol.Coerce(float),
    vol.Optional(CONF_HOST): cv.string,
    vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})

HARMONY_SYNC_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

HARMONY_CHANGE_CHANNEL_SCHEMA = vol.Schema({
    vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Required(ATTR_CHANNEL): cv.positive_int
})

async def async_setup_platform(hass, config, async_add_entities,
                               discovery_info=None):
    """Set up the Harmony platform."""
    activity = None

    if CONF_DEVICE_CACHE not in hass.data:
        hass.data[CONF_DEVICE_CACHE] = []

    if discovery_info:
        # Find the discovered device in the list of user configurations
        override = next((c for c in hass.data[CONF_DEVICE_CACHE]
                         if c.get(CONF_NAME) == discovery_info.get(CONF_NAME)),
                        None)

        port = DEFAULT_PORT
        delay_secs = DEFAULT_DELAY_SECS
        if override is not None:
            activity = override.get(ATTR_ACTIVITY)
            delay_secs = override.get(ATTR_DELAY_SECS)
            port = override.get(CONF_PORT, DEFAULT_PORT)

        host = (
            discovery_info.get(CONF_NAME),
            discovery_info.get(CONF_HOST),
            port)

        # Ignore hub name when checking if this hub is known - ip and port only
        if host[1:] in ((h.host, h.port) for h in DEVICES):
            _LOGGER.debug("Discovered host already known: %s", host)
            return
    elif CONF_HOST in config:
        host = (
            config.get(CONF_NAME),
            config.get(CONF_HOST),
            config.get(CONF_PORT),
        )
        activity = config.get(ATTR_ACTIVITY)
        delay_secs = config.get(ATTR_DELAY_SECS)
    else:
        hass.data[CONF_DEVICE_CACHE].append(config)
        return

    name, address, port = host
    _LOGGER.info("Loading Harmony Platform: %s at %s:%s, startup activity: %s",
                 name, address, port, activity)

    harmony_conf_file = hass.config.path(
        '{}{}{}'.format('harmony_', slugify(name), '.conf'))
    try:
        device = HarmonyRemote(
            name, address, port, activity, harmony_conf_file, delay_secs)
        DEVICES.append(device)
        async_add_entities([device])
        register_services(hass)
    except (ValueError, AttributeError):
        raise PlatformNotReady

def register_services(hass):
    """Register all services for harmony devices."""
    hass.services.async_register(
        DOMAIN, SERVICE_SYNC, _sync_service,
        schema=HARMONY_SYNC_SCHEMA)

    hass.services.async_register(
        DOMAIN, SERVICE_CHANGE_CHANNEL, _change_channel_service,
        schema=HARMONY_CHANGE_CHANNEL_SCHEMA)

async def _apply_service(service, service_func, *service_func_args):
    """Handle services to apply."""
    entity_ids = service.data.get('entity_id')

    if entity_ids:
        _devices = [device for device in DEVICES
                    if device.entity_id in entity_ids]
    else:
        _devices = DEVICES

    for device in _devices:
        await service_func(device, *service_func_args)

async def _sync_service(service):
    await _apply_service(service, HarmonyRemote.sync)

async def _change_channel_service(service):
    channel = service.data.get(ATTR_CHANNEL)
    await _apply_service(service, HarmonyRemote.change_channel, channel)

class HarmonyRemote(remote.RemoteDevice):
    """Remote representation used to control a Harmony device."""

    def __init__(self, name, host, port, activity, out_path, delay_secs):
        """Initialize HarmonyRemote class."""
        from aioharmony.harmonyapi import (
            HarmonyAPI as HarmonyClient, ClientCallbackType
        )

        _LOGGER.debug("%s: Device init started", name)
        self._name = name
        self.host = host
        self.port = port
        self._state = None
        self._current_activity = None
        self._default_activity = activity
        self._client = HarmonyClient(
            ip_address=host,
            callbacks=ClientCallbackType(
                new_activity=self.new_activity,
                config_updated=self.new_config,
                connect=self.got_connected,
                disconnect=self.got_disconnected
            )
        )
        self._config_path = out_path
        self._delay_secs = delay_secs
        self._available = False

    async def async_added_to_hass(self):
        """Complete the initialization."""
        _LOGGER.debug("%s: Harmony Hub added", self._name)
        import aioharmony.exceptions as aioexc

        async def shutdown(_):
            """Close connection on shutdown."""
            _LOGGER.debug("%s: Closing Harmony Hub", self._name)
            try:
                await self._client.close()
            except aioexc.TimeOut:
                _LOGGER.warning("%s: Disconnect timed-out", self._name)

        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)

        _LOGGER.debug("%s: Connecting", self._name)
        try:
            await self._client.connect()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Connection timed-out", self._name)
        else:
            # Set initial state
            self.new_activity(self._client.current_activity)

    @property
    def name(self):
        """Return the Harmony device's name."""
        return self._name

    @property
    def should_poll(self):
        """Return the fact that we should not be polled."""
        return False

    @property
    def device_state_attributes(self):
        """Add platform specific attributes."""
        return {ATTR_CURRENT_ACTIVITY: self._current_activity}

    @property
    def is_on(self):
        """Return False if PowerOff is the current activity, otherwise True."""
        return self._current_activity not in [None, 'PowerOff']

    @property
    def available(self):
        """Return True if connected to Hub, otherwise False."""
        return self._available

    def new_activity(self, activity_info: tuple) -> None:
        """Call for updating the current activity."""
        activity_id, activity_name = activity_info
        _LOGGER.debug("%s: activity reported as: %s", self._name,
                      activity_name)
        self._current_activity = activity_name
        self._state = bool(activity_id != -1)
        self._available = True
        self.async_schedule_update_ha_state()

    async def new_config(self, _=None):
        """Call for updating the current activity."""
        _LOGGER.debug("%s: configuration has been updated", self._name)
        self.new_activity(self._client.current_activity)
        await self.hass.async_add_executor_job(self.write_config_file)

    async def got_connected(self, _=None):
        """Notification that we're connected to the HUB."""
        _LOGGER.debug("%s: connected to the HUB.", self._name)
        if not self._available:
            # We were disconnected before.
            await self.new_config()

    async def got_disconnected(self, _=None):
        """Notification that we're disconnected from the HUB."""
        _LOGGER.debug("%s: disconnected from the HUB.", self._name)
        self._available = False
        # We're going to wait for 10 seconds before announcing we're
        # unavailable, this to allow a reconnection to happen.
        await asyncio.sleep(10)

        if not self._available:
            # Still disconnected. Let the state engine know.
            self.async_schedule_update_ha_state()

    async def async_turn_on(self, **kwargs):
        """Start an activity from the Harmony device."""
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Turn On", self.name)

        activity = kwargs.get(ATTR_ACTIVITY, self._default_activity)

        if activity:
            activity_id = None
            if activity.isdigit() or activity == '-1':
                _LOGGER.debug("%s: Activity is numeric", self.name)
                if self._client.get_activity_name(int(activity)):
                    activity_id = activity

            if activity_id is None:
                _LOGGER.debug("%s: Find activity ID based on name", self.name)
                activity_id = self._client.get_activity_id(
                    str(activity).strip())

            if activity_id is None:
                _LOGGER.error("%s: Activity %s is invalid",
                              self.name, activity)
                return

            try:
                await self._client.start_activity(activity_id)
            except aioexc.TimeOut:
                _LOGGER.error("%s: Starting activity %s timed-out",
                              self.name,
                              activity)
        else:
            _LOGGER.error("%s: No activity specified with turn_on service",
                          self.name)

    async def async_turn_off(self, **kwargs):
        """Start the PowerOff activity."""
        import aioharmony.exceptions as aioexc
        _LOGGER.debug("%s: Turn Off", self.name)
        try:
            await self._client.power_off()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Powering off timed-out", self.name)

    # pylint: disable=arguments-differ
    async def async_send_command(self, command, **kwargs):
        """Send a list of commands to one device."""
        from aioharmony.harmonyapi import SendCommandDevice
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Send Command", self.name)
        device = kwargs.get(ATTR_DEVICE)
        if device is None:
            _LOGGER.error("%s: Missing required argument: device", self.name)
            return

        device_id = None
        if device.isdigit():
            _LOGGER.debug("%s: Device %s is numeric",
                          self.name, device)
            if self._client.get_device_name(int(device)):
                device_id = device

        if device_id is None:
            _LOGGER.debug("%s: Find device ID %s based on device name",
                          self.name, device)
            device_id = self._client.get_device_id(str(device).strip())

        if device_id is None:
            _LOGGER.error("%s: Device %s is invalid", self.name, device)
            return

        num_repeats = kwargs.get(ATTR_NUM_REPEATS)
        delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs)

        # Creating list of commands to send.
        snd_cmnd_list = []
        for _ in range(num_repeats):
            for single_command in command:
                send_command = SendCommandDevice(
                    device=device,
                    command=single_command,
                    delay=0
                )
                snd_cmnd_list.append(send_command)
                if delay_secs > 0:
                    snd_cmnd_list.append(float(delay_secs))

        _LOGGER.debug("%s: Sending commands", self.name)
        try:
            result_list = await self._client.send_commands(snd_cmnd_list)
        except aioexc.TimeOut:
            _LOGGER.error("%s: Sending commands timed-out", self.name)
            return

        for result in result_list:
            _LOGGER.error("Sending command %s to device %s failed with code "
                          "%s: %s",
                          result.command.command,
                          result.command.device,
                          result.command.code,
                          result.command.msg
                          )

    async def change_channel(self, channel):
        """Change the channel using Harmony remote."""
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Changing channel to %s",
                      self.name, channel)
        try:
            await self._client.change_channel(channel)
        except aioexc.TimeOut:
            _LOGGER.error("%s: Changing channel to %s timed-out",
                          self.name,
                          channel)

    async def sync(self):
        """Sync the Harmony device with the web service."""
        import aioharmony.exceptions as aioexc

        _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name)
        try:
            await self._client.sync()
        except aioexc.TimeOut:
            _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out",
                          self.name)
        else:
            await self.hass.async_add_executor_job(self.write_config_file)

    def write_config_file(self):
        """Write Harmony configuration file."""
        _LOGGER.debug("%s: Writing hub config to file: %s",
                      self.name,
                      self._config_path)
        if self._client.config is None:
            _LOGGER.warning("%s: No configuration received from hub",
                            self.name)
            return

        try:
            with open(self._config_path, 'w+', encoding='utf-8') as file_out:
                json.dump(self._client.json_config, file_out,
                          sort_keys=True, indent=4)
        except IOError as exc:
            _LOGGER.error("%s: Unable to write HUB configuration to %s: %s",
                          self.name, self._config_path, exc)
<|endoftext|>


<commit_before>"""
Support for the NetAtmo Weather Service.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.netatmo/
"""
import logging
from time import time
import threading

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    TEMP_CELSIUS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
    STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

CONF_MODULES = 'modules'
CONF_STATION = 'station'

DEPENDENCIES = ['netatmo']

# This is the NetAtmo data upload interval in seconds
NETATMO_UPDATE_INTERVAL = 600

SENSOR_TYPES = {
    'temperature': ['Temperature', TEMP_CELSIUS, None,
                    DEVICE_CLASS_TEMPERATURE],
    'co2': ['CO2', 'ppm', 'mdi:cloud', None],
    'pressure': ['Pressure', 'mbar', 'mdi:gauge', None],
    'noise': ['Noise', 'dB', 'mdi:volume-high', None],
    'humidity': ['Humidity', '%', None, DEVICE_CLASS_HUMIDITY],
    'rain': ['Rain', 'mm', 'mdi:weather-rainy', None],
    'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy', None],
    'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy', None],
    'battery_vp': ['Battery', '', 'mdi:battery', None],
    'battery_lvl': ['Battery_lvl', '', 'mdi:battery', None],
    'min_temp': ['Min Temp.', TEMP_CELSIUS, 'mdi:thermometer', None],
    'max_temp': ['Max Temp.', TEMP_CELSIUS, 'mdi:thermometer', None],
    'windangle': ['Angle', '', 'mdi:compass', None],
    'windangle_value': ['Angle Value', 'º', 'mdi:compass', None],
    'windstrength': ['Strength', 'km/h', 'mdi:weather-windy', None],
    'gustangle': ['Gust Angle', '', 'mdi:compass', None],
    'gustangle_value': ['Gust Angle Value', 'º', 'mdi:compass', None],
    'guststrength': ['Gust Strength', 'km/h', 'mdi:weather-windy', None],
    'rf_status': ['Radio', '', 'mdi:signal', None],
    'rf_status_lvl': ['Radio_lvl', '', 'mdi:signal', None],
    'wifi_status': ['Wifi', '', 'mdi:wifi', None],
    'wifi_status_lvl': ['Wifi_lvl', 'dBm', 'mdi:wifi', None],
    'lastupdated': ['Last Updated', 's', 'mdi:timer', None],
}

MODULE_SCHEMA = vol.Schema({
    vol.Required(cv.string):
        vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_STATION): cv.string,
    vol.Optional(CONF_MODULES): MODULE_SCHEMA,
})

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the available Netatmo weather sensors."""
    netatmo = hass.components.netatmo
    data = NetAtmoData(netatmo.NETATMO_AUTH, config.get(CONF_STATION, None))

    dev = []
    import pyatmo
    try:
        if CONF_MODULES in config:
            # Iterate each module
            for module_name, monitored_conditions in\
                    config[CONF_MODULES].items():
                # Test if module exists
                if module_name not in data.get_module_names():
                    _LOGGER.error('Module name: "%s" not found', module_name)
                    continue
                # Only create sensors for monitored properties
                for variable in monitored_conditions:
                    dev.append(NetAtmoSensor(data, module_name, variable))
        else:
            for module_name in data.get_module_names():
                for variable in\
                        data.station_data.monitoredConditions(module_name):
                    if variable in SENSOR_TYPES.keys():
                        dev.append(NetAtmoSensor(data, module_name, variable))
                    else:
                        _LOGGER.warning("Ignoring unknown var %s for mod %s",
                                        variable, module_name)
    except pyatmo.NoDevice:
        return None

    add_devices(dev, True)

class NetAtmoSensor(Entity):
    """Implementation of a Netatmo sensor."""

    def __init__(self, netatmo_data, module_name, sensor_type):
        """Initialize the sensor."""
        self._name = 'Netatmo {} {}'.format(module_name,
                                            SENSOR_TYPES[sensor_type][0])
        self.netatmo_data = netatmo_data
        self.module_name = module_name
        self.type = sensor_type
        self._state = None
        self._device_class = SENSOR_TYPES[self.type][3]
        self._icon = SENSOR_TYPES[self.type][2]
        self._unit_of_measurement = SENSOR_TYPES[self.type][1]
        module_id = self.netatmo_data.\
            station_data.moduleByName(module=module_name)['_id']
        self.module_id = module_id[1]

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return self._icon

    @property
    def device_class(self):
        """Return the device class of the sensor."""
        return self._device_class

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity, if any."""
        return self._unit_of_measurement

    def update(self):
        """Get the latest data from NetAtmo API and updates the states."""
        self.netatmo_data.update()
        data = self.netatmo_data.data.get(self.module_name)

        if data is None:
            _LOGGER.warning("No data found for %s", self.module_name)
            self._state = STATE_UNKNOWN
            return

        if self.type == 'temperature':
            self._state = round(data['Temperature'], 1)
        elif self.type == 'humidity':
            self._state = data['Humidity']
        elif self.type == 'rain':
            self._state = data['Rain']
        elif self.type == 'sum_rain_1':
            self._state = data['sum_rain_1']
        elif self.type == 'sum_rain_24':
            self._state = data['sum_rain_24']
        elif self.type == 'noise':
            self._state = data['Noise']
        elif self.type == 'co2':
            self._state = data['CO2']
        elif self.type == 'pressure':
            self._state = round(data['Pressure'], 1)
        elif self.type == 'battery_lvl':
            self._state = data['battery_vp']
        elif self.type == 'battery_vp' and self.module_id == '6':
            if data['battery_vp'] >= 5590:
                self._state = "Full"
            elif data['battery_vp'] >= 5180:
                self._state = "High"
            elif data['battery_vp'] >= 4770:
                self._state = "Medium"
            elif data['battery_vp'] >= 4360:
                self._state = "Low"
            elif data['battery_vp'] < 4360:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '5':
            if data['battery_vp'] >= 5500:
                self._state = "Full"
            elif data['battery_vp'] >= 5000:
                self._state = "High"
            elif data['battery_vp'] >= 4500:
                self._state = "Medium"
            elif data['battery_vp'] >= 4000:
                self._state = "Low"
            elif data['battery_vp'] < 4000:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '3':
            if data['battery_vp'] >= 5640:
                self._state = "Full"
            elif data['battery_vp'] >= 5280:
                self._state = "High"
            elif data['battery_vp'] >= 4920:
                self._state = "Medium"
            elif data['battery_vp'] >= 4560:
                self._state = "Low"
            elif data['battery_vp'] < 4560:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '2':
            if data['battery_vp'] >= 5500:
                self._state = "Full"
            elif data['battery_vp'] >= 5000:
                self._state = "High"
            elif data['battery_vp'] >= 4500:
                self._state = "Medium"
            elif data['battery_vp'] >= 4000:
                self._state = "Low"
            elif data['battery_vp'] < 4000:
                self._state = "Very Low"
        elif self.type == 'min_temp':
            self._state = data['min_temp']
        elif self.type == 'max_temp':
            self._state = data['max_temp']
        elif self.type == 'windangle_value':
            self._state = data['WindAngle']
        elif self.type == 'windangle':
            if data['WindAngle'] >= 330:
                self._state = "N (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 300:
                self._state = "NW (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 240:
                self._state = "W (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 210:
                self._state = "SW (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 150:
                self._state = "S (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 120:
                self._state = "SE (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 60:
                self._state = "E (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 30:
                self._state = "NE (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 0:
                self._state = "N (%d\xb0)" % data['WindAngle']
        elif self.type == 'windstrength':
            self._state = data['WindStrength']
        elif self.type == 'gustangle_value':
            self._state = data['GustAngle']
        elif self.type == 'gustangle':
            if data['GustAngle'] >= 330:
                self._state = "N (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 300:
                self._state = "NW (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 240:
                self._state = "W (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 210:
                self._state = "SW (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 150:
                self._state = "S (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 120:
                self._state = "SE (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 60:
                self._state = "E (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 30:
                self._state = "NE (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 0:
                self._state = "N (%d\xb0)" % data['GustAngle']
        elif self.type == 'guststrength':
            self._state = data['GustStrength']
        elif self.type == 'rf_status_lvl':
            self._state = data['rf_status']
        elif self.type == 'rf_status':
            if data['rf_status'] >= 90:
                self._state = "Low"
            elif data['rf_status'] >= 76:
                self._state = "Medium"
            elif data['rf_status'] >= 60:
                self._state = "High"
            elif data['rf_status'] <= 59:
                self._state = "Full"
        elif self.type == 'wifi_status_lvl':
            self._state = data['wifi_status']
        elif self.type == 'wifi_status':
            if data['wifi_status'] >= 86:
                self._state = "Low"
            elif data['wifi_status'] >= 71:
                self._state = "Medium"
            elif data['wifi_status'] >= 56:
                self._state = "High"
            elif data['wifi_status'] <= 55:
                self._state = "Full"
        elif self.type == 'lastupdated':
            self._state = int(time() - data['When'])

class NetAtmoData(object):
    """Get the latest data from NetAtmo."""

    def __init__(self, auth, station):
        """Initialize the data object."""
        self.auth = auth
        self.data = None
        self.station_data = None
        self.station = station
        self._next_update = time()
        self._update_in_progress = threading.Lock()

    def get_module_names(self):
        """Return all module available on the API as a list."""
        self.update()
        return self.data.keys()

    def update(self):
        """Call the Netatmo API to update the data.

        This method is not throttled by the builtin Throttle decorator
        but with a custom logic, which takes into account the time
        of the last update from the cloud.
        """
        if time() < self._next_update or \
                not self._update_in_progress.acquire(False):
            return

        try:
            import pyatmo
            self.station_data = pyatmo.WeatherStationData(self.auth)

            if self.station is not None:
                self.data = self.station_data.lastData(
                    station=self.station, exclude=3600)
            else:
                self.data = self.station_data.lastData(exclude=3600)

            newinterval = 0
            for module in self.data:
                if 'When' in self.data[module]:
                    newinterval = self.data[module]['When']
                    break
            if newinterval:
                # Try and estimate when fresh data will be available
                newinterval += NETATMO_UPDATE_INTERVAL - time()
                if newinterval > NETATMO_UPDATE_INTERVAL - 30:
                    newinterval = NETATMO_UPDATE_INTERVAL
                else:
                    if newinterval < NETATMO_UPDATE_INTERVAL / 2:
                        # Never hammer the NetAtmo API more than
                        # twice per update interval
                        newinterval = NETATMO_UPDATE_INTERVAL / 2
                    _LOGGER.warning(
                        "NetAtmo refresh interval reset to %d seconds",
                        newinterval)
            else:
                # Last update time not found, fall back to default value
                newinterval = NETATMO_UPDATE_INTERVAL

            self._next_update = time() + newinterval
        finally:
            self._update_in_progress.release()
<commit_msg>Remove relative time from state machine (#15560)<commit_after>"""
Support for the NetAtmo Weather Service.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.netatmo/
"""
import logging
from time import time
import threading

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    TEMP_CELSIUS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE,
    STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

CONF_MODULES = 'modules'
CONF_STATION = 'station'

DEPENDENCIES = ['netatmo']

# This is the NetAtmo data upload interval in seconds
NETATMO_UPDATE_INTERVAL = 600

SENSOR_TYPES = {
    'temperature': ['Temperature', TEMP_CELSIUS, None,
                    DEVICE_CLASS_TEMPERATURE],
    'co2': ['CO2', 'ppm', 'mdi:cloud', None],
    'pressure': ['Pressure', 'mbar', 'mdi:gauge', None],
    'noise': ['Noise', 'dB', 'mdi:volume-high', None],
    'humidity': ['Humidity', '%', None, DEVICE_CLASS_HUMIDITY],
    'rain': ['Rain', 'mm', 'mdi:weather-rainy', None],
    'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy', None],
    'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy', None],
    'battery_vp': ['Battery', '', 'mdi:battery', None],
    'battery_lvl': ['Battery_lvl', '', 'mdi:battery', None],
    'min_temp': ['Min Temp.', TEMP_CELSIUS, 'mdi:thermometer', None],
    'max_temp': ['Max Temp.', TEMP_CELSIUS, 'mdi:thermometer', None],
    'windangle': ['Angle', '', 'mdi:compass', None],
    'windangle_value': ['Angle Value', 'º', 'mdi:compass', None],
    'windstrength': ['Strength', 'km/h', 'mdi:weather-windy', None],
    'gustangle': ['Gust Angle', '', 'mdi:compass', None],
    'gustangle_value': ['Gust Angle Value', 'º', 'mdi:compass', None],
    'guststrength': ['Gust Strength', 'km/h', 'mdi:weather-windy', None],
    'rf_status': ['Radio', '', 'mdi:signal', None],
    'rf_status_lvl': ['Radio_lvl', '', 'mdi:signal', None],
    'wifi_status': ['Wifi', '', 'mdi:wifi', None],
    'wifi_status_lvl': ['Wifi_lvl', 'dBm', 'mdi:wifi', None],
}

MODULE_SCHEMA = vol.Schema({
    vol.Required(cv.string):
        vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_STATION): cv.string,
    vol.Optional(CONF_MODULES): MODULE_SCHEMA,
})

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the available Netatmo weather sensors."""
    netatmo = hass.components.netatmo
    data = NetAtmoData(netatmo.NETATMO_AUTH, config.get(CONF_STATION, None))

    dev = []
    import pyatmo
    try:
        if CONF_MODULES in config:
            # Iterate each module
            for module_name, monitored_conditions in\
                    config[CONF_MODULES].items():
                # Test if module exists
                if module_name not in data.get_module_names():
                    _LOGGER.error('Module name: "%s" not found', module_name)
                    continue
                # Only create sensors for monitored properties
                for variable in monitored_conditions:
                    dev.append(NetAtmoSensor(data, module_name, variable))
        else:
            for module_name in data.get_module_names():
                for variable in\
                        data.station_data.monitoredConditions(module_name):
                    if variable in SENSOR_TYPES.keys():
                        dev.append(NetAtmoSensor(data, module_name, variable))
                    else:
                        _LOGGER.warning("Ignoring unknown var %s for mod %s",
                                        variable, module_name)
    except pyatmo.NoDevice:
        return None

    add_devices(dev, True)

class NetAtmoSensor(Entity):
    """Implementation of a Netatmo sensor."""

    def __init__(self, netatmo_data, module_name, sensor_type):
        """Initialize the sensor."""
        self._name = 'Netatmo {} {}'.format(module_name,
                                            SENSOR_TYPES[sensor_type][0])
        self.netatmo_data = netatmo_data
        self.module_name = module_name
        self.type = sensor_type
        self._state = None
        self._device_class = SENSOR_TYPES[self.type][3]
        self._icon = SENSOR_TYPES[self.type][2]
        self._unit_of_measurement = SENSOR_TYPES[self.type][1]
        module_id = self.netatmo_data.\
            station_data.moduleByName(module=module_name)['_id']
        self.module_id = module_id[1]

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return self._icon

    @property
    def device_class(self):
        """Return the device class of the sensor."""
        return self._device_class

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity, if any."""
        return self._unit_of_measurement

    def update(self):
        """Get the latest data from NetAtmo API and updates the states."""
        self.netatmo_data.update()
        data = self.netatmo_data.data.get(self.module_name)

        if data is None:
            _LOGGER.warning("No data found for %s", self.module_name)
            self._state = STATE_UNKNOWN
            return

        if self.type == 'temperature':
            self._state = round(data['Temperature'], 1)
        elif self.type == 'humidity':
            self._state = data['Humidity']
        elif self.type == 'rain':
            self._state = data['Rain']
        elif self.type == 'sum_rain_1':
            self._state = data['sum_rain_1']
        elif self.type == 'sum_rain_24':
            self._state = data['sum_rain_24']
        elif self.type == 'noise':
            self._state = data['Noise']
        elif self.type == 'co2':
            self._state = data['CO2']
        elif self.type == 'pressure':
            self._state = round(data['Pressure'], 1)
        elif self.type == 'battery_lvl':
            self._state = data['battery_vp']
        elif self.type == 'battery_vp' and self.module_id == '6':
            if data['battery_vp'] >= 5590:
                self._state = "Full"
            elif data['battery_vp'] >= 5180:
                self._state = "High"
            elif data['battery_vp'] >= 4770:
                self._state = "Medium"
            elif data['battery_vp'] >= 4360:
                self._state = "Low"
            elif data['battery_vp'] < 4360:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '5':
            if data['battery_vp'] >= 5500:
                self._state = "Full"
            elif data['battery_vp'] >= 5000:
                self._state = "High"
            elif data['battery_vp'] >= 4500:
                self._state = "Medium"
            elif data['battery_vp'] >= 4000:
                self._state = "Low"
            elif data['battery_vp'] < 4000:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '3':
            if data['battery_vp'] >= 5640:
                self._state = "Full"
            elif data['battery_vp'] >= 5280:
                self._state = "High"
            elif data['battery_vp'] >= 4920:
                self._state = "Medium"
            elif data['battery_vp'] >= 4560:
                self._state = "Low"
            elif data['battery_vp'] < 4560:
                self._state = "Very Low"
        elif self.type == 'battery_vp' and self.module_id == '2':
            if data['battery_vp'] >= 5500:
                self._state = "Full"
            elif data['battery_vp'] >= 5000:
                self._state = "High"
            elif data['battery_vp'] >= 4500:
                self._state = "Medium"
            elif data['battery_vp'] >= 4000:
                self._state = "Low"
            elif data['battery_vp'] < 4000:
                self._state = "Very Low"
        elif self.type == 'min_temp':
            self._state = data['min_temp']
        elif self.type == 'max_temp':
            self._state = data['max_temp']
        elif self.type == 'windangle_value':
            self._state = data['WindAngle']
        elif self.type == 'windangle':
            if data['WindAngle'] >= 330:
                self._state = "N (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 300:
                self._state = "NW (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 240:
                self._state = "W (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 210:
                self._state = "SW (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 150:
                self._state = "S (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 120:
                self._state = "SE (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 60:
                self._state = "E (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 30:
                self._state = "NE (%d\xb0)" % data['WindAngle']
            elif data['WindAngle'] >= 0:
                self._state = "N (%d\xb0)" % data['WindAngle']
        elif self.type == 'windstrength':
            self._state = data['WindStrength']
        elif self.type == 'gustangle_value':
            self._state = data['GustAngle']
        elif self.type == 'gustangle':
            if data['GustAngle'] >= 330:
                self._state = "N (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 300:
                self._state = "NW (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 240:
                self._state = "W (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 210:
                self._state = "SW (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 150:
                self._state = "S (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 120:
                self._state = "SE (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 60:
                self._state = "E (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 30:
                self._state = "NE (%d\xb0)" % data['GustAngle']
            elif data['GustAngle'] >= 0:
                self._state = "N (%d\xb0)" % data['GustAngle']
        elif self.type == 'guststrength':
            self._state = data['GustStrength']
        elif self.type == 'rf_status_lvl':
            self._state = data['rf_status']
        elif self.type == 'rf_status':
            if data['rf_status'] >= 90:
                self._state = "Low"
            elif data['rf_status'] >= 76:
                self._state = "Medium"
            elif data['rf_status'] >= 60:
                self._state = "High"
            elif data['rf_status'] <= 59:
                self._state = "Full"
        elif self.type == 'wifi_status_lvl':
            self._state = data['wifi_status']
        elif self.type == 'wifi_status':
            if data['wifi_status'] >= 86:
                self._state = "Low"
            elif data['wifi_status'] >= 71:
                self._state = "Medium"
            elif data['wifi_status'] >= 56:
                self._state = "High"
            elif data['wifi_status'] <= 55:
                self._state = "Full"

class NetAtmoData(object):
    """Get the latest data from NetAtmo."""

    def __init__(self, auth, station):
        """Initialize the data object."""
        self.auth = auth
        self.data = None
        self.station_data = None
        self.station = station
        self._next_update = time()
        self._update_in_progress = threading.Lock()

    def get_module_names(self):
        """Return all module available on the API as a list."""
        self.update()
        return self.data.keys()

    def update(self):
        """Call the Netatmo API to update the data.

        This method is not throttled by the builtin Throttle decorator
        but with a custom logic, which takes into account the time
        of the last update from the cloud.
        """
        if time() < self._next_update or \
                not self._update_in_progress.acquire(False):
            return

        try:
            import pyatmo
            self.station_data = pyatmo.WeatherStationData(self.auth)

            if self.station is not None:
                self.data = self.station_data.lastData(
                    station=self.station, exclude=3600)
            else:
                self.data = self.station_data.lastData(exclude=3600)

            newinterval = 0
            for module in self.data:
                if 'When' in self.data[module]:
                    newinterval = self.data[module]['When']
                    break
            if newinterval:
                # Try and estimate when fresh data will be available
                newinterval += NETATMO_UPDATE_INTERVAL - time()
                if newinterval > NETATMO_UPDATE_INTERVAL - 30:
                    newinterval = NETATMO_UPDATE_INTERVAL
                else:
                    if newinterval < NETATMO_UPDATE_INTERVAL / 2:
                        # Never hammer the NetAtmo API more than
                        # twice per update interval
                        newinterval = NETATMO_UPDATE_INTERVAL / 2
                    _LOGGER.warning(
                        "NetAtmo refresh interval reset to %d seconds",
                        newinterval)
            else:
                # Last update time not found, fall back to default value
                newinterval = NETATMO_UPDATE_INTERVAL

            self._next_update = time() + newinterval
        finally:
            self._update_in_progress.release()
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""Parse the input."""

import sys
import re

from tokenizer import Tokenizer
from matrix import Matrix

class Parser(object):  # noqa

    """Input parser: parse token arguments."""

    def __init__(self, vals):
        """Initialize Parser.

        :tokens: lexed argument inputs.
        """
        self.vals = vals
        self.bounds = 'BOUNDS'
        self.operator = 'OPERATOR'
        self.variable = 'VARIABLE'
        self.assignment = 'ASSIGNMENT'
        self.ints = 'INTS'

    def retrieve_args(self, tokens):
        """Format the tokens and save them to file."""
        m = Matrix()
        if len(tokens) == 1:
            # variable call or display array / matrix
            if tokens[0][0] == self.variable:
                # variable call
                value = m.find_variable(tokens[0][1])
                if value:
                    value_mx = m.to_matrix(value)
                    if value_mx is not None:
                        sys.stdout.write(str(value_mx) + '\n')
                    else:
                        sys.stdout.write('Input error.\n')
                else:
                    sys.stdout.write(
                        'Variable `{}` not defined.\n'.format(
                            str(tokens[0][1])))
            elif tokens[0][0] == self.bounds:
                # display array / list
                mx = m.to_matrix(tokens[0][1])
                if mx is not None:
                    sys.stdout.write(str(mx) + '\n')
                else:
                    sys.stdout.write('Input error.\n')
            elif tokens[0][0] == self.operator:
                o = tokens[0][1]
                if o.startswith('inv'):
                    r = re.compile('\(\w\)')
                    p = r.search(o).group()
                    v = re.compile('\w')
                    var = v.search(p).group()
                    val = m.find_variable(var)
                    if val is not None:
                        var_x = m.to_matrix(val)
                        if var_x is not None:
                            res = m.arith(('inverse', var_x, None))
                            if res is not None:
                                sys.stdout.write(str(res) + '\n')
                            else:
                                sys.stdout.write('Wrong matrix format.\n')
                        else:
                            sys.stdout.write('Input error.\n')
                    else:
                        sys.stdout.write(
                            'Variable `{}` not defined.\n'.format(
                                str(tokens[0][1])))
            else:
                sys.stdout.write('Wrong input...\n')

        if len(tokens) == 2:
            if tokens[0][0] == self.variable:
                if tokens[1][0] != self.operator:
                    sys.stdout.write('Wrong input...\n')
                else:
                    if tokens[1][1] == '\'':
                        tr_value = m.find_variable(tokens[0][1])
                        if tr_value:
                            tr_value_mx = m.to_matrix(tr_value)
                            if tr_value_mx is not None:
                                tr = m.transpose(tr_value_mx)
                                sys.stdout.write(str(tr) + '\n')
                            else:
                                sys.stdout.write('Input error.\n')
                        else:
                            sys.stdout.write(
                                'Variable `{}` not defined.\n'.format(
                                    str(tokens[0][1])))
                    else:
                        sys.stdout.write('Wrong input...\n')
            else:
                sys.stdout.write('Wrong input...\n')

        if len(tokens) == 3:
            if tokens[0][0] == self.variable:
                if tokens[1][0] == self.assignment:
                    if tokens[2][0] == self.bounds:
                        input_var = tokens[0][1]
                        assign = tokens[2][1]
                        save_vars = (input_var, assign)
                        m.var_assigner(save_vars)
                    elif tokens[2][0] == self.variable:
                        assigner = tokens[0][1]
                        assignee = tokens[2][1]
                        assigner_val = m.find_variable(assigner)
                        if assigner_val:
                            m.var_assigner((assignee, assigner_val))
                        else:
                            sys.stdout.write(
                                'Variable `{}` not defined.\n'.format(
                                    str(assigner)))
                elif tokens[1][0] == self.operator:
                    op = tokens[1][1]
                    op_var = m.find_variable(tokens[0][1])
                    if op_var is not None:
                        op_vars = m.to_matrix(op_var)
                        if tokens[2][0] == self.bounds:
                            op_bounds = m.to_matrix(tokens[2][1])
                            if op_bounds is not None:
                                op_res = m.arith((op_vars, op_bounds, op))
                                print op_res
                                if op_res is not None:
                                    sys.stdout.write(str(op_res) + '\n')
                                else:
                                    sys.stdout.write(
                                        'Operation not valid.\n')
                            else:
                                sys.stdout.write('Input error.\n')
                        elif tokens[2][0] == self.variable:
                            val = m.find_variable(tokens[2][1])
                            if val is not None:
                                var_value = m.to_matrix(val)
                                op_results = m.arith(
                                    (op_vars, var_value, op))
                                if op_results is not None:
                                    sys.stdout.write(str(op_results) + '\n')
                                else:
                                    sys.stdout.write(
                                        'Operation not valid.\n')
                            else:
                                sys.stdout.write(
                                    'Variable `{}` not defined.\n'.format(
                                        tokens[2][1]))
                    else:
                        sys.stdout.write(
                            'Variable `{}` not defined.\n'.format(
                                tokens[0][1]))
                else:
                    sys.stdout.write('Wrong input...\n')
            elif tokens[0][0] == self.bounds:
                if tokens[1][0] == self.operator:
                    if tokens[2][0] == self.self.bounds:
                        sys.stdout.write('Perform operation on bounds.\n')
                    elif tokens[2][0] == self.variable:
                        sys.stdout.write('Perform operation on variable.\n')
                else:
                    sys.stdout.write('Wrong input...\n')

        if len(tokens) > 3:
            sys.stdout.write('Not implemented yet. WIP\n')

    def save_retrieve_args(self):
        """Save and retrieve arguments."""
        the_tokens = Tokenizer(self.vals)
        tokens = the_tokens.lexer()
        self.retrieve_args(tokens)

<commit_msg>(Feature-update) Color output Update parsers to use color utility.<commit_after>#!/usr/bin/env python
"""Parse the input."""

import re

from tokenizer import Tokenizer
from matrix import Matrix
from utils import color_output

class Parser(object):  # noqa

    """Input parser: parse token arguments."""

    def __init__(self, vals):
        """Initialize Parser.

        :tokens: lexed argument inputs.
        """
        self.vals = vals
        self.bounds = 'BOUNDS'
        self.operator = 'OPERATOR'
        self.variable = 'VARIABLE'
        self.assignment = 'ASSIGNMENT'
        self.ints = 'INTS'

    def retrieve_args(self, tokens):
        """Format the tokens and save them to file."""
        m = Matrix()
        if len(tokens) == 1:
            # variable call or display array / matrix
            if tokens[0][0] == self.variable:
                # variable call
                value = m.find_variable(tokens[0][1])
                if value:
                    value_mx = m.to_matrix(value)
                    if value_mx is not None:
                        color_output(value_mx)
                    else:
                        color_output('Input error.')
                else:
                    color_output(
                        'Variable `{}` not defined.'.format(
                            str(tokens[0][1])))
            elif tokens[0][0] == self.bounds:
                # display array / list
                mx = m.to_matrix(tokens[0][1])
                if mx is not None:
                    color_output(mx)
                else:
                    color_output('Input error')
            elif tokens[0][0] == self.operator:
                o = tokens[0][1]
                if o.startswith('inv'):
                    r = re.compile('\(\w\)')
                    p = r.search(o).group()
                    v = re.compile('\w')
                    var = v.search(p).group()
                    val = m.find_variable(var)
                    if val is not None:
                        var_x = m.to_matrix(val)
                        if var_x is not None:
                            res = m.arith(('inverse', var_x, None))
                            if res is not None:
                                color_output(res)
                            else:
                                color_output('Wrong matrix format.')
                        else:
                            color_output('Input error.')
                    else:
                        color_output(
                            'Variable `{}` not defined.'.format(
                                str(tokens[0][1])))
            else:
                color_output('Wrong input...')

        if len(tokens) == 2:
            if tokens[0][0] == self.variable:
                if tokens[1][0] != self.operator:
                    color_output('Wrong input...')
                else:
                    if tokens[1][1] == '\'':
                        tr_value = m.find_variable(tokens[0][1])
                        if tr_value:
                            tr_value_mx = m.to_matrix(tr_value)
                            if tr_value_mx is not None:
                                tr = m.transpose(tr_value_mx)
                                color_output(str(tr))
                            else:
                                color_output('Input error.')
                        else:
                            color_output(
                                'Variable `{}` not defined.'.format(
                                    str(tokens[0][1])))
                    else:
                        color_output('Wrong input...')
            else:
                color_output('Wrong input...')

        if len(tokens) == 3:
            if tokens[0][0] == self.variable:
                if tokens[1][0] == self.assignment:
                    if tokens[2][0] == self.bounds:
                        input_var = tokens[0][1]
                        assign = tokens[2][1]
                        save_vars = (input_var, assign)
                        m.var_assigner(save_vars)
                    elif tokens[2][0] == self.variable:
                        assigner = tokens[0][1]
                        assignee = tokens[2][1]
                        assigner_val = m.find_variable(assigner)
                        if assigner_val:
                            m.var_assigner((assignee, assigner_val))
                        else:
                            color_output(
                                'Variable `{}` not defined.'.format(
                                    str(assigner)))
                elif tokens[1][0] == self.operator:
                    op = tokens[1][1]
                    op_var = m.find_variable(tokens[0][1])
                    if op_var is not None:
                        op_vars = m.to_matrix(op_var)
                        if tokens[2][0] == self.bounds:
                            op_bounds = m.to_matrix(tokens[2][1])
                            if op_bounds is not None:
                                op_res = m.arith((op_vars, op_bounds, op))
                                print op_res
                                if op_res is not None:
                                    color_output(str(op_res))
                                else:
                                    color_output(
                                        'Operation not valid.')
                            else:
                                color_output('Input error.')
                        elif tokens[2][0] == self.variable:
                            val = m.find_variable(tokens[2][1])
                            if val is not None:
                                var_value = m.to_matrix(val)
                                op_results = m.arith(
                                    (op_vars, var_value, op))
                                if op_results is not None:
                                    color_output(str(op_results))
                                else:
                                    color_output(
                                        'Operation not valid.')
                            else:
                                color_output(
                                    'Variable `{}` not defined.'.format(
                                        tokens[2][1]))
                    else:
                        color_output(
                            'Variable `{}` not defined.'.format(
                                tokens[0][1]))
                else:
                    color_output('Wrong input...')
            elif tokens[0][0] == self.bounds:
                if tokens[1][0] == self.operator:
                    if tokens[2][0] == self.self.bounds:
                        color_output('Perform operation on bounds.')
                    elif tokens[2][0] == self.variable:
                        color_output('Perform operation on variable.')
                else:
                    color_output('Wrong input...')

        if len(tokens) > 3:
            color_output('Not implemented yet. WIP')

    def save_retrieve_args(self):
        """Save and retrieve arguments."""
        the_tokens = Tokenizer(self.vals)
        tokens = the_tokens.lexer()
        self.retrieve_args(tokens)

<|endoftext|>