Cheaterdev / clear_grass-ha

Xiaomi ClearGrass Air Detector integration into Home Assistant
59 stars 18 forks source link

Stopped working after upgrading a ClearGrass device firmware #13

Open ildar170975 opened 2 years ago

ildar170975 commented 2 years ago

After updating a ClearGrass Air Monitor to 4.1.8_0244 the device is not available in HA. It is still available in MiHome.

pkmcnc commented 2 years ago

Confirm! I updated 2 minutes before I read this message )

ildar170975 commented 2 years ago

With a Xiaomi Miio integration - same unavailable sensors.

akarpych commented 2 years ago

Yes, they are unavailable for me. In any integration.

ildar170975 commented 2 years ago

FYI Related issues with an official integration: https://github.com/home-assistant/core/issues/76646 https://github.com/rytilahti/python-miio/issues/1489

akarpych commented 2 years ago

I removed everything related to the battery from the integration and now all values except the battery have appeared.

ildar170975 commented 2 years ago

I removed everything related to the battery

Could you explain how you did it?

akarpych commented 2 years ago

Delete all rows and blocks with the battery. ` import logging from collections import defaultdict

import click import json from miio.click_common import command, format_output from miio.device import Device

_LOGGER = logging.getLogger(name)

MODEL_AIRQUALITYMONITOR_S1 = 'cgllc.airmonitor.s1'

AVAILABLE_PROPERTIES_COMMON = [ 'battery_state', 'co2', 'humidity', 'pm25' , 'temperature', 'tvoc']

AVAILABLE_PROPERTIES = { MODEL_AIRQUALITYMONITOR_S1: AVAILABLE_PROPERTIES_COMMON, }

class AirQualityMonitorStatus: """Container of air quality monitor status."""

def __init__(self, data):
    self.data = data

@property
def power(self) -> str:
    """Current power state."""
    return self.data["power"]

@property
def is_on(self) -> bool:
    """Return True if the device is turned on."""
    return self.power == "on"

@property
def temperature(self) -> bool:
    """Return True if the device's usb is on."""
    return self.data["temperature"] 

@property
def humidity(self) -> int:
    """Air quality index value. (0...600)."""
    return self.data["humidity"]

@property
def co2(self) -> int:
    """Air quality index value. (0...600)."""
    return self.data["co2"]

@property
def tvoc(self) -> int:
    """Current battery level (0...100)."""
    return self.data["tvoc"]

@property
def pm25(self) -> bool:
    """Display a clock instead the AQI."""
    return self.data["pm25"]

@property
def battery_state(self) -> str:
    """Return the begin of the night time."""
    return self.data["battery_state"]

def __repr__(self) -> str:
    s = "<AirQualityMonitorStatus humidity=%s, " \
        "co2=%s, " \
        "tvoc=%s, " \
        "pm25=%s, " \
        "battery=%s, " \
        "battery_state=%s>" % \
        (self.humidity,
         self.co2,
         self.tvoc,
         self.pm25,
         self.battery_state,
         )
    return s

def __json__(self):
    return self.data

class AirQualityMonitor(Device): """Xiaomi PM2.5 Air Quality Monitor.""" def init(self, ip: str = None, token: str = None, start_id: int = 0, debug: int = 0, lazy_discover: bool = True, model: str = MODEL_AIRQUALITYMONITOR_S1) -> None: super().init(ip, token, start_id, debug, lazy_discover, model=model)

    if model not in AVAILABLE_PROPERTIES:
        _LOGGER.error("Device model %s unsupported. Falling back to %s.", model, self.model)

    self.device_info = None

@command(
    default_output=format_output(
        ""
    )
)
def status(self) -> AirQualityMonitorStatus:
    """Return device status."""

    properties = AVAILABLE_PROPERTIES[self.model]

    values = self.send(
        "get_prop",
        properties
    )

    properties_count = len(properties)
    values_count = len(values)
    if properties_count != values_count:
        _LOGGER.error(
            "Count (%s) of requested properties does not match the "
            "count (%s) of received values.",
            properties_count, values_count)

    return AirQualityMonitorStatus(
        defaultdict(lambda: None, values))

@command(
    default_output=format_output("Powering on"),
)
def on(self):
    """Power on."""
    return self.send("set_power", ["on"])

@command(
    default_output=format_output("Powering off"),
)
def off(self):
    """Power off."""
    return self.send("set_power", ["off"])

@command(
    click.argument("display_clock", type=bool),
    default_output=format_output(
        lambda led: "Turning on display clock"
        if led else "Turning off display clock"
    )
)
def set_display_clock(self, display_clock: bool):
    """Enable/disable displaying a clock instead the AQI."""
    if display_clock:
        self.send("set_time_state", ["on"])
    else:
        self.send("set_time_state", ["off"])

@command(
    click.argument("auto_close", type=bool),
    default_output=format_output(
        lambda led: "Turning on auto close"
        if led else "Turning off auto close"
    )
)
def set_auto_close(self, auto_close: bool):
    """Purpose unknown."""
    if auto_close:
        self.send("set_auto_close", ["on"])
    else:
        self.send("set_auto_close", ["off"])

@command(
    click.argument("night_mode", type=bool),
    default_output=format_output(
        lambda led: "Turning on night mode"
        if led else "Turning off night mode"
    )
)
def set_night_mode(self, night_mode: bool):
    """Decrease the brightness of the display."""
    if night_mode:
        self.send("set_night_state", ["on"])
    else:
        self.send("set_night_state", ["off"])

@command(
    click.argument("begin_hour", type=int),
    click.argument("begin_minute", type=int),
    click.argument("end_hour", type=int),
    click.argument("end_minute", type=int),
    default_output=format_output(
        "Setting night time to {begin_hour}:{begin_minute} - {end_hour}:{end_minute}")
)
def set_night_time(self, begin_hour: int, begin_minute: int,
                   end_hour: int, end_minute: int):
    """Enable night mode daily at bedtime."""
    begin = begin_hour * 3600 + begin_minute * 60
    end = end_hour * 3600 + end_minute * 60

    if begin < 0 or begin > 86399 or end < 0 or end > 86399:
        raise Exception("Begin or/and end time invalid.")

    self.send("set_night_time", [begin, end])

"""Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" import logging

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(name)

DEFAULT_NAME = 'clear_grass' DATA_KEY = 'sensor.clear_grass'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, })

ATTR_TEMPERATURE = 'temperature' ATTR_HUMIDITY = 'humidity' ATTR_CO2 = 'co2' ATTR_TVOC = 'tvoc' ATTR_PM25 = 'pm25'

ATTR_BATTERY_STATE = 'battery_state'

ATTR_MODEL = 'model'

SUCCESS = ['ok']

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the sensor from config.""" if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {}

host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
token = config.get(CONF_TOKEN)

_LOGGER.info("Initializing with host %s (token %s...)", host, token[:5])

try:
    air_quality_monitor = AirQualityMonitor(host, token)
    device_info = air_quality_monitor.info()
    model = device_info.model
    unique_id = "{}-{}".format(model, device_info.mac_address)
    _LOGGER.info("%s %s %s detected",
                 model,
                 device_info.firmware_version,
                 device_info.hardware_version)
    device = ClearGrassMonitor(
        name, air_quality_monitor, model, unique_id)
except Exception:
    raise PlatformNotReady

hass.data[DATA_KEY][host] = device
async_add_entities([device], update_before_add=True)

class ClearGrassMonitor(Entity): """Representation of a Xiaomi Air Quality Monitor."""

def __init__(self, name, device, model, unique_id):
    """Initialize the entity."""
    self._name = name
    self._device = device
    self._model = model
    self._unique_id = unique_id

    self._icon = 'mdi:cloud'
    self._unit_of_measurement = 'AQI'
    self._available = None
    self._state = None
    self._state_attrs = {
        ATTR_TEMPERATURE: None,
        ATTR_HUMIDITY: None,
        ATTR_CO2: None,
        ATTR_TVOC: None,
        #ATTR_PM25: None,
        ATTR_BATTERY_STATE: None,
        ATTR_MODEL: self._model,
    }

@property
def should_poll(self):
    """Poll the miio device."""
    return True

@property
def unique_id(self):
    """Return an unique ID."""
    return self._unique_id

@property
def name(self):
    """Return the name of this entity, if any."""
    return self._name

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

@property
def icon(self):
    """Return the icon to use for device if any."""
    return self._icon

@property
def available(self):
    """Return true when state is known."""
    return self._available

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

@property
def extra_state_attributes(self):
    """Return the state attributes of the device."""
    return self._state_attrs

async def async_update(self):
    """Fetch state from the miio device."""        
    try:
        state = await self.hass.async_add_executor_job(self._device.status)

        self._available = True
        self._state = state.pm25
        self._state_attrs.update({
            ATTR_TEMPERATURE: state.temperature,
            ATTR_HUMIDITY: state.humidity,
            ATTR_CO2: state.co2,
            ATTR_TVOC: state.tvoc,
          #  ATTR_PM25:state.pm25,
            ATTR_BATTERY_STATE:state.battery_state,
        })

    except Exception as ex:
        self._available = False
        _LOGGER.error("Got exception while fetching the state: %s", ex) `
ildar170975 commented 2 years ago

i.e. by changing a source code.... So sad.

akarpych commented 2 years ago

currently, only so.

Cheaterdev commented 2 years ago

@akarpych Thanks for help!

So I've removed battery state request from the code but haven't tested it. You can try the hotfix form here: https://github.com/Cheaterdev/clear_grass-ha/tree/firmware-update

And if it works - I'll update the main branch.

ildar170975 commented 2 years ago

And if it works - I'll update the main branch.

Sorry, does it mean that battery info will not be available? battery level, battery_charging

Cheaterdev commented 2 years ago

And if it works - I'll update the main branch.

Sorry, does it mean that battery info will not be available? battery level, battery_charging

As far I can see - it's should be requested in the other way. Currently I'm in the Ukrainian army so I don't have a possibility to make and test a proper fix. At least other values will be presented.

ildar170975 commented 2 years ago

Currently I'm in the Ukrainian army so I don't have a possibility to make and test a proper fix

Take care if yourself!

ildar170975 commented 2 years ago

You can try the hotfix form here:

What I did is:

  1. Open sensor.py, manifest.json as "raw".
  2. Copy/paste content into sensor.py, manifest.json files.
  3. I see an error while checking config: image

Update: very strange - see for yourself:

Update: not, that was not a mistake - same error I see in logs now after HA restart:

