Пример #1
0
    async def query(host: str, request: Union[str, Dict], retry_count: int = 3) -> Dict:
        """Request information from a TP-Link SmartHome Device.

        :param str host: host name or ip address of the device
        :param request: command to send to the device (can be either dict or
        json string)
        :param retry_count: how many retries to do in case of failure
        :return: response dict
        """
        if isinstance(request, dict):
            request = json.dumps(request)

        timeout = TPLinkSmartHomeProtocol.DEFAULT_TIMEOUT
        writer = None
        for retry in range(retry_count + 1):
            try:
                task = asyncio.open_connection(
                    host, TPLinkSmartHomeProtocol.DEFAULT_PORT
                )
                reader, writer = await asyncio.wait_for(task, timeout=timeout)
                _LOGGER.debug("> (%i) %s", len(request), request)
                writer.write(TPLinkSmartHomeProtocol.encrypt(request))
                await writer.drain()

                buffer = bytes()
                # Some devices send responses with a length header of 0 and
                # terminate with a zero size chunk. Others send the length and
                # will hang if we attempt to read more data.
                length = -1
                while True:
                    chunk = await reader.read(4096)
                    if length == -1:
                        length = struct.unpack(">I", chunk[0:4])[0]
                    buffer += chunk
                    if (length > 0 and len(buffer) >= length + 4) or not chunk:
                        break

                response = TPLinkSmartHomeProtocol.decrypt(buffer[4:])
                json_payload = json.loads(response)
                _LOGGER.debug("< (%i) %s", len(response), pf(json_payload))

                return json_payload

            except Exception as ex:
                if retry >= retry_count:
                    _LOGGER.debug("Giving up after %s retries", retry)
                    raise SmartDeviceException(
                        "Unable to query the device: %s" % ex
                    ) from ex

                _LOGGER.debug("Unable to query the device, retrying: %s", ex)

            finally:
                if writer:
                    writer.close()
                    await writer.wait_closed()

        # make mypy happy, this should never be reached..
        raise SmartDeviceException("Query reached somehow to unreachable")
Пример #2
0
    def get_plug_by_name(self, name: str) -> "SmartDevice":
        """Return child device for the given name."""
        for p in self.children:
            if p.alias == name:
                return p

        raise SmartDeviceException(f"Device has no child with {name}")
Пример #3
0
 def get_plug_by_index(self, index: int) -> "SmartDevice":
     """Return child device for the given index."""
     if index + 1 > len(self.children) or index < 0:
         raise SmartDeviceException(
             f"Invalid index {index}, device has {len(self.children)} plugs"
         )
     return self.children[index]
Пример #4
0
    async def current_consumption(self) -> float:
        """Get the current power consumption in Watt."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        response = EmeterStatus(await self.get_emeter_realtime())
        return response["power"]
Пример #5
0
    async def erase_emeter_stats(self) -> Dict:
        """Erase energy meter statistics."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        return await self._query_helper(self.emeter_type, "erase_emeter_stat",
                                        None)
Пример #6
0
    def __getitem__(self, item):
        valid_keys = [
            "voltage_mv",
            "power_mw",
            "current_ma",
            "energy_wh",
            "total_wh",
            "voltage",
            "power",
            "current",
            "total",
            "energy",
        ]

        # 1. if requested data is available, return it
        if item in super().keys():
            return super().__getitem__(item)
        # otherwise decide how to convert it
        else:
            if item not in valid_keys:
                raise KeyError(item)
            if "_" in item:  # upscale
                return super().__getitem__(item[:item.find("_")]) * 1000
            else:  # downscale
                for i in super().keys():
                    if i.startswith(item):
                        return self.__getitem__(i) / 1000

                raise SmartDeviceException("Unable to find a value for '%s'" %
                                           item)
