Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Home Assistant 2022.4.0b0 - Unable to prepare setup for platform sonoff.fan: Platform not found #735

Closed
pimw1 opened this issue Mar 31, 2022 · 40 comments
Labels
enhancement New feature or request

Comments

@pimw1
Copy link

pimw1 commented Mar 31, 2022

Since Home Assistant 2022.4.0b0, the Sonoff integration is failing to load. The error message is the following:

Logger: homeassistant.setup
Source: setup.py:298
First occurred: 18:58:36 (3 occurrences)
Last logged: 18:58:41

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'SPEED_LOW' from 'homeassistant.components.fan' (/usr/src/homeassistant/homeassistant/components/fan/__init__.py)).

It was working fine with the latest non-beta version of Home Assistant (2022.3.8). I run a supervised install.
The functional impact is that my fans are not working at the moment.

@splerman
Copy link

splerman commented Apr 1, 2022

Same issue here.

@B-Hartley
Copy link

B-Hartley commented Apr 2, 2022

Set_Speed service has been deprecated and speed_low, speed_medium and speed_high has been deprecated too.
Code needs to be updated to use set_percentage with percentages mapped to speeds on the iFan.
Alternatively, maybe set_preset_mode could be used ?

also note........

These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
Please use the FanEntityFeature enum instead.

@B-Hartley
Copy link

B-Hartley commented Apr 3, 2022

I've done a pretty messy fix for my own use for now.
I'm not a python dev, so feel free to criticise and improve the code. Also I only use the ifan bit so for simplicity I've removed the other code. But this might help someone limp along until a real fix is made.

from typing import Optional, List

from homeassistant.components.fan import FanEntity, SUPPORT_SET_SPEED

# noinspection PyUnresolvedReferences
from . import DOMAIN, SCAN_INTERVAL
from .sonoff_main import EWeLinkEntity
from .switch import EWeLinkToggle

IFAN02_CHANNELS = [2, 3, 4]
IFAN02_STATES = {
    "off": {2: False},
    "low": {2: True, 3: False, 4: False},
    "medium": {2: True, 3: True, 4: False},
    "high": {2: True, 3: False, 4: True}
}

async def async_setup_platform(hass, config, add_entities,
                               discovery_info=None):
    if discovery_info is None:
        return

    deviceid = discovery_info['deviceid']
    channels = discovery_info['channels']
    registry = hass.data[DOMAIN]

    # iFan02 and iFan03 have the same uiid!
    uiid = registry.devices[deviceid].get('uiid')
    if uiid == 34 or uiid == 'fan_light':
        # only channel 2 is used for switching
        add_entities([SonoffFan02(registry, deviceid, [2])])

class SonoffFanBase(EWeLinkEntity, FanEntity):
    _percentage = 0

    @property
    def supported_features(self):
        return SUPPORT_SET_SPEED

    @property
    def speed_count(self) -> int:
        return 3
 
class SonoffFan02(SonoffFanBase):
    def _is_on_list(self, state: dict) -> List[bool]:
        # https://github.com/AlexxIT/SonoffLAN/issues/146
        switches = sorted(state['switches'], key=lambda i: i['outlet'])
        return [
            switches[channel - 1]['switch'] == 'on'
            for channel in IFAN02_CHANNELS
        ]

    def _update_handler(self, state: dict, attrs: dict):
        self._attrs.update(attrs)

        if 'switches' in state:
            mask = self._is_on_list(state)
            if mask[0]:
                if not mask[1] and not mask[2]:
                    self._percentage = 33
                elif mask[1] and not mask[2]:
                    self._percentage = 67                   
                elif not mask[1] and mask[2]:
                    self._percentage = 100                  
                else:
                    raise Exception("Wrong iFan02 state")
            else:
                self._percentage = 0

        self.schedule_update_ha_state()

    @property
    def percentage(self) -> Optional[int]:
        return self._percentage

    async def async_set_percentage(self, percentage: int) -> None:
        if percentage == 0:
          speed = "off"
        elif percentage <= 33:
          speed = "low"
        elif percentage <= 67:
          speed = "medium"
        else:
          speed = "high"
        channels = IFAN02_STATES.get(speed)
        await self._turn_bulk(channels)
        
    async def async_turn_on(self, percentage: Optional[int]=33, **kwargs):
        if percentage:
            await self.async_set_percentage(percentage)
        else:
            await self._turn_on()

    async def async_turn_off(self, **kwargs) -> None:
        await self._turn_off()


@pimw1
Copy link
Author

pimw1 commented Apr 3, 2022

Thanks! Perhaps someone can continue the work from here and merge the code.

@minimalco
Copy link

I've done a pretty messy fix for my own use for now. I'm not a python dev, so feel free to criticise and improve the code. Also I only use the ifan bit so for simplicity I've removed the other code. But this might help someone limp along until a real fix is made.

from typing import Optional, List

from homeassistant.components.fan import FanEntity, SUPPORT_SET_SPEED

# noinspection PyUnresolvedReferences
from . import DOMAIN, SCAN_INTERVAL
from .sonoff_main import EWeLinkEntity
from .switch import EWeLinkToggle

IFAN02_CHANNELS = [2, 3, 4]
IFAN02_STATES = {
    "off": {2: False},
    "low": {2: True, 3: False, 4: False},
    "medium": {2: True, 3: True, 4: False},
    "high": {2: True, 3: False, 4: True}
}

async def async_setup_platform(hass, config, add_entities,
                               discovery_info=None):
    if discovery_info is None:
        return

    deviceid = discovery_info['deviceid']
    channels = discovery_info['channels']
    registry = hass.data[DOMAIN]

    # iFan02 and iFan03 have the same uiid!
    uiid = registry.devices[deviceid].get('uiid')
    if uiid == 34 or uiid == 'fan_light':
        # only channel 2 is used for switching
        add_entities([SonoffFan02(registry, deviceid, [2])])

class SonoffFanBase(EWeLinkEntity, FanEntity):
    _percentage = 0

    @property
    def supported_features(self):
        return SUPPORT_SET_SPEED

    @property
    def speed_count(self) -> int:
        return 3
 
class SonoffFan02(SonoffFanBase):
    def _is_on_list(self, state: dict) -> List[bool]:
        # https://github.com/AlexxIT/SonoffLAN/issues/146
        switches = sorted(state['switches'], key=lambda i: i['outlet'])
        return [
            switches[channel - 1]['switch'] == 'on'
            for channel in IFAN02_CHANNELS
        ]

    def _update_handler(self, state: dict, attrs: dict):
        self._attrs.update(attrs)

        if 'switches' in state:
            mask = self._is_on_list(state)
            if mask[0]:
                if not mask[1] and not mask[2]:
                    self._percentage = 33
                elif mask[1] and not mask[2]:
                    self._percentage = 67                   
                elif not mask[1] and mask[2]:
                    self._percentage = 100                  
                else:
                    raise Exception("Wrong iFan02 state")
            else:
                self._percentage = 0

        self.schedule_update_ha_state()

    @property
    def percentage(self) -> Optional[int]:
        return self._percentage

    async def async_set_percentage(self, percentage: int) -> None:
        if percentage == 0:
          speed = "off"
        elif percentage <= 33:
          speed = "low"
        elif percentage <= 67:
          speed = "medium"
        else:
          speed = "high"
        channels = IFAN02_STATES.get(speed)
        await self._turn_bulk(channels)
        
    async def async_turn_on(self, percentage: Optional[int]=33, **kwargs):
        if percentage:
            await self.async_set_percentage(percentage)
        else:
            await self._turn_on()

    async def async_turn_off(self, **kwargs) -> None:
        await self._turn_off()

Works like a charm! thanx

@pimw1
Copy link
Author

pimw1 commented Apr 6, 2022

It works great here too. Thanks again!

If in doubt how to do this, it's pretty simple:

  1. Navigate to your sonofff custom_components folder

image
2. Edit fan.py and replace all its content by the code above
3. restart Home Assistant

@pimw1
Copy link
Author

pimw1 commented Apr 6, 2022

I'll keep this issue open, hoping that someone could deploy an official code fix.

@Petramuzammel88
Copy link

Hi - Not sure why im getting this issue. Is there anything to add in sonoff_main.py?

Logger: homeassistant.setup
Source: setup.py:298
First occurred: 11:59:44 AM (3 occurrences)
Last logged: 11:59:44 AM

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'EWeLinkEntity' from 'custom_components.sonoff.sonoff_main' (/config/custom_components/sonoff/sonoff_main.py)).

Thanks in advance!

@pimw1
Copy link
Author

pimw1 commented Apr 7, 2022

In order to help you, can you explain your situation?

  • Which steps did you exactly take to get this error?
  • Which Sonoff devices do you use via this integration?

@eugeniodb
Copy link

-Edited fan.py and replaced complete with above code.
-restarted
-I have 3 ifan03 and not working with the above error

Logger: homeassistant.setup
Source: setup.py:298
First occurred: 11:59:44 AM (3 occurrences)
Last logged: 11:59:44 AM

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'EWeLinkEntity' from 'custom_components.sonoff.sonoff_main' (/config/custom_components/sonoff/sonoff_main.py)).

@pimw1
Copy link
Author

pimw1 commented Apr 7, 2022

I've 3 ifan03 as well (no other Sonoff hardware), and took exactly the same steps as you. It is working fine for me. I've no idea why it is not working for you.

@Petramuzammel88
Copy link

from

Same situation. I'm using ifan03 as well.

@Petramuzammel88
Copy link

I've 3 ifan03 as well (no other Sonoff hardware), and took exactly the same steps as you. It is working fine for me. I've no idea why it is not working for you.

Mind sharing your sonoff_main.py ?

@pimw1
Copy link
Author

pimw1 commented Apr 7, 2022

Sure :)

import asyncio
import json
import logging
import os
import time
from typing import Optional, List, Callable

from aiohttp import ClientSession
from homeassistant.const import ATTR_BATTERY_LEVEL, MAJOR_VERSION, \
    MINOR_VERSION

from .sonoff_cloud import EWeLinkCloud
from .sonoff_local import EWeLinkLocal

_LOGGER = logging.getLogger(__name__)

ATTRS = ('local', 'cloud', 'rssi', 'humidity', 'temperature', 'power',
         'current', 'voltage', 'consumption', 'water', ATTR_BATTERY_LEVEL)


def load_cache(filename: str):
    """Load device list from file."""
    if os.path.isfile(filename):
        try:
            with open(filename, 'rt', encoding='utf-8') as f:
                return json.load(f)
        except:
            _LOGGER.error("Can't read cache file.")
    return None


def save_cache(filename: str, data: dict):
    """Save device list to file."""
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, separators=(',', ':'))


def get_attrs(state: dict) -> dict:
    return {k: state[k] for k in ATTRS if k in state}


