Custom Cover is not working now, but was in previous HA versions

I think this error is related, but I can’t tell where it goes.
I am sure it is missing the “key” but I not sure what. Since this was working in the past, it has to be something new. Unfornutally the last time I know it worked was in July and I skipped a lot of versions in between so I am not sure what version notes to go back to.

Here is the error…

Log Details (ERROR)
Sat Feb 23 2019 07:49:55 GMT-0600 (Central Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/discovery.py", line 161, in async_load_platform
    hass, component, hass_config)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/setup.py", line 56, in async_setup_component
    return await task  # type: ignore
  File "/usr/local/lib/python3.7/site-packages/homeassistant/setup.py", line 124, in _async_setup_component
    conf_util.async_process_component_config(hass, config, domain)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/config.py", line 719, in async_process_component_config
    for p_name, p_config in config_per_platform(config, domain):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/__init__.py", line 18, in config_per_platform
    for config_key in extract_domain_configs(config, domain):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/__init__.py", line 41, in extract_domain_configs
    return [key for key in config.keys() if pattern.match(key)]
AttributeError: 'Config' object has no attribute 'keys'

Here is the code …

"""
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover/almond_plus
"""

import logging
import requests
import traceback
import datetime

from homeassistant.components.cover import (
    CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, STATE_OPENING, STATE_CLOSING)
from homeassistant.const import (STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN)

_LOGGER = logging.getLogger(__name__)

DOMAIN = "almond_plus"
DATA_ALMONDPLUS = "ALMONDPLUS"


def setup_platform(hass, config, add_devices, discovery_info=None):
    global my_almond_plus
    return_value = False
    try:
        _LOGGER.debug("Started - find me 3")
        my_almond_plus = hass.data[DATA_ALMONDPLUS]["almondplus_api"]
        covers = []
        _LOGGER.debug("looking for devices (cover)")
        for almond_key, almond_entity in my_almond_plus.get_device_list().items():
            _LOGGER.debug("Device Name (cover) "+almond_entity.name+" type "+almond_entity.type)
            if almond_entity.type == "53":
                tmp = AlmondPlusCover(almond_entity)
                _LOGGER.debug("Device - Str (cover)"+str(tmp))
                _LOGGER.debug("Device - (cover)" + tmp.id + ", " + tmp.device_id + ", " + tmp.state + ", " + tmp.name)
                covers.append(tmp)
        if len(covers) > 0:
            add_devices(covers)
            hass.data[DATA_ALMONDPLUS]["almondplus_cover_entities"] = covers
        return_value = True
    except Exception as e:
        _LOGGER.error("Error\n"
                      + "**************************\n"
                      + str(e) + "\n"
                      + traceback.format_exc()
                      + "**************************")
    _LOGGER.debug("Setup ended with " + str(return_value))
    return return_value


#               "4":{
#                   "Data":{
#                         "ID":"4",
#                         "Name":"GarageDoorOpener Two Car",
#                         "FriendlyDeviceType":"GarageDoorOpener",
#                         "Type":"53",
#                         "Location":"Default",
#                         "LastActiveEpoch":"1531243088",
#                         "Model":"Unknown: type=4744,",
#                         "Version":"0",
#                         "Manufacturer":"Linear"
#                         },
#                   "DeviceValues":{
#                                   "1":{
#                                       "Name":"BARRIER OPERATOR",
#                                       "Value":"0",
#                                       "Type":"44"
#                                       }
#                                 }
#                   }


class AlmondPlusCover(CoverDevice):
    """Representation of a Almond+ cover."""

    def __init__(self, device):
        self._id = None
        self._device_id = None
        self._name = None
        self._state = None

        """Attributes"""
        self.friendly_device_type = None
        self.type = None
        self.location = None
        self.last_active_epoch = None
        self.model = None
        self.value_name = None
        self.value_value = None
        self.value_type = None
        self._state_before_move = None
        self._update_properties(device)

    def _update_properties(self, device):
        self._id = device.id
        self._device_id = device.device_id
        self._name = DOMAIN+"_"+device.name + '_' + device.id + '_' + device.device_id
        self._state = ''
        self.set_state(device.value_value)

        """Attributes"""
        self.friendly_device_type = device.friendly_device_type
        self.type = device.type
        self.location = device.location
        self.last_active_epoch = device.last_active_epoch
        self.model = device.model
        self.value_name = device.value_name
        self.value_value = device.value_value
        self.value_type = device.value_type


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

    @property
    def id(self):
        """Get the name of the pin."""
        return self._id

    @property
    def device_id(self):
        """Get the name of the pin."""
        return self._device_id

    @property
    def should_poll(self):
        return False

    @property
    def device_state_attributes(self):
        """Return the optional state attributes."""
        data = {}
        data["id"] = self._id
        data["device_id"] = self._device_id
        data["friendly_device_type"] = self.friendly_device_type
        data["type"] = self.type
        data["location"] = self.location
        data["last_active_epoch"] = self.last_active_epoch
        data["last_active_datetime"] = datetime.datetime.fromtimestamp(int(self.last_active_epoch))
        data["model"] = self.model
        data["value_name"] = self.value_name
        data["value_value"] = self.value_value
        data["value_type"] = self.value_type
        return data

    def update(self, device):
        _LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
        self._update_properties(device)
        self.schedule_update_ha_state()

    def set_state(self, value_value):
        _LOGGER.debug("Setting State -"+value_value+"-"+value_value.lower()+"-")
        if value_value.lower() == "0":
            self._state = STATE_CLOSED
            _LOGGER.debug("Setting closed "+self._state)
        elif value_value.lower() == "255":
            self._state = STATE_OPEN
            _LOGGER.debug("Setting open "+self._state)
        elif value_value.lower() == "254":
            self._state = STATE_OPENING
            _LOGGER.debug("Setting opening " + self._state)
        elif value_value.lower() == "252":
            self._state = STATE_CLOSING
            _LOGGER.debug("Setting closing " + self._state)
        else:
            self._state = 'unknown'
            _LOGGER.debug("Setting unknown " + self._state)
        _LOGGER.debug("Setting State Finish "+self._state)

    @property
    def is_opening(self):
        """Return if the cover is opening."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_OPENING]

    @property
    def is_closing(self):
        """Return if the cover is closing."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_CLOSING]

    @property
    def is_closed(self):
        """Return if the cover is closed."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_CLOSED, STATE_OPENING]

    def close_cover(self, **kwargs):
        """Close the cover."""
        if self._state not in [STATE_CLOSED, STATE_CLOSING]:
            _LOGGER.debug("Close Cover")
            self._state_before_move = self._state
            my_almond_plus.set_device(self.id, self.device_id, "0")

    def open_cover(self, **kwargs):
        """Open the cover."""
        if self._state not in [STATE_OPEN, STATE_OPENING]:
            _LOGGER.debug("Open Cover")
            self._state_before_move = self._state
            my_almond_plus.set_device(self.id, self.device_id, "255")

    def update(self, device):
        _LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
        self._update_properties(device)
        self.schedule_update_ha_state()

    @property
    def supported_features(self):
        """Flag supported features."""
        return SUPPORT_OPEN | SUPPORT_CLOSE

Here’s my guess:
When you created your component, you based it on how other climate components worked for the version you used. Something has changed in how climate components work since then (like the structure of the ‘Config’ object’).

I has this happen to me for a custom MQTT climate component I made that was based on code from 0.80. It stopped working several versions later (I think it was 0.85) because of underlying changes in the MQTT component.

My solution was to start all over again. I suppose I could’ve looked for the precise differences between old and new, and adapt my old version accordingly, but I chose to start with a clean slate. I took a copy of the new MQTT climate component and modified it to suit my needs.

Thanks, @123 there are new properties after more debugging I don’t think the error message is related.

For debugging I added a delay and that is why I am assuming they are not related.
Here is my discovery code snippet.

time.sleep(2)
_LOGGER.debug("Start Loading cover platform")
load_platform(hass, 'cover', DOMAIN, None, hass.config)
_LOGGER.debug("Done Loading cover platform")
time.sleep(2)

Here is the log entry. It actually looks like HA thinks it loaded the cover. But none of the code is executed in the “def setup_platform”

019-02-23 15:30:01 DEBUG (SyncWorker_5) [custom_components.almond_plus] Start Loading cover platform
2019-02-23 15:30:01 DEBUG (SyncWorker_5) [custom_components.almond_plus] Done Loading cover platform
2019-02-23 15:30:01 INFO (MainThread) [homeassistant.loader] Loaded cover from homeassistant.components.cover

As an experiment, I renamed cover.py to cover.keep.py and the HA still said it loaded. Do I have the file named right? Should the cover platform be called cover.py?

I tried a different angle. I rem out the switch discovery and changed the cover to switch. My thought was to see if I had some type-o and it loaded the switches and there was no “key” error. Put it back to covers and there is the “key” error. So my last post was in error. The error message is related to the cover.
At the bottom, after the logs of running it with cover, then with the switch is the cover.py code. It almost has to be in the “setup_platform” section, I just don’t see it. Can anyone else?

"""load_platform(hass, 'switch', DOMAIN, None, hass.config)
LOGGER.debug("Done Loading switch platform")
time.sleep(2)"""
_LOGGER.debug("Start Loading cover platform")
load_platform(hass, 'cover', DOMAIN, None, hass.config)
_LOGGER.debug("Done Loading cover platform")
time.sleep(2)
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV




2019-02-24 06:14:22 DEBUG (SyncWorker_8) [custom_components.almond_plus] Start Loading switch platform
2019-02-24 06:14:22 DEBUG (SyncWorker_8) [custom_components.almond_plus] Start Loading cover platform
2019-02-24 06:14:22 DEBUG (SyncWorker_8) [custom_components.almond_plus] Done Loading cover platform
2019-02-24 06:14:22 INFO (MainThread) [homeassistant.loader] Loaded cover from homeassistant.components.cover


Received invalid command: system_health/info
6:14 AM components/websocket_api/connection.py (ERROR)
Error doing job: Task exception was never retrieved
6:14 AM helpers/__init__.py (ERROR)
Monitored condition temperature_min is deprecated
6:14 AM components/sensor/darksky.py (WARNING)
Monitored condition temperature_max is deprecated
6:14 AM components/sensor/darksky.py (WARNING)


**************************************************************************************************************************

Received invalid command: system_health/info
6:18 AM components/websocket_api/connection.py (ERROR)
You are using a custom component for almond_plus.switch which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.
6:18 AM loader.py (WARNING)
Monitored condition temperature_min is deprecated
6:18 AM components/sensor/darksky.py (WARNING)
Monitored condition temperature_max is deprecated
6:18 AM components/sensor/darksky.py (WARNING)


2019-02-24 06:18:42 DEBUG (SyncWorker_1) [custom_components.almond_plus] Start Loading cover platform
2019-02-24 06:18:42 DEBUG (SyncWorker_1) [custom_components.almond_plus] Done Loading cover platform
2019-02-24 06:18:42 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event platform_discovered[L]: service=load_platform.switch, platform=almond_plus>
2019-02-24 06:18:42 INFO (MainThread) [homeassistant.loader] Loaded almond_plus.switch from custom_components.almond_plus.switch
2019-02-24 06:18:42 WARNING (MainThread) [homeassistant.loader] You are using a custom component for almond_plus.switch which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.



::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
"""
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover/almond_plus
"""
from datetime import timedelta
import functools as ft
import logging
import traceback
import voluptuous as vol
import homeassistant.helpers.config_validation as cv

from homeassistant.loader import bind_hass
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import (  # noqa
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
from homeassistant.components import group
from homeassistant.helpers import intent
from homeassistant.const import (
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
STATE_CLOSED, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=15)

GROUP_NAME_ALL_COVERS = 'all covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')

ENTITY_ID_FORMAT = DOMAIN + '.{}'

DEVICE_CLASSES = [
'damper',
'garage',        # Garage door control
'window',        # Window control
]

DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))

SUPPORT_OPEN = 1
SUPPORT_CLOSE = 2
SUPPORT_SET_POSITION = 4
SUPPORT_STOP = 8
SUPPORT_OPEN_TILT = 16
SUPPORT_CLOSE_TILT = 32
SUPPORT_STOP_TILT = 64
SUPPORT_SET_TILT_POSITION = 128

ATTR_CURRENT_POSITION = 'current_position'
ATTR_CURRENT_TILT_POSITION = 'current_tilt_position'
ATTR_POSITION = 'position'
ATTR_TILT_POSITION = 'tilt_position'

INTENT_OPEN_COVER = 'HassOpenCover'
INTENT_CLOSE_COVER = 'HassCloseCover'

COVER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})

COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
vol.Required(ATTR_POSITION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
})

COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({
vol.Required(ATTR_TILT_POSITION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
})
   

DOMAIN = "almond_plus"
DATA_ALMONDPLUS = "ALMONDPLUS"


def setup_platform(hass, config, add_devices, discovery_info=None):
global my_almond_plus
return_value = False
try:
_LOGGER.debug("Started - find me 3")
my_almond_plus = hass.data[DATA_ALMONDPLUS]["almondplus_api"]
covers = []
_LOGGER.debug("looking for devices (cover)")
for almond_key, almond_entity in my_almond_plus.get_device_list().items():
    _LOGGER.debug("Device Name (cover) "+almond_entity.name+" type "+almond_entity.type)
    if almond_entity.type == "53":
        tmp = AlmondPlusCover(almond_entity)
        _LOGGER.debug("Device - Str (cover)"+str(tmp))
        _LOGGER.debug("Device - (cover)" + tmp.id + ", " + tmp.device_id + ", " + tmp.state + ", " + tmp.name)
        covers.append(tmp)
if len(covers) > 0:
    add_devices(covers)
    hass.data[DATA_ALMONDPLUS]["almondplus_cover_entities"] = covers
return_value = True
except Exception as e:
_LOGGER.error("Error\n"
              + "**************************\n"
              + str(e) + "\n"
              + traceback.format_exc()
              + "**************************")
_LOGGER.debug("Setup ended with " + str(return_value))
return return_value


#               "4":{
#                   "Data":{
#                         "ID":"4",
#                         "Name":"GarageDoorOpener Two Car",
#                         "FriendlyDeviceType":"GarageDoorOpener",
#                         "Type":"53",
#                         "Location":"Default",
#                         "LastActiveEpoch":"1531243088",
#                         "Model":"Unknown: type=4744,",
#                         "Version":"0",
#                         "Manufacturer":"Linear"
#                         },
#                   "DeviceValues":{
#                                   "1":{
#                                       "Name":"BARRIER OPERATOR",
#                                       "Value":"0",
#                                       "Type":"44"
#                                       }
#                                 }
#                   }


class AlmondPlusCover(CoverDevice):
"""Representation of a Almond+ cover."""

def __init__(self, device):
self._id = None
self._device_id = None
self._name = None
self._state = None

"""Attributes"""
self.friendly_device_type = None
self.type = None
self.location = None
self.last_active_epoch = None
self.model = None
self.value_name = None
self.value_value = None
self.value_type = None
self._state_before_move = None
self._update_properties(device)

def _update_properties(self, device):
self._id = device.id
self._device_id = device.device_id
self._name = DOMAIN+"_"+device.name + '_' + device.id + '_' + device.device_id
self._state = ''
self.set_state(device.value_value)

"""Attributes"""
self.friendly_device_type = device.friendly_device_type
self.type = device.type
self.location = device.location
self.last_active_epoch = device.last_active_epoch
self.model = device.model
self.value_name = device.value_name
self.value_value = device.value_value
self.value_type = device.value_type


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

@property
def id(self):
"""Get the name of the pin."""
return self._id

@property
def device_id(self):
"""Get the name of the pin."""
return self._device_id

@property
def should_poll(self):
return False

@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
data["id"] = self._id
data["device_id"] = self._device_id
data["friendly_device_type"] = self.friendly_device_type
data["type"] = self.type
data["location"] = self.location
data["last_active_epoch"] = self.last_active_epoch
data["last_active_datetime"] = datetime.datetime.fromtimestamp(int(self.last_active_epoch))
data["model"] = self.model
data["value_name"] = self.value_name
data["value_value"] = self.value_value
data["value_type"] = self.value_type
return data

def update(self, device):
_LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
self._update_properties(device)
self.schedule_update_ha_state()

def set_state(self, value_value):
_LOGGER.debug("Setting State -"+value_value+"-"+value_value.lower()+"-")
if value_value.lower() == "0":
    self._state = STATE_CLOSED
    _LOGGER.debug("Setting closed "+self._state)
elif value_value.lower() == "255":
    self._state = STATE_OPEN
    _LOGGER.debug("Setting open "+self._state)
elif value_value.lower() == "254":
    self._state = STATE_OPENING
    _LOGGER.debug("Setting opening " + self._state)
elif value_value.lower() == "252":
    self._state = STATE_CLOSING
    _LOGGER.debug("Setting closing " + self._state)
else:
    self._state = 'unknown'
    _LOGGER.debug("Setting unknown " + self._state)
_LOGGER.debug("Setting State Finish "+self._state)

@property
def is_opening(self):
"""Return if the cover is opening."""
if self._state in [STATE_UNKNOWN]:
    return None
return self._state in [STATE_OPENING]

@property
def is_closing(self):
"""Return if the cover is closing."""
if self._state in [STATE_UNKNOWN]:
    return None
return self._state in [STATE_CLOSING]

@property
def is_closed(self):
"""Return if the cover is closed."""
if self._state in [STATE_UNKNOWN]:
    return None
return self._state in [STATE_CLOSED, STATE_OPENING]

def close_cover(self, **kwargs):
"""Close the cover."""
if self._state not in [STATE_CLOSED, STATE_CLOSING]:
    _LOGGER.debug("Close Cover")
    self._state_before_move = self._state
    my_almond_plus.set_device(self.id, self.device_id, "0")

def open_cover(self, **kwargs):
"""Open the cover."""
if self._state not in [STATE_OPEN, STATE_OPENING]:
    _LOGGER.debug("Open Cover")
    self._state_before_move = self._state
    my_almond_plus.set_device(self.id, self.device_id, "255")

def update(self, device):
_LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
self._update_properties(device)
self.schedule_update_ha_state()

def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
pass

def stop_cover(self, **kwargs):
"""Stop the cover."""
pass        

def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
pass

def set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
pass        

def stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
pass        

@property
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP

if self.current_cover_position is not None:
    supported_features |= SUPPORT_SET_POSITION

if self.current_cover_tilt_position is not None:
    supported_features |= (
        SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
        SUPPORT_SET_TILT_POSITION)

return supported_features

Here is my current code.

"""
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover/almond_plus
"""

import logging
import requests
import traceback
import datetime

from homeassistant.components.cover import (
    CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, STATE_OPENING, STATE_CLOSING)
from homeassistant.const import (STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN)

_LOGGER = logging.getLogger(__name__)

DOMAIN = "almond_plus"
DATA_ALMONDPLUS = "ALMONDPLUS"


def setup_platform(hass, config, add_devices, discovery_info=None):
    global my_almond_plus
    return_value = False
    try:
        _LOGGER.debug("Started - find me 3")
        my_almond_plus = hass.data[DATA_ALMONDPLUS]["almondplus_api"]
        covers = []
        _LOGGER.debug("looking for devices (cover)")
        for almond_key, almond_entity in my_almond_plus.get_device_list().items():
            _LOGGER.debug("Device Name (cover) "+almond_entity.name+" type "+almond_entity.type)
            if almond_entity.type == "53":
                tmp = AlmondPlusCover(almond_entity)
                _LOGGER.debug("Device - Str (cover)"+str(tmp))
                _LOGGER.debug("Device - (cover)" + tmp.id + ", " + tmp.device_id + ", " + tmp.state + ", " + tmp.name)
                covers.append(tmp)
        if len(covers) > 0:
            add_devices(covers)
            hass.data[DATA_ALMONDPLUS]["almondplus_cover_entities"] = covers
        return_value = True
    except Exception as e:
        _LOGGER.error("Error\n"
                      + "**************************\n"
                      + str(e) + "\n"
                      + traceback.format_exc()
                      + "**************************")
    _LOGGER.debug("Setup ended with " + str(return_value))
    return return_value


#               "4":{
#                   "Data":{
#                         "ID":"4",
#                         "Name":"GarageDoorOpener Two Car",
#                         "FriendlyDeviceType":"GarageDoorOpener",
#                         "Type":"53",
#                         "Location":"Default",
#                         "LastActiveEpoch":"1531243088",
#                         "Model":"Unknown: type=4744,",
#                         "Version":"0",
#                         "Manufacturer":"Linear"
#                         },
#                   "DeviceValues":{
#                                   "1":{
#                                       "Name":"BARRIER OPERATOR",
#                                       "Value":"0",
#                                       "Type":"44"
#                                       }
#                                 }
#                   }


class AlmondPlusCover(CoverDevice):
    """Representation of a Almond+ cover."""

    def __init__(self, device):
        self._id = None
        self._device_id = None
        self._name = None
        self._state = None

        """Attributes"""
        self.friendly_device_type = None
        self.type = None
        self.location = None
        self.last_active_epoch = None
        self.model = None
        self.value_name = None
        self.value_value = None
        self.value_type = None
        self._state_before_move = None
        self._update_properties(device)

    def _update_properties(self, device):
        self._id = device.id
        self._device_id = device.device_id
        self._name = DOMAIN+"_"+device.name + '_' + device.id + '_' + device.device_id
        self._state = ''
        self.set_state(device.value_value)

        """Attributes"""
        self.friendly_device_type = device.friendly_device_type
        self.type = device.type
        self.location = device.location
        self.last_active_epoch = device.last_active_epoch
        self.model = device.model
        self.value_name = device.value_name
        self.value_value = device.value_value
        self.value_type = device.value_type


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

    @property
    def id(self):
        """Get the name of the pin."""
        return self._id

    @property
    def device_id(self):
        """Get the name of the pin."""
        return self._device_id

    @property
    def should_poll(self):
        return False

    @property
    def device_state_attributes(self):
        """Return the optional state attributes."""
        data = {}
        data["id"] = self._id
        data["device_id"] = self._device_id
        data["friendly_device_type"] = self.friendly_device_type
        data["type"] = self.type
        data["location"] = self.location
        data["last_active_epoch"] = self.last_active_epoch
        data["last_active_datetime"] = datetime.datetime.fromtimestamp(int(self.last_active_epoch))
        data["model"] = self.model
        data["value_name"] = self.value_name
        data["value_value"] = self.value_value
        data["value_type"] = self.value_type
        return data

    def update(self, device):
        _LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
        self._update_properties(device)
        self.schedule_update_ha_state()

    def set_state(self, value_value):
        _LOGGER.debug("Setting State -"+value_value+"-"+value_value.lower()+"-")
        if value_value.lower() == "0":
            self._state = STATE_CLOSED
            _LOGGER.debug("Setting closed "+self._state)
        elif value_value.lower() == "255":
            self._state = STATE_OPEN
            _LOGGER.debug("Setting open "+self._state)
        elif value_value.lower() == "254":
            self._state = STATE_OPENING
            _LOGGER.debug("Setting opening " + self._state)
        elif value_value.lower() == "252":
            self._state = STATE_CLOSING
            _LOGGER.debug("Setting closing " + self._state)
        else:
            self._state = 'unknown'
            _LOGGER.debug("Setting unknown " + self._state)
        _LOGGER.debug("Setting State Finish "+self._state)

    @property
    def is_opening(self):
        """Return if the cover is opening."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_OPENING]

    @property
    def is_closing(self):
        """Return if the cover is closing."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_CLOSING]

    @property
    def is_closed(self):
        """Return if the cover is closed."""
        if self._state in [STATE_UNKNOWN]:
            return None
        return self._state in [STATE_CLOSED, STATE_OPENING]

    def close_cover(self, **kwargs):
        """Close the cover."""
        if self._state not in [STATE_CLOSED, STATE_CLOSING]:
            _LOGGER.debug("Close Cover")
            self._state_before_move = self._state
            my_almond_plus.set_device(self.id, self.device_id, "0")

    def open_cover(self, **kwargs):
        """Open the cover."""
        if self._state not in [STATE_OPEN, STATE_OPENING]:
            _LOGGER.debug("Open Cover")
            self._state_before_move = self._state
            my_almond_plus.set_device(self.id, self.device_id, "255")

    def update(self, device):
        _LOGGER.debug("Switch Update -"+self._id+"-"+self.device_id+"-")
        self._update_properties(device)
        self.schedule_update_ha_state()

    @property
    def supported_features(self):
        """Flag supported features."""
        return SUPPORT_OPEN | SUPPORT_CLOSE

Here is the current error also …

Log Details (ERROR)
Sun Feb 24 2019 07:31:05 GMT-0600 (Central Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/discovery.py", line 161, in async_load_platform
    hass, component, hass_config)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/setup.py", line 56, in async_setup_component
    return await task  # type: ignore
  File "/usr/local/lib/python3.7/site-packages/homeassistant/setup.py", line 124, in _async_setup_component
    conf_util.async_process_component_config(hass, config, domain)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/config.py", line 719, in async_process_component_config
    for p_name, p_config in config_per_platform(config, domain):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/__init__.py", line 18, in config_per_platform
    for config_key in extract_domain_configs(config, domain):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/__init__.py", line 41, in extract_domain_configs
    return [key for key in config.keys() if pattern.match(key)]
AttributeError: 'Config' object has no attribute 'keys'

Here is my __init__.py

"""
Support for Almond+.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/almond_plus/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform

# -*- coding: utf-8 -*-
import threading
import websocket
import uuid
import json
import time
import traceback

REQUIREMENTS = ['websocket-client']

_LOGGER = logging.getLogger(__name__)


DOMAIN = "almond_plus"
DATA_ALMONDPLUS = "ALMONDPLUS"
CONF_URL = "url"


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


def setup(hass, config):
    """Set up the Almond+ component."""
    return_status = False
    try:
        global myhass_data
        myhass_data = {}
        myhass_data["config"] = config[DOMAIN]
        connect_url = config[DOMAIN][CONF_URL]
        myhass_data["almondplus_api"] = AlmondPlus(connect_url, almond_plus_call_back)
        myhass_data["almondplus_switch_entities"] = None
        myhass_data["almondplus_cover_entities"] = None
        hass.data[DATA_ALMONDPLUS] = myhass_data
        hass.data[DATA_ALMONDPLUS]["almondplus_api"].start()
        time.sleep(2)
        _LOGGER.debug("Test Almond+ setup: "+str(hass.data[DATA_ALMONDPLUS]["almondplus_api"].get_device_list()))
        _LOGGER.debug("Start Loading switch platform")
        load_platform(hass, 'switch', DOMAIN, None, hass.config)
        _LOGGER.debug("Done Loading switch platform")
        _LOGGER.debug("Start Loading cover platform")
        load_platform(hass, 'cover', DOMAIN, None, hass.config)
        _LOGGER.debug("Done Loading cover platform")
        time.sleep(2)

        switches = sorted(hass.states.async_entity_ids('switch'))
        _LOGGER.debug("switch list 1: "+str(switches))
        tmp_switches = myhass_data["almondplus_switch_entities"]
        _LOGGER.debug("switch list 2: "+str(tmp_switches))

        covers = sorted(hass.states.async_entity_ids('cover'))
        _LOGGER.debug("cover list 1: "+str(covers))
        tmp_covers = myhass_data["almondplus_cover_entities"]
        _LOGGER.debug("cover list 2: "+str(tmp_covers))

        return_status = True
    except Exception as e:
        _LOGGER.error("Error\n"
                      +"**************************\n"
                      + str(e) + "\n"
                      + traceback.format_exc()
                      + "**************************")
    _LOGGER.debug("Setup ended with "+str(return_status))
    return return_status