2022-08-14 16:33:32.239 ERROR (MainThread) [homeassistant.loader] Unexpected exception importing platform custom_components.clear_grass.sensor
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/loader.py", line 657, in get_platform
cache[full_name] = self._import_platform(platform_name)
File "/usr/src/homeassistant/homeassistant/loader.py", line 674, in _import_platform
return importlib.import_module(f"{self.pkg_path}.{platform_name}")
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 879, in exec_module
File "<frozen importlib._bootstrap_external>", line 1017, in get_code
File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/config/custom_components/clear_grass/sensor.py", line 76
"battery_state=%s>" % \
IndentationError: unexpected indent

Then I just removed this line: image Then after HA restart there is no errors in log - but also no data for Cleargrass sensor.

akarpych commented 2 years ago

What I posted above works. It does not show only the battery %

ildar170975 commented 2 years ago

@akarpych could you repost your code again? That post contains a strangely formatted text.

akarpych commented 2 years ago

from collections import defaultdict

import click
import json
from miio.click_common import command, format_output
from miio.device import Device

_LOGGER = logging.getLogger(__name__)

MODEL_AIRQUALITYMONITOR_S1 = 'cgllc.airmonitor.s1'

AVAILABLE_PROPERTIES_COMMON = [ 'battery_state', 'co2', 'humidity', 'pm25' , 'temperature', 'tvoc']

AVAILABLE_PROPERTIES = {
    MODEL_AIRQUALITYMONITOR_S1: AVAILABLE_PROPERTIES_COMMON,
}

class AirQualityMonitorStatus:
    """Container of air quality monitor status."""

    def __init__(self, data):
        self.data = data

    @property
    def power(self) -> str:
        """Current power state."""
        return self.data["power"]

    @property
    def is_on(self) -> bool:
        """Return True if the device is turned on."""
        return self.power == "on"

    @property
    def temperature(self) -> bool:
        """Return True if the device's usb is on."""
        return self.data["temperature"] 

    @property
    def humidity(self) -> int:
        """Air quality index value. (0...600)."""
        return self.data["humidity"]

    @property
    def co2(self) -> int:
        """Air quality index value. (0...600)."""
        return self.data["co2"]

    @property
    def tvoc(self) -> int:
        """Current battery level (0...100)."""
        return self.data["tvoc"]

    @property
    def pm25(self) -> bool:
        """Display a clock instead the AQI."""
        return self.data["pm25"]

    @property
    def battery_state(self) -> str:
        """Return the begin of the night time."""
        return self.data["battery_state"]

    def __repr__(self) -> str:
        s = "<AirQualityMonitorStatus humidity=%s, " \
            "co2=%s, " \
            "tvoc=%s, " \
            "pm25=%s, " \
            "battery_state=%s>" % \
            (self.humidity,
             self.co2,
             self.tvoc,
             self.pm25,
             self.battery_state,
             )
        return s

    def __json__(self):
        return self.data

class AirQualityMonitor(Device):
    """Xiaomi PM2.5 Air Quality Monitor."""
    def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
                 debug: int = 0, lazy_discover: bool = True,
                 model: str = MODEL_AIRQUALITYMONITOR_S1) -> None:
        super().__init__(ip, token, start_id, debug, lazy_discover, model=model)

        if model not in AVAILABLE_PROPERTIES:
            _LOGGER.error("Device model %s unsupported. Falling back to %s.", model, self.model)

        self.device_info = None

    @command(
        default_output=format_output(
            ""
        )
    )
    def status(self) -> AirQualityMonitorStatus:
        """Return device status."""

        properties = AVAILABLE_PROPERTIES[self.model]

        values = self.send(
            "get_prop",
            properties
        )

        properties_count = len(properties)
        values_count = len(values)
        if properties_count != values_count:
            _LOGGER.error(
                "Count (%s) of requested properties does not match the "
                "count (%s) of received values.",
                properties_count, values_count)

        return AirQualityMonitorStatus(
            defaultdict(lambda: None, values))

    @command(
        default_output=format_output("Powering on"),
    )
    def on(self):
        """Power on."""
        return self.send("set_power", ["on"])

    @command(
        default_output=format_output("Powering off"),
    )
    def off(self):
        """Power off."""
        return self.send("set_power", ["off"])

    @command(
        click.argument("display_clock", type=bool),
        default_output=format_output(
            lambda led: "Turning on display clock"
            if led else "Turning off display clock"
        )
    )
    def set_display_clock(self, display_clock: bool):
        """Enable/disable displaying a clock instead the AQI."""
        if display_clock:
            self.send("set_time_state", ["on"])
        else:
            self.send("set_time_state", ["off"])

    @command(
        click.argument("auto_close", type=bool),
        default_output=format_output(
            lambda led: "Turning on auto close"
            if led else "Turning off auto close"
        )
    )
    def set_auto_close(self, auto_close: bool):
        """Purpose unknown."""
        if auto_close:
            self.send("set_auto_close", ["on"])
        else:
            self.send("set_auto_close", ["off"])

    @command(
        click.argument("night_mode", type=bool),
        default_output=format_output(
            lambda led: "Turning on night mode"
            if led else "Turning off night mode"
        )
    )
    def set_night_mode(self, night_mode: bool):
        """Decrease the brightness of the display."""
        if night_mode:
            self.send("set_night_state", ["on"])
        else:
            self.send("set_night_state", ["off"])

    @command(
        click.argument("begin_hour", type=int),
        click.argument("begin_minute", type=int),
        click.argument("end_hour", type=int),
        click.argument("end_minute", type=int),
        default_output=format_output(
            "Setting night time to {begin_hour}:{begin_minute} - {end_hour}:{end_minute}")
    )
    def set_night_time(self, begin_hour: int, begin_minute: int,
                       end_hour: int, end_minute: int):
        """Enable night mode daily at bedtime."""
        begin = begin_hour * 3600 + begin_minute * 60
        end = end_hour * 3600 + end_minute * 60

        if begin < 0 or begin > 86399 or end < 0 or end > 86399:
            raise Exception("Begin or/and end time invalid.")

        self.send("set_night_time", [begin, end])

"""Support for Xiaomi Mi Air Quality Monitor (PM2.5)."""
import logging

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = 'clear_grass'
DATA_KEY = 'sensor.clear_grass'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)),
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

ATTR_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity'
ATTR_CO2 = 'co2'
ATTR_TVOC = 'tvoc'
ATTR_PM25 = 'pm25'

ATTR_BATTERY_STATE = 'battery_state'

ATTR_MODEL = 'model'

SUCCESS = ['ok']

async def async_setup_platform(hass, config, async_add_entities,
                               discovery_info=None):
    """Set up the sensor from config."""
    if DATA_KEY not in hass.data:
        hass.data[DATA_KEY] = {}

    host = config.get(CONF_HOST)
    name = config.get(CONF_NAME)
    token = config.get(CONF_TOKEN)

    _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5])

    try:
        air_quality_monitor = AirQualityMonitor(host, token)
        device_info = air_quality_monitor.info()
        model = device_info.model
        unique_id = "{}-{}".format(model, device_info.mac_address)
        _LOGGER.info("%s %s %s detected",
                     model,
                     device_info.firmware_version,
                     device_info.hardware_version)
        device = ClearGrassMonitor(
            name, air_quality_monitor, model, unique_id)
    except Exception:
        raise PlatformNotReady

    hass.data[DATA_KEY][host] = device
    async_add_entities([device], update_before_add=True)

class ClearGrassMonitor(Entity):
    """Representation of a Xiaomi Air Quality Monitor."""

    def __init__(self, name, device, model, unique_id):
        """Initialize the entity."""
        self._name = name
        self._device = device
        self._model = model
        self._unique_id = unique_id

        self._icon = 'mdi:cloud'
        self._unit_of_measurement = 'AQI'
        self._available = None
        self._state = None
        self._state_attrs = {
            ATTR_TEMPERATURE: None,
            ATTR_HUMIDITY: None,
            ATTR_CO2: None,
            ATTR_TVOC: None,
            #ATTR_PM25: None,
            ATTR_BATTERY_STATE: None,
            ATTR_MODEL: self._model,
        }

    @property
    def should_poll(self):
        """Poll the miio device."""
        return True

    @property
    def unique_id(self):
        """Return an unique ID."""
        return self._unique_id

    @property
    def name(self):
        """Return the name of this entity, if any."""
        return self._name

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

    @property
    def icon(self):
        """Return the icon to use for device if any."""
        return self._icon

    @property
    def available(self):
        """Return true when state is known."""
        return self._available

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

    @property
    def extra_state_attributes(self):
        """Return the state attributes of the device."""
        return self._state_attrs

    async def async_update(self):
        """Fetch state from the miio device."""        
        try:
            state = await self.hass.async_add_executor_job(self._device.status)

            self._available = True
            self._state = state.pm25
            self._state_attrs.update({
                ATTR_TEMPERATURE: state.temperature,
                ATTR_HUMIDITY: state.humidity,
                ATTR_CO2: state.co2,
                ATTR_TVOC: state.tvoc,
              #  ATTR_PM25:state.pm25,
                ATTR_BATTERY_STATE:state.battery_state,
            })

        except Exception as ex:
            self._available = False
            _LOGGER.error("Got exception while fetching the state: %s", ex)
ildar170975 commented 2 years ago

@akarpych No, this code gives errors on HA restart:

2022-08-14 17:32:17.846 ERROR (MainThread) [homeassistant.loader] Unexpected exception importing platform custom_components.clear_grass.sensor
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/loader.py", line 657, in get_platform
cache[full_name] = self._import_platform(platform_name)
File "/usr/src/homeassistant/homeassistant/loader.py", line 674, in _import_platform
return importlib.import_module(f"{self.pkg_path}.{platform_name}")
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 879, in exec_module
File "<frozen importlib._bootstrap_external>", line 1017, in get_code
File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/config/custom_components/clear_grass/sensor.py", line 20
"""Container of air quality monitor status."""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
IndentationError: expected an indented block after class definition on line 19
2022-08-14 17:32:17.848 ERROR (MainThread) [homeassistant.config] Platform error: sensor
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/loader.py", line 657, in get_platform
cache[full_name] = self._import_platform(platform_name)
File "/usr/src/homeassistant/homeassistant/loader.py", line 674, in _import_platform
return importlib.import_module(f"{self.pkg_path}.{platform_name}")
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 879, in exec_module
File "<frozen importlib._bootstrap_external>", line 1017, in get_code
File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/config/custom_components/clear_grass/sensor.py", line 20
"""Container of air quality monitor status."""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
IndentationError: expected an indented block after class definition on line 19
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config.py", line 878, in async_process_component_config
platform = p_integration.get_platform(domain)
File "/usr/src/homeassistant/homeassistant/loader.py", line 666, in get_platform
raise ImportError(
ImportError: Exception importing custom_components.clear_grass.sensor
2022-08-14 17:32:17.850 ERROR (MainThread) [homeassistant.loader] Unexpected exception importing platform custom_components.clear_grass.sensor
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/loader.py", line 657, in get_platform
cache[full_name] = self._import_platform(platform_name)
File "/usr/src/homeassistant/homeassistant/loader.py", line 674, in _import_platform
return importlib.import_module(f"{self.pkg_path}.{platform_name}")
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 879, in exec_module
File "<frozen importlib._bootstrap_external>", line 1017, in get_code
File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/config/custom_components/clear_grass/sensor.py", line 20
"""Container of air quality monitor status."""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
IndentationError: expected an indented block after class definition on line 19
2022-08-14 17:32:17.851 ERROR (MainThread) [homeassistant.config] Platform error: sensor
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/loader.py", line 657, in get_platform
cache[full_name] = self._import_platform(platform_name)
File "/usr/src/homeassistant/homeassistant/loader.py", line 674, in _import_platform
return importlib.import_module(f"{self.pkg_path}.{platform_name}")
File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 879, in exec_module
File "<frozen importlib._bootstrap_external>", line 1017, in get_code
File "<frozen importlib._bootstrap_external>", line 947, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/config/custom_components/clear_grass/sensor.py", line 20
"""Container of air quality monitor status."""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
IndentationError: expected an indented block after class definition on line 19
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/config.py", line 878, in async_process_component_config
platform = p_integration.get_platform(domain)
File "/usr/src/homeassistant/homeassistant/loader.py", line 666, in get_platform
raise ImportError(
ImportError: Exception importing custom_components.clear_grass.sensor
akarpych commented 2 years ago

Everything works for me without errors. Try deleting all rows with battery from sensor.ru

akarpych commented 2 years ago

2022-08-12_20-18-30

ildar170975 commented 2 years ago

Everything works for me without errors. Try deleting all rows with battery from sensor.ru

I just copy/pasted your code into sensor. I think that when you pasted the code into a comment - something lost in the file. As I already said - it is strangely formatted. Could you paste a code on some other external resource? (pastebin,...)

I tried to compare your code with an original one - and got a lot of differences on indentation.

akarpych commented 2 years ago

https://pastebin.com/88PNA3hP

akarpych commented 2 years ago

If the HA version is 2022.8.4, then in the manifest file you need to change the version to "python-miio>=0.5.12"

ildar170975 commented 2 years ago

@akarpych Yes! Exactly what I meant - loosing indentation when pasting into a comment. Here is a comparison of YOUR version & author's fixed version: https://www.diffchecker.com/w80ShahZ

Basic differences are: image

Pasted your code into my file, rebooted - and no errors in Log, no ClearGrass sensor is created.

If the HA version is 2022.8.4, then in the manifest file you need to change the version to "python-miio>=0.5.12"

Running HA 2022.8.3. Took a manifest file from the author's fixed version. Rebooted - same: no errors in Log, no ClearGrass sensor is created.

akarpych commented 2 years ago

Try to upgrade

ildar170975 commented 2 years ago

Try to upgrade

If you mean "upgrade HA to 2022.8.4" - it did not help. Let's check if I did right:

  1. Copy / paste you code into sensor.py.
  2. Copy /paste manifest from here.

Here is how sensor is declared:

sensor:
  - platform: clear_grass
    name: xxxxxxxxxxxxxxx
    host: 192.168.x.x
    token: some token
Leon-1409 commented 2 years ago

@ildar170975 HA 2022.8.4. I updated manifest (python-miio>=0.5.12) and copy/paste the code from @akarpych comment to sensor.py. Everything is ok - a new sensor has appeared

1) check the token (it may have changed) 2) check the way to add a new sensor - if the path to the folder with sensors is specified in the configuration, then the first line (sensors: ...) in the new file does not need to be specified 3) ... well, I hope there are no dublicate sensors names )

ildar170975 commented 2 years ago
  1. check the token (it may have changed)

You are absolutely right! Tokens were changed since I reset devices to reconnect them to MiHome! After firmware update, MiHome was unable to connect to ClearGrass devices, so I reset monitors and reconnected them to MiHome. BTW, a battery_level is still available in MiHome.

So, after updating tokens in HA my monitors work again! @akarpych, @Leon-1409 - thanks a lot.

So, now we are waiting for restoring the old functionality...

rybackisback commented 2 years ago

Try to upgrade

If you mean "upgrade HA to 2022.8.4" - it did not help. Let's check if I did right:

  1. Copy / paste you code into sensor.py.
  2. Copy /paste manifest from here.

Here is how sensor is declared:

sensor:
  - platform: clear_grass
    name: xxxxxxxxxxxxxxx
    host: 192.168.x.x
    token: some token

Hi @ildar170975 can you please guide me as to where to access the sensor.py file and where and how do you copy the manifest.

batja84 commented 2 years ago

can you please guide me as to where to access the sensor.py file and where and how do you copy the manifest. image

rybackisback commented 2 years ago

@batja84 thanks for that. All up and running now.

axxeeellll commented 2 years ago

Hello! I am newbie. Help me to understand. I downloaded the archive and put it in custom_components. Also announced in sensor:

But doesn't work....

AntoxaSa commented 2 years ago

updated to 8.6 the problem remained. You can paint step by step what to do, it doesn’t work out. Thank you in advance for your help

AntoxaSa commented 2 years ago

2022-08-19 12:20:44.706 ERROR (SyncWorker_3) [custom_components.clear_grass.sensor] Count (7) of requested properties does not match the count (2) of received values. 2022-08-19 12:20:44.867 ERROR (MainThread) [custom_components.clear_grass.sensor] Got exception while fetching the state: dictionary update sequence element #0 has length 1; 2 is required 2022-08-19 12:20:53.220 ERROR (MainThread) [homeassistant.helpers.event] Error while processing template: Template("{{ (states('sensor.termogolovka_komnata_position')| int == 100) }}")

AntoxaSa commented 2 years ago

temperature: 25.7 humidity: 46.2 co2: 1226 tvoc: 316 battery_state: charging model: cgllc.airmonitor.s1 unit_of_measurement: AQI icon: mdi:cloud friendly_name: Xiaomi ClearGrass Air Detector

figured out deleted the battery status, still does not show pm2.5

akarpych commented 2 years ago

temperature: 25.7 humidity: 46.2 co2: 1226 tvoc: 316 battery_state: charging model: cgllc.airmonitor.s1 unit_of_measurement: AQI icon: mdi:cloud friendly_name: Xiaomi ClearGrass Air Detector

figured out deleted the battery status, still does not show pm2.5

uncomment

ATTR_PM25:state.pm25,

AntoxaSa commented 2 years ago

Thanks, got it working

sstu commented 2 years ago

Confirm, it's working

petron1e commented 1 year ago

Hello, there is a easy way to get ClearGrass working in HA - via HACS integration Xiaomi Miot Auto. P.

almirus commented 1 year ago

or direct way to connect to your MQTT server https://touch-max.ru/umnyj-dom/otvyazka-clear-grass-air-monitor-ot-oblaka-i-podklyuchenie-k-home-assistant

ildar170975 commented 8 months ago

Cleaned up the "sensor.py" file:

  1. Removed unused code.
  2. Added a check for "wrong values" for battery_level: if value is not a dict - use "100" value.

https://github.com/ildar170975/cleargrass_ha/blob/main/sensor.py

So far have not found a way to retrieve battery_level data. Meanwhile using a fixed "100" value.

@Cheaterdev If you have some spare time - please have a look at my changed file; you are more than welcome to replace the old file with this new one.