class EWeLinkRegistry:
    """
    device:
      params: dict, init state
      uiid: Union[int, str], cloud or local type (strip, plug, light, rf)
      extra: dict, device manufacturer and model
      online: bool, cloud online state
      host: str, local IP (local online state)
      handlers: list, update handlers
    """
    devices: Optional[dict] = None

    # for bulk send switches command
    bulk_params = {}

    def __init__(self, session: ClientSession):
        self.cloud = EWeLinkCloud(session)
        self.local = EWeLinkLocal(session)

    def _registry_handler(self, deviceid: str, state: dict, sequence: str):
        """Feedback from local and cloud connections

        :param deviceid: example `1000abcdefg`
        :param state: example `{'switch': 'on'}`
        :param sequence: message serial number to verify uniqueness
        """
        device: dict = self.devices.get(deviceid)
        if not device:
            _LOGGER.warning(f"Unknown deviceid: {deviceid}")
            return

        # skip update with same sequence (from cloud and local or from local)
        if sequence:
            sequence = int(sequence)
            ts = time.time()
            # skip same and lower sequence in last 10 seconds
            if ('seq' in device and ts - device['seq_ts'] < 10 and
                    sequence <= device['seq']):
                _LOGGER.debug("Skip update with same sequence")
                return
            device['seq'] = sequence
            device['seq_ts'] = ts

        # check when cloud offline first time
        if state.get('cloud') == 'offline' and device.get('host'):
            coro = self.local.check_offline(deviceid)
            asyncio.create_task(coro)

        if 'handlers' in device:
            # TODO: right place?
            device['available'] = device.get('online') or device.get('host')

            attrs = get_attrs(state)
            try:
                for handler in device['handlers']:
                    handler(state, attrs)
            except Exception as e:
                _LOGGER.exception(f"Registry update error: {e}")

    def concat_devices(self, newdevices: dict):
        """Concat current device list with new device list."""
        if self.devices:
            for deviceid, devicecfg in newdevices.items():
                if deviceid in self.devices:
                    self.devices[deviceid].update(devicecfg)
                else:
                    self.devices[deviceid] = devicecfg

        else:
            self.devices = newdevices

    def cache_load_devices(self, cachefile: str):
        """Load devices from cache."""
        self.devices = load_cache(cachefile)

    async def cloud_login(self, username: str, password: str):
        return await self.cloud.login(username, password)

    async def cloud_load_devices(self, cachefile: str = None):
        """Load devices list from Cloud Servers."""
        newdevices = await self.cloud.load_devices()
        if newdevices is not None:
            newdevices = {p['deviceid']: p for p in newdevices}
            if cachefile:
                save_cache(cachefile, newdevices)
            self.devices = newdevices

    async def cloud_start(self):
        if self.devices is None:
            self.devices = {}

        await self.cloud.start([self._registry_handler], self.devices)

    async def local_start(self, handlers: List[Callable], zeroconf):
        if self.devices is None:
            self.devices = {}

        if handlers:
            handlers.append(self._registry_handler)
        else:
            handlers = [self._registry_handler]

        self.local.start(handlers, self.devices, zeroconf)

    async def stop(self, *args):
        # TODO: do something
        pass

    async def send(self, deviceid: str, params: dict):
        """Send command to device."""
        seq = str(int(time.time() * 1000))

        device: dict = self.devices[deviceid]
        can_local = self.local.started and device.get('host')
        can_cloud = self.cloud.started and device.get('online')

        state = {}

        if can_local and can_cloud:
            # try to send a command locally (wait no more than a second)
            state['local'] = await self.local.send(deviceid, params, seq, 1)

            # otherwise send a command through the cloud
            if state['local'] != 'online':
                state['cloud'] = await self.cloud.send(deviceid, params, seq)
                if state['cloud'] != 'online':
                    coro = self.local.check_offline(deviceid)
                    asyncio.create_task(coro)

        elif can_local:
            state['local'] = await self.local.send(deviceid, params, seq, 5)
            if state['local'] != 'online':
                coro = self.local.check_offline(deviceid)
                asyncio.create_task(coro)

        elif can_cloud:
            state['cloud'] = await self.cloud.send(deviceid, params, seq)

        else:
            return

        # update device attrs
        self._registry_handler(deviceid, state, None)

    async def bulk(self, deviceid: str, params: dict):
        """For bulk send switches command. You cannot send two commands
        simultaneously to different channels. This causes errors on local and
        cloud connections.

        https://github.com/AlexxIT/SonoffLAN/issues/139
        https://github.com/AlexxIT/SonoffLAN/issues/151
        """
        assert 'switches' in params, params

        if deviceid not in self.bulk_params:
            self.bulk_params[deviceid] = params
            await asyncio.sleep(0.1)
            return await self.send(deviceid, self.bulk_params.pop(deviceid))

        else:
            self.bulk_params[deviceid]['switches'] += params['switches']