def almond_plus_call_back(almond_entities):
    tmp_switches = myhass_data["almondplus_switch_entities"]
    tmp_covers = myhass_data["almondplus_cover_entities"]
    tmp_almond_entities = myhass_data["almondplus_api"].get_device_list()
    for almond_key, almond_entity in almond_entities.items():
        _LOGGER.debug("id: "+almond_entity.id+" device_id: "
                      + almond_entity.device_id+" value: "
                      + almond_entity.value_value)
        tmp_id = almond_entity.id
        tmp_device_id = almond_entity.device_id
        tmp_key = tmp_id.zfill(4) + tmp_device_id.zfill(4)
        """
        First check through switches
        """
        for switch_entity in tmp_switches:
            if switch_entity.id == tmp_id and switch_entity.device_id == tmp_device_id:
                switch_entity.update(tmp_almond_entities[tmp_key])
        """
        Second check through covers
        """
        for cover_entity in tmp_covers:
            if cover_entity.id == tmp_id and cover_entity.device_id == tmp_device_id:
                cover_entity.update(tmp_almond_entities[tmp_key])



def stop_almond_plus(event):
    pass


def start_almond_plus(event):
    pass

I can not figure out why I am getting this error on my cover.py and not my switch.py. Going back through the trace the function that is calling the offending function is called config_per_platform. It is in the helpers __init__.py

Can someone help me out with what this function is supposed to do?

def config_per_platform(config: ConfigType,
                        domain: str) -> Iterable[Tuple[Any, Any]]:
    """Break a component config into different platforms.
    For example, will find 'switch', 'switch 2', 'switch 3', .. etc
    Async friendly.
    """
    for config_key in extract_domain_configs(config, domain):
        platform_config = config[config_key]

        if not platform_config:
            continue
        elif not isinstance(platform_config, list):
            platform_config = [platform_config]

        for item in platform_config:
            try:
                platform = item.get(CONF_PLATFORM)
            except AttributeError:
                platform = None

            yield platform, item

More detail info:
This code is at helpers/init.py
As you can see in previous post line 41 is where the error starts. And is called from line 18 which is the config_per_platform. This makes me think there is an issue with my config file (posted above) but I don’t know what it wants.

Your filename is incorrect. You are making a custom integration, so you should name it <config>/custom_components/almond_plus/cover.py. By calling it cover.py, you have been overriding the actual component.

You don’t have to copy any other files over, as there is no almond plus integration as part of HA.

After that, just add to your configuration.yaml:

cover:
  platform: almond_plus

@balloob Thank you so much. From the discord channel I know you guys had your battle. :slight_smile:
I getting ready to set some stage for the solutions but you can jump to the end if you want.

May be more info than you care, I have two components (switch, cover) that I am supporting. I had this all working back in July 2018 and I have not upgraded until dec 2018 and there were issues. Right now all my automation were running on the almond plus. The only reason I started my custom was because of all the issues with Open ZWave and barriers. Once I got things working in July I never move the automation over to HA. I started playing around with Lovelace. Even though HA now has it’s own zwave, I thought I would leave it on the almond, just to keep some load off of the HA. The truth may be my custom component my be a bigger drag. :slight_smile: Anyway almond has quit it’s support for weather, and therefore sun rise/set. So I started digging back into what was broke. I had three files. One had the API code and then did a manual discovery of the switch and cover. (load_platform(hass, 'switch', DOMAIN, None, hass.config) and load_platform(hass, 'cover', DOMAIN, None, hass.config)). I know that is breaking the API rule, but I was just testing. There were a lot of discussion back in the day on the almond forums about how the potential was there for the Almond Plus, but how Almond just kind of let it fizzle. That is where I learned about HA. There were a lot of people who wanted HA integration. So I thought I would do that. We use .NET at work so I had to learn python. Just another language. I learn better when I have a goal, so I thought I would kill two birds. Well I think all the Almond people have moved on, so I don’t see much use in making the code fit for main stream HA.

Back to the problem. So I have the three files named custom_components/almond_plus.py, custom_components/switches/almond_plus.py, custom_components/cover/almond_plus.py. In my config/packages was my almond_plus.yaml. Here is what it looked like:

almond_plus:
    url: !secret almond_plus_url

The API for the Almond Plus is a websocket and the URL is the password. So that is why I used the secret file. So the first thing I did was convert to .88 format. So I created the directory almond_plus. My API code was moved into it and renamed __init__.py. Then the code in the switch directory was moved into the almond_plus directory and called switch.py. Finally, the code in the cover directory was moved and called cover. Still had the same issues.

Solutions…
Remember this all ran in July 2018.
The first mistake, I was not passing a hass.config in the load_platform. So I added hass.config to both. The switch platform started working but the cover was missing a “key” in the config. For the life of me, I could not figure out what in the config it was missing. I kept thinking it was my code, till @elupus was helping me. I notice that according to the log, the cover was loaded, then the task error. We realized that it was not my code throwing the error. Just could not see it.

So now here is my yaml and it fixed the task error.

almond_plus:
    url: !secret almond_plus_url
cover:
    platform: almond_plus

I have to ask some curious questions, that if it takes too much time and if it is not helpful for others then I understand.

  1. Why did switch not have the same issue?

I was getting 2 entities for each one, but I thought it might be a bug in my code and I will look at it later. So thinking best pratices would be to add the switch, my yaml is now

almond_plus:
    url: !secret almond_plus_url
cover:
    platform: almond_plus
switch:
    platform: almond_plus

And now I have two entities for each switch. So I then rem out the load_platform for my cover and the cover still loaded and there was only one set of entities and they were correct. I rem out the load_platform for switches and now there is just one set of entities.

I know I put a lot here, but it is for anyone who might make the same mistake.
Thanks again @balloob for the nudge and @elupus for your time also.