Пример #7
0
    async def get_emeter_realtime(self) -> EmeterStatus:
        """Retrieve current energy readings."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        return EmeterStatus(await self._query_helper(self.emeter_type,
                                                     "get_realtime"))
Пример #8
0
    async def get_emeter_daily(self,
                               year: int = None,
                               month: int = None,
                               kwh: bool = True) -> Dict:
        """Retrieve daily statistics for a given month.
        :param year: year for which to retrieve statistics (default: this year)
        :param month: month for which to retrieve statistics (default: this
                      month)
        :param kwh: return usage in kWh (default: True)
        :return: mapping of day of month to value
        """
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        if year is None:
            year = datetime.now().year
        if month is None:
            month = datetime.now().month

        response = await self._query_helper(self.emeter_type, "get_daystat", {
            "month": month,
            "year": year
        })

        return self._emeter_convert_emeter_data(response["day_list"], kwh)
Пример #9
0
    def emeter_realtime(self) -> EmeterStatus:
        """Return current energy readings."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        return EmeterStatus(
            self._last_update[self.emeter_type]["get_realtime"])
Пример #10
0
    async def _query_helper(self,
                            target: str,
                            cmd: str,
                            arg: Optional[Dict] = None,
                            child_ids=None) -> Any:
        """Query device, return results or raise an exception.

        :param target: Target system {system, time, emeter, ..}
        :param cmd: Command to execute
        :param arg: payload dict to be send to the device
        :param child_ids: ids of child devices
        :return: Unwrapped result for the call.
        """
        request = self._create_request(target, cmd, arg, child_ids)

        try:
            response = await self.protocol.query(host=self.host,
                                                 request=request)
        except Exception as ex:
            raise SmartDeviceException(
                f"Communication error on {target}:{cmd}") from ex

        if target not in response:
            raise SmartDeviceException(
                f"No required {target} in response: {response}")

        result = response[target]
        if "err_code" in result and result["err_code"] != 0:
            raise SmartDeviceException(f"Error on {target}.{cmd}: {result}")

        if cmd not in result:
            raise SmartDeviceException(f"No command in response: {response}")
        result = result[cmd]
        if "err_code" in result and result["err_code"] != 0:
            raise SmartDeviceException(f"Error on {target} {cmd}: {result}")

        if "err_code" in result:
            del result["err_code"]

        return result
Пример #11
0
    def emeter_this_month(self) -> Optional[float]:
        """Return this month's energy consumption in kWh."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        raw_data = self._last_update[
            self.emeter_type]["get_monthstat"]["month_list"]
        data = self._emeter_convert_emeter_data(raw_data)
        current_month = datetime.now().month

        if current_month in data:
            return data[current_month]

        return None
Пример #12
0
    def emeter_today(self) -> Optional[float]:
        """Return today's energy consumption in kWh."""
        if not self.has_emeter:
            raise SmartDeviceException("Device has no emeter")

        raw_data = self._last_update[
            self.emeter_type]["get_daystat"]["day_list"]
        data = self._emeter_convert_emeter_data(raw_data)
        today = datetime.now().day

        if today in data:
            return data[today]

        return None
Пример #13
0
    def mac(self) -> str:
        """Return mac address.
        :return: mac address in hexadecimal with colons, e.g. 01:23:45:67:89:ab
        """
        sys_info = self.sys_info

        if "mac" in sys_info:
            return str(sys_info["mac"])
        elif "mic_mac" in sys_info:
            return ":".join(
                format(s, "02x") for s in bytes.fromhex(sys_info["mic_mac"]))

        raise SmartDeviceException(
            "Unknown mac, please submit a bug report with sys_info output.")
Пример #14
0
    async def wifi_scan(self) -> List[WifiNetwork]:  # noqa: D202
        """Scan for available wifi networks."""
        async def _scan(target):
            return await self._query_helper(target, "get_scaninfo",
                                            {"refresh": 1})

        try:
            info = await _scan("netif")
        except SmartDeviceException as ex:
            _LOGGER.debug(
                "Unable to scan using 'netif', retrying with 'softaponboarding': %s",
                ex)
            info = await _scan("smartlife.iot.common.softaponboarding")

        if "ap_list" not in info:
            raise SmartDeviceException("Invalid response for wifi scan: %s" %
                                       info)

        return [WifiNetwork(**x) for x in info["ap_list"]]
Пример #15
0
 async def wrapped(*args, **kwargs):
     self = args[0]
     if self._last_update is None:
         raise SmartDeviceException(
             "You need to await update() to access the data")
     return await f(*args, **kwargs)