class EWeLinkBase:
    registry: EWeLinkRegistry = None
    deviceid: str = None
    channels: list = None
    _attrs: dict = None
    _name: str = None
    _is_on: bool = None
    _is_th_3_4_0: bool = False

    def __init__(self, registry: EWeLinkRegistry, deviceid: str,
                 channels: list = None):
        self.registry = registry
        self.deviceid = deviceid
        self.channels = channels

    def _init(self, force_refresh: bool = True) -> dict:
        device: dict = self.registry.devices[self.deviceid]

        # Присваиваем имя устройства только на этом этапе, чтоб в `entity_id`
        # было "sonoff_{unique_id}". Если имя присвоить в конструкторе - в
        # `entity_id` попадёт имя в латинице.
        # TODO: fix init name
        if self.channels and len(self.channels) == 1:
            ch = str(self.channels[0] - 1)
            self._name = device.get('tags', {}).get('ck_channel_name', {}). \
                             get(ch) or device.get('name')
        else:
            self._name = device.get('name')

        state = device['params']

        self._attrs = device['extra'] or {}
        # don't know if deviceType only in Sonoff TH
        # https://github.com/AlexxIT/SonoffLAN/issues/158
        self._is_th_3_4_0 = 'deviceType' in state

        if force_refresh:
            attrs = get_attrs(state)
            self._update_handler(state, attrs)

        # init update_handler
        device['handlers'].append(self._update_handler)

        return device

    def _is_on_list(self, state: dict) -> List[bool]:
        if self.channels:
            # very rarely channels can be reversed
            # https://github.com/AlexxIT/SonoffLAN/issues/146
            return [
                switch['switch'] == 'on'
                for switch in state['switches']
                if switch['outlet'] + 1 in self.channels
            ]
        else:
            return [state['switch'] == 'on']

    def _update_handler(self, state: dict, attrs: dict):
        raise NotImplemented

    async def _turn_on(self):
        if self.channels:
            switches = [
                {'outlet': channel - 1, 'switch': 'on'}
                for channel in self.channels
            ]
            await self.registry.bulk(self.deviceid, {'switches': switches})
        elif self._is_th_3_4_0:
            await self.registry.send(self.deviceid, {
                'switch': 'on', 'mainSwitch': 'on', 'deviceType': 'normal'})
        else:
            await self.registry.send(self.deviceid, {'switch': 'on'})

    async def _turn_off(self):
        if self.channels:
            switches = [
                {'outlet': channel - 1, 'switch': 'off'}
                for channel in self.channels
            ]
            await self.registry.bulk(self.deviceid, {'switches': switches})
        elif self._is_th_3_4_0:
            await self.registry.send(self.deviceid, {
                'switch': 'off', 'mainSwitch': 'off', 'deviceType': 'normal'})
        else:
            await self.registry.send(self.deviceid, {'switch': 'off'})

    async def _turn_bulk(self, channels: dict):
        """Включает, либо выключает указанные каналы.

        :param channels: Словарь каналов, ключ - номер канала, значение - bool
        """
        switches = [
            {'outlet': channel - 1, 'switch': 'on' if switch else 'off'}
            for channel, switch in channels.items()
        ]
        await self.registry.send(self.deviceid, {'switches': switches})


class EWeLinkEntity(EWeLinkBase):
    @property
    def should_poll(self):
        return False

    @property
    def unique_id(self):
        return self.deviceid

    @property
    def name(self):
        return self._name

    @property
    def extra_state_attributes(self):
        return self._attrs

    @property
    def available(self):
        device: dict = self.registry.devices[self.deviceid]
        return device['available']

    async def async_added_to_hass(self):
        self._init()


if [MAJOR_VERSION, MINOR_VERSION] < [2021, 4]:
    # Backwards compatibility for "device_state_attributes"
    # deprecated in 2021.4, add warning in 2021.6, remove in 2021.10
    p = getattr(EWeLinkEntity, 'extra_state_attributes')
    setattr(EWeLinkEntity, 'device_state_attributes', p)

@eugeniodb
Copy link

Now for me it worked

  • I updated the SmartIR in Hacs
  • -restarted
    *It has the code provided above.

All 3 iFan03 working fine.

@RoadXY
Copy link

RoadXY commented Apr 7, 2022

Duplicate of #725

@DravenSA
Copy link

DravenSA commented Apr 7, 2022

Can i ask what cards you are all using,
I had Fan Control Entity Row, but although the edited code works for a slider percentage, this card still says "failed to call service fan/set_speed. Service now found",
I am assuming this is the card

@chemelli74
Copy link

Duplicate of #725

@splerman
Copy link

splerman commented Apr 7, 2022

B-Hartley’s fix, above, works for iFan04 as well.

@pimw1
Copy link
Author

pimw1 commented Apr 7, 2022

Can i ask what cards you are all using, I had Fan Control Entity Row, but although the edited code works for a slider percentage, this card still says "failed to call service fan/set_speed. Service now found", I am assuming this is the card

I use a plain Entities Card.

@itamarb
Copy link

itamarb commented Apr 8, 2022

The edited code works, so now i can control my sonoff fans again, thanks!
But I was also using 'custom:slider-entity-row' to select the fan speed with a slider in an entities card, but that doesn't work anymore, any ideas?

@pepe59
Copy link

pepe59 commented Apr 8, 2022

It doesn't work for me when using a diffuser.

Logger: homeassistant.setup
Source: setup.py:298
First occurred: 21:19:20 (1 occurrences)
Last logged: 21:19:20

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'EWeLinkEntity' from 'custom_components.sonoff.sonoff_main' (/config/custom_components/sonoff/sonoff_main.py)).

@Qoheleth
Copy link

Qoheleth commented Apr 8, 2022

I've done a pretty messy fix for my own use for now. I'm not a python dev, so feel free to criticise and improve the code. Also I only use the ifan bit so for simplicity I've removed the other code. But this might help someone limp along until a real fix is made.

from typing import Optional, List

from homeassistant.components.fan import FanEntity, SUPPORT_SET_SPEED

# noinspection PyUnresolvedReferences
from . import DOMAIN, SCAN_INTERVAL
from .sonoff_main import EWeLinkEntity
from .switch import EWeLinkToggle

IFAN02_CHANNELS = [2, 3, 4]
IFAN02_STATES = {
    "off": {2: False},
    "low": {2: True, 3: False, 4: False},
    "medium": {2: True, 3: True, 4: False},
    "high": {2: True, 3: False, 4: True}
}

async def async_setup_platform(hass, config, add_entities,
                               discovery_info=None):
    if discovery_info is None:
        return

    deviceid = discovery_info['deviceid']
    channels = discovery_info['channels']
    registry = hass.data[DOMAIN]

    # iFan02 and iFan03 have the same uiid!
    uiid = registry.devices[deviceid].get('uiid')
    if uiid == 34 or uiid == 'fan_light':
        # only channel 2 is used for switching
        add_entities([SonoffFan02(registry, deviceid, [2])])

class SonoffFanBase(EWeLinkEntity, FanEntity):
    _percentage = 0

    @property
    def supported_features(self):
        return SUPPORT_SET_SPEED

    @property
    def speed_count(self) -> int:
        return 3
 
class SonoffFan02(SonoffFanBase):
    def _is_on_list(self, state: dict) -> List[bool]:
        # https://github.com/AlexxIT/SonoffLAN/issues/146
        switches = sorted(state['switches'], key=lambda i: i['outlet'])
        return [
            switches[channel - 1]['switch'] == 'on'
            for channel in IFAN02_CHANNELS
        ]

    def _update_handler(self, state: dict, attrs: dict):
        self._attrs.update(attrs)

        if 'switches' in state:
            mask = self._is_on_list(state)
            if mask[0]:
                if not mask[1] and not mask[2]:
                    self._percentage = 33
                elif mask[1] and not mask[2]:
                    self._percentage = 67                   
                elif not mask[1] and mask[2]:
                    self._percentage = 100                  
                else:
                    raise Exception("Wrong iFan02 state")
            else:
                self._percentage = 0

        self.schedule_update_ha_state()

    @property
    def percentage(self) -> Optional[int]:
        return self._percentage

    async def async_set_percentage(self, percentage: int) -> None:
        if percentage == 0:
          speed = "off"
        elif percentage <= 33:
          speed = "low"
        elif percentage <= 67:
          speed = "medium"
        else:
          speed = "high"
        channels = IFAN02_STATES.get(speed)
        await self._turn_bulk(channels)
        
    async def async_turn_on(self, percentage: Optional[int]=33, **kwargs):
        if percentage:
            await self.async_set_percentage(percentage)
        else:
            await self._turn_on()

    async def async_turn_off(self, **kwargs) -> None:
        await self._turn_off()

I can confirm that this works on my system as well. I've got iFan03's and am accessing them through custom:button_card buttons.
Thanks also to pimw1 for his walk-through. I used the Samba integration to edit the fan.py file and restarted HA as specified.

@SirGoodenough
Copy link

I have this app for 1 sonoff micro, and this fan error is still in my logs. Grrr...
Tasmota on all my fans.

@MJWMJW2
Copy link

MJWMJW2 commented Apr 9, 2022

thank you @B-Hartley worked for me also. Used also with fan-percent-button-row and its working great along with automations using set percentage.

@Phil-m-pinto
Copy link

I will try this code as I have the same problem with iFan03

@rodders99
Copy link

rodders99 commented Apr 10, 2022

I'm also getting the following error after trying the suggestion from @minimalco

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'EWeLinkEntity' from 'custom_components.sonoff.sonoff_main' (/config/custom_components/sonoff/sonoff_main.py)).

@AlexxIT AlexxIT added the enhancement New feature or request label Apr 10, 2022
@eplantequebec
Copy link

eplantequebec commented Apr 10, 2022

Finally, it works. I had the "cannot import name 'EWeLinkEntity'" problem.

Thanks to @B-Hartley for the code and @pimw1 who showed us his sonoff_main file! I saw that the file was different from mine. It seems that the latest version from HACS (2.4.3) is not the same as Gihut one. This is what I did.

  • In HACS>Integration>Sonoff LAN, I ask to redownload. I selected the Master version and NOT the 2.4.3 version.
  • I modified the fan.py with the B-Hartley code.
  • Reboot
    And everything works!

@dammx
Copy link

dammx commented Apr 10, 2022

Finally, it works. I had the "cannot import name 'EWeLinkEntity'" problem.

Thanks to @B-Hartley for the code and @pimw1 who showed us his sonoff_main file! I saw that the file was different from mine. It seems that the latest version from HACS (2.4.3) is not the same as Gihut one. This is what I did.

  • In HACS>Integration>Sonoff LAN, I ask to redownload. I selected the Master version and NOT the 2.4.3 version.
  • I modified the fan.py with the B-Hartley code.
  • Reboot
    And everything works!

Thank you very much, it also worked for me with iFan02 your method, ;)

@B-Hartley
Copy link

I've created a file in:
https://github.com/B-Hartley/SonoffLAN/blob/BHartley-2022.04/custom_components/sonoff/fan.py

This one has the other fan platforms in it, and I've attempted to fix the diffuser fan as well (although I don't have one so can't test).
Still not sure it is coded "correctly", so will leave it in my fork for now.

@B-Hartley
Copy link

It doesn't work for me when using a diffuser.

Logger: homeassistant.setup
Source: setup.py:298
First occurred: 21:19:20 (1 occurrences)
Last logged: 21:19:20

Unable to prepare setup for platform sonoff.fan: Platform not found (cannot import name 'EWeLinkEntity' from 'custom_components.sonoff.sonoff_main' (/config/custom_components/sonoff/sonoff_main.py)).

Try the new version in my fork....
https://github.com/B-Hartley/SonoffLAN/blob/BHartley-2022.04/custom_components/sonoff/fan.py

@pepe59
Copy link

pepe59 commented Apr 10, 2022

I installed the master and then your modified code.
The diffuser fan entity has formed, but it is behaving strangely.
The switch turns on automatically and the diffuser does not run. If I move the speed slider, the diffuser turns on. If I turn off the switch, the diffuser will turn off and the switch will turn itself on again and the diffuser will not run. I turn it on again by setting the speed control slider and it doesn't matter which position.

@pepe59
Copy link

pepe59 commented Apr 10, 2022

I would be happy if it just goes on / off. Thanks to so far less progress and great work.

@B-Hartley
Copy link

I've made a tweak, there was an error.
There is also a couple of lines of code that might help, don't know, but worth a try, see comments.

@pepe59
Copy link

pepe59 commented Apr 10, 2022

Same switch behavior on last code test. The progress is that I turn on the diffuser with the speed slider. If the speed slider moves to 0, the diffuser will turn off. The switch still works the same way. If the diffuser is on with the speed slider, I can turn it off with the switch. The switch automatically returns to the on position and the diffuser remains off. The switch only works for switching off, but switches on again automatically. No log errors.

@AlexxIT
Copy link
Owner

AlexxIT commented Apr 11, 2022

Fixed in v2.4.7

@AlexxIT AlexxIT closed this as completed Apr 11, 2022
@B-Hartley
Copy link

Great, thanks.

Interesting to see how it is done "properly" compared with how I did it. Pleased that it was vaguely similar.

Is it ok that you are using low, med & high values on the diffuser even though it only has two speeds as far as I can tell? Will it just get pushed onto the correct values ?

@AlexxIT
Copy link
Owner

AlexxIT commented Apr 11, 2022

This is not very properly. This is just fast solution. Your solution also great.
My solution with diffuser is bad. But it is quick.
I hope I will fix all problems in 2nd version.

@WillemGoosen
Copy link

Hi All
Looking for help. Not sure if I can post it here.
I implemented the fix that was shared on this forum and had no problems with my fans since then.
I am running one iFan02 and two iFan03.
However, with the SONOFF Lan v3.0.0 & v3.0.1 & v3.0.2 upgrade my iFan02 is not working. I can see the entities and they even display when active if turned on from ewelink, but I am unable to do so in HA.
Home Assistant 2022.4.7
iFan02 firmware 3.5.1

I even restored a backup before I applied the above-mentioned fix and then did the SONOFF LAN upgrade again but still get the same error.

Currently on SONOFF LAN 2.4.7 and everything runs as it should.

Thanks much appreciated.

@AlexxIT
Copy link
Owner

AlexxIT commented May 3, 2022

@WillemGoosen go here and help to me with logs #786

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests