Skip to content
This repository has been archived by the owner on Jul 23, 2021. It is now read-only.

missing 1 required positional argument: 'sensors' #222

Closed
Mariusthvdb opened this issue Mar 8, 2020 · 7 comments
Closed

missing 1 required positional argument: 'sensors' #222

Mariusthvdb opened this issue Mar 8, 2020 · 7 comments

Comments

@Mariusthvdb
Copy link
Contributor

Mariusthvdb commented Mar 8, 2020

u[on updating the new binary_sensor, this is logged:

2020-03-08 23:17:33 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/config/custom_components/hue_custom/binary_sensor.py", line 228, in update_bridge
    for sensor in bridge.api.sensors.values()
TypeError: parse_hue_api_response() missing 1 required positional argument: 'sensors'

which in my local file is the line:

            for sensor in bridge.api.sensors.values()

@azogue please have a look?

before, this was:

        data = parse_hue_api_response(
            bridge.api.id, (sensor.raw
            for sensor in bridge.api.sensors.values()
            if sensor.type != TYPE_GEOFENCE
        )

could it be you accidentally cut that, or is this the wanted change to the new data source, and another issue has been introduced.

@azogue
Copy link
Contributor

azogue commented Mar 8, 2020

Hi @Mariusthvdb, sure, I'll check it.

I only tested it locally over HA dev, and after that, over HA 106.4 in my production system, but there I only use the remote platftorm, so I'm not sure if something in HA dev is not in 106.4 (related to Hue sensors).

Could you provide more details?

  • Version of HA, version of this component, etc.

Also, have you 'jumped' more than 1 version, right?
I say that because the last change didn't involve any of that.

Another strange thing is the line in your exception log: File "/config/custom_components/hue_custom/binary_sensor.py", line 228.
It doesn't match current version. Could you check that you are using full last versions for this component and for HA?

@Mariusthvdb
Copy link
Contributor Author

Mariusthvdb commented Mar 9, 2020

My file is a heavily modified file which only creates the PHD sensor, found elsewhere in this Github Repo. I am using Hassio 106.5, and if I use the full Custom integration up to the changes you made, the Custom integration works perfectly, creating all (binary_)sensors and remotes.

Note I need the bridge_id for the PHD sensor to create unique sensors for multiple bridges. Would that be an issue now?

        if modelid == "PHD":
            _key = modelid + "_" + bridgeid
            if _key not in data_dict:
                data_dict[_key] = parse_phd(sensor)
            else:
                data_dict[_key].update(parse_phd(sensor))

the remote file is working fine using the new technique here, as is the device_tracker, which isn't very useful though, because it hardly ever (if ever) changes state....)

Using the core binary and regular sensor.

I'll copy it here so you can see what I am doing. I hope to have adapted all changes you made in the new way of retrieving data from the Hub:

"""
Binary sensor for Hue motion sensors.
NOTE: modified to only use the PHD sensor (not yet merged but fully operational
Using core binary_sensors and sensors
@mariusthvdb
"""
import asyncio
import logging
import threading
from datetime import timedelta

from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.event import async_track_time_interval

from . import get_bridges


_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=60)
TYPE_GEOFENCE = "Geofence"
# ICONS = {"SML": "mdi:run"}
DEVICE_CLASSES = {"SML": "motion","PHD": "light"}
ATTRS = {
#     "SML": [
#         "light_level",
#         "battery",
#         "last_updated",
#         "lx",
#         "dark",
#         "daylight",
#         "temperature",
#         "on",
#         "reachable",
#         "sensitivity",
#         "threshold",
#         "threshold_offset",
#     ],
    "PHD": [
        "on",
        "configured",
        "sunrise_offset",
        "sunset_offset",
        "name",
        "type",
        "modelid",
        "swversion",
        "daylight",
        "last_updated",
    ]

}


def parse_hue_api_response(bridgeid, sensors):
    """Take in the Hue API json response."""
    data_dict = {}  # The list of sensors, referenced by their hue_id.

    # Loop over all keys (1,2 etc) to identify sensors and get data.
    for sensor in sensors:
        modelid = sensor["modelid"][0:3]
#         if modelid == "SML":
#             _key = modelid + "_" + sensor["uniqueid"][:-5]
#             if _key not in data_dict:
#                 data_dict[_key] = parse_sml(sensor)
#             else:
#                 data_dict[_key].update(parse_sml(sensor))

        if modelid == "PHD":
            _key = modelid + "_" + bridgeid
            if _key not in data_dict:
                data_dict[_key] = parse_phd(sensor)
            else:
                data_dict[_key].update(parse_phd(sensor))

    return data_dict


# def parse_sml(response):
#     """Parse the json for a SML Hue motion sensor and return the data."""
#     if response["type"] == "ZLLLightLevel":
#         lightlevel = response["state"]["lightlevel"]
#         tholddark = response["config"]["tholddark"]
#         tholdoffset = response["config"]["tholdoffset"]
#         if lightlevel is not None:
#             lx = round(float(10 ** ((lightlevel - 1) / 10000)), 2)
#             dark = response["state"]["dark"]
#             daylight = response["state"]["daylight"]
#             data = {
#                 "light_level": lightlevel,
#                 "lx": lx,
#                 "dark": dark,
#                 "daylight": daylight,
#                 "threshold": tholddark,
#                 "threshold_offset": tholdoffset
#             }
#         else:
#             data = {
#                 "light_level": "No light level data",
#                 "lx": None,
#                 "dark": None,
#                 "daylight": None,
#                 "threshold": None,
#                 "threshold_offset": None
#             }
#
#     elif response["type"] == "ZLLTemperature":
#         if response["state"]["temperature"] is not None:
#             data = {"temperature": response["state"]["temperature"] / 100.0}
#         else:
#             data = {"temperature": "No temperature data"}
#
#     elif response["type"] == "ZLLPresence":
#         name_raw = response["name"]
#         arr = name_raw.split()
#         arr.insert(-1, "motion")
#         name = " ".join(arr)
#         hue_state = response["state"]["presence"]
#         if hue_state is True:
#             state = STATE_ON
#         else:
#             state = STATE_OFF
#
#         data = {
#             "model": "SML",
#             "name": name,
#             "state": state,
#             "battery": response["config"]["battery"],
#             "on": response["config"]["on"],
#             "reachable": response["config"]["reachable"],
#             "sensitivity": response["config"]["sensitivity"],
#             "last_updated": response["state"]["lastupdated"].split("T")
#         }
#
#     return data


def parse_phd(response):
    """Parse the json for a PHD Daylight sensor and return the data."""
    if response["type"] == "Daylight":
        daylight = response["state"]["daylight"]
        name_raw = response["name"]
        name = "Hue " + name_raw

        if daylight is True:
            state = STATE_ON
        else:
            state = STATE_OFF
        if daylight is not None:
            data = {
                "model": "PHD",
                "state": state,
                "on": response["config"]["on"],
                "configured": response["config"]["configured"],
                "sunrise_offset": response["config"]["sunriseoffset"],
                "sunset_offset": response["config"]["sunsetoffset"],
                "name": name,
                "type": response["type"],
                "modelid": response["modelid"],
                "swversion": response["swversion"],
                "daylight": daylight,
                "last_updated": response["state"]["lastupdated"].split("T")
            }
        else:
            data = {
                "model": "PHD",
                "state": "No Daylight data",
                "on": None,
                "configured": None,
                "sunrise_offset": None,
                "sunset_offset": None,
                "name": None,
                "type": None,
                "modelid": None,
                "swversion": None,
                "daylight": "No Daylight data",
                "last_updated": None
            }

    return data

#
# def get_bridges(hass):
#     from homeassistant.components import hue
#     from homeassistant.components.hue.bridge import HueBridge
#
#     return [
#         entry
#         for entry in hass.data[hue.DOMAIN].values()
#         if isinstance(entry, HueBridge) and entry.api
#     ]
#
#
# async def update_api(api):
#     import aiohue
#
#     try:
#         with async_timeout.timeout(10):
#             await api.update()
#     except (asyncio.TimeoutError, aiohue.AiohueException) as err:
#         _LOGGER.debug("Failed to fetch sensors: %s", err)
#         return False
#     return True


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Initialise Hue Bridge connection."""
    data = HueSensorData(hass, async_add_entities)
    await data.async_update_info()
    async_track_time_interval(hass, data.async_update_info, SCAN_INTERVAL)


class HueSensorData(object):
    """Get the latest sensor data."""

    def __init__(self, hass, async_add_entities):
        """Initialize the data object."""
        self.hass = hass
        self.lock = threading.Lock()
        self.data = {}
        self.sensors = {}
        self.async_add_entities = async_add_entities

    async def update_bridge(self, bridge):
        await bridge.sensor_manager.coordinator.async_request_refresh()

        data = parse_hue_api_response(
            bridge.api.id, (sensor.raw
            for sensor in bridge.api.sensors.values()
            if sensor.type != TYPE_GEOFENCE
        )


        new_sensors = data.keys() - self.data.keys()
        updated_sensors = []
        for key, new in data.items():
            new["changed"] = True
            old = self.data.get(key)
            if not old or old == new:
                continue
            updated_sensors.append(key)
            if (
                old["last_updated"] == new["last_updated"]
                and old["state"] == new["state"]
            ):
                new["changed"] = False
        self.data.update(data)

        new_entities = {
            entity_id: HueSensor(entity_id, self) for entity_id in new_sensors
        }
        if new_entities:
            _LOGGER.debug("Created %s", ", ".join(new_entities.keys()))
            self.sensors.update(new_entities)
            self.async_add_entities(new_entities.values(), True)
        for entity_id in updated_sensors:
            self.sensors[entity_id].async_schedule_update_ha_state()

    async def async_update_info(self, now=None):
        """Get the bridge info."""
        locked = self.lock.acquire(False)
        if not locked:
            return
        try:
            bridges = get_bridges(self.hass)
            if not bridges:
                if now:
                    # periodic task
                    await asyncio.sleep(5)
                return
            await asyncio.wait(
                [self.update_bridge(bridge) for bridge in bridges], loop=self.hass.loop
            )
        finally:
            self.lock.release()


class HueSensor(BinarySensorDevice):
    """Class to hold Hue Sensor basic info."""

    def __init__(self, hue_id, data):
        """Initialize the sensor object."""
        self._hue_id = hue_id
        self._data = data.data  # data is in .data

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

    @property
    def name(self):
        """Return the name of the sensor."""
        data = self._data.get(self._hue_id)
        if data:
            return data["name"]

    @property
    def unique_id(self):
        """Return the ID of this Hue sensor."""
        return self._hue_id[+4:][:-3]

    @property
    def is_on(self):
        """Return the state of the sensor."""
        data = self._data.get(self._hue_id)
        if data and data["model"] in ["SML","PHD"] and data["changed"]:
            return data["state"] == STATE_ON
        return False

    @property
    def device_class(self):
        """Return the class of this device, from component DEVICE_CLASSES."""
        data = self._data.get(self._hue_id)
        if data:
            device_class = DEVICE_CLASSES.get(data["model"])
            if device_class:
                return device_class

    @property
    def device_state_attributes(self):
        """Attributes."""
        data = self._data.get(self._hue_id)
        if data:
            return {key: data.get(key) for key in ATTRS.get(data["model"], [])}

@Mariusthvdb
Copy link
Contributor Author

Mariusthvdb commented Mar 9, 2020

small update:
taking out the bridge_id of

        if modelid == "PHD":
            _key = modelid + "_" + bridgeid
            if _key not in data_dict:
                data_dict[_key] = parse_phd(sensor)
            else:
                data_dict[_key].update(parse_phd(sensor))

to

        if modelid == "PHD":
            _key = modelid
            if _key not in data_dict:
                data_dict[_key] = parse_phd(sensor)
            else:
                data_dict[_key].update(parse_phd(sensor))

results in:

Traceback (most recent call last):
  File "/config/custom_components/hue_custom/binary_sensor.py", line 231, in update_bridge
    for sensor in bridge.api.sensors.values()
TypeError: parse_hue_api_response() missing 1 required positional argument: 'sensors'

only other big change is:

    async def update_bridge(self, bridge):
        available = await update_api(bridge.api.sensors)
        if not available:
            return

        data = parse_hue_api_response(
            bridge.api.id, (sensor.raw
            for sensor in bridge.api.sensors.values()
            if sensor.type != TYPE_GEOFENCE)
        )

in the original (old) binary_sensor.py.

@azogue
Copy link
Contributor

azogue commented Mar 10, 2020

@robmarkcole, this issue should be closed, as relates to custom code, not present in master branch.

Sure @Mariusthvdb agrees on that

@Mariusthvdb
Copy link
Contributor Author

yes I am fine with that, especially since I added the PHD sensor to your refactored setup and it works very nicely indeed. Only wish is we could add a bridgeid to the sensor_id, to make them hub specific in the multiple hubs setup configurations, like I had it. Could you help me realize that please? Do we have the bridged in the custom integration available?

def parse_phd(response):
    """Parse the json for a PHD Daylight sensor and return the data."""
    if response["type"] == "Daylight":
        daylight = response["state"]["daylight"]
        name_raw = response["name"]
        name = "Hue " + name_raw + " custom" ##<-- before:  modelid + "_" + bridgeid

        if daylight is True:
            state = STATE_ON
        else:
            state = STATE_OFF
        if daylight is not None:
            data = {
                "model": "PHD",
                "state": state,
                "on": response["config"]["on"],
                "configured": response["config"]["configured"],
                "sunrise_offset": response["config"]["sunriseoffset"],
                "sunset_offset": response["config"]["sunsetoffset"],
                "name": name,
                "type": response["type"],
                "modelid": response["modelid"],
                "swversion": response["swversion"],
                "daylight": daylight,
                "last_updated": response["state"]["lastupdated"].split("T"),
                "manufacturername": response["manufacturername"]
            }
        else:
            data = {
                "model": "PHD",
                "state": "No Daylight data",
                "on": None,
                "configured": None,
                "sunrise_offset": None,
                "sunset_offset": None,
                "name": None,
                "type": None,
                "modelid": None,
                "swversion": None,
                "daylight": "No Daylight data",
                "last_updated": None,
                "manufacturername": None
            }

    return data

Schermafbeelding 2020-03-10 om 14 48 40

thanks for having a look!

@robmarkcole
Copy link
Owner

@Mariusthvdb please create a feature request

@Mariusthvdb
Copy link
Contributor Author

Mariusthvdb commented Mar 10, 2020

will do, thought it wasn't allowed since it isn't a feature (yet).
thanks for the opportunity!


Done: #227

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants