diff --git a/custom_components/jq300/__init__.py b/custom_components/jq300/__init__.py index 0af82c5..246ce0a 100644 --- a/custom_components/jq300/__init__.py +++ b/custom_components/jq300/__init__.py @@ -19,8 +19,8 @@ import homeassistant.util.dt as dt_util import requests import voluptuous as vol -from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_DEVICES, \ CONF_DEVICE_ID, CONCENTRATION_PARTS_PER_BILLION, \ CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_MILLION @@ -28,13 +28,12 @@ from requests import PreparedRequest from .const import DOMAIN, VERSION, ISSUE_URL, SUPPORT_LIB_URL, DATA_JQ300, \ - QUERY_TYPE_API, QUERY_TYPE_DEVICE, QUERY_METHOD_GET, BASE_URL_API, \ - BASE_URL_DEVICE, USERAGENT_API, USERAGENT_DEVICE, QUERY_TIMEOUT, \ - MSG_GENERIC_FAIL, MSG_LOGIN_FAIL, QUERY_METHOD_POST, MSG_BUSY, \ - SENSORS, UPDATE_MIN_TIME, CONF_RECEIVE_TVOC_IN_PPB, \ - CONF_RECEIVE_HCHO_IN_PPB, SENSORS_FILTER_TIME, MWEIGTH_TVOC, MWEIGTH_HCHO, \ - ATTR_DEVICE_BRAND, ATTR_DEVICE_MODEL, ATTR_DEVICE_ID, ATTR_RAW_STATE, \ - BINARY_SENSORS + QUERY_TYPE_API, QUERY_TYPE_DEVICE, BASE_URL_API, BASE_URL_DEVICE, \ + USERAGENT_API, USERAGENT_DEVICE, QUERY_TIMEOUT, MSG_GENERIC_FAIL, \ + MSG_LOGIN_FAIL, MSG_BUSY, SENSORS, UPDATE_MIN_TIME, \ + CONF_RECEIVE_TVOC_IN_PPB, CONF_RECEIVE_HCHO_IN_PPB, SENSORS_FILTER_TIME, \ + MWEIGTH_TVOC, MWEIGTH_HCHO, ATTR_DEVICE_BRAND, ATTR_DEVICE_MODEL, \ + ATTR_DEVICE_ID, ATTR_RAW_STATE, BINARY_SENSORS _LOGGER = logging.getLogger(__name__) @@ -66,25 +65,24 @@ async def async_setup(hass, config): controller = JqController( username, password, receive_tvoc_in_ppb, receive_hcho_in_ppb) - hass.data[DATA_JQ300][username] = controller _LOGGER.info('Connected to cloud account %s', username) devs = controller.get_devices_list() if not devs: _LOGGER.error("Can't receive devices list from cloud.") return False - for dev_id in devs.keys(): + for dev_id in devs: if devices and devs[dev_id]['pt_name'] not in devices: continue + device = JqDevice(controller, dev_id) + hass.data[DATA_JQ300][device.unique_id] = device hass.async_create_task( discovery.async_load_platform(hass, BINARY_SENSOR, DOMAIN, { - CONF_USERNAME: username, - CONF_DEVICE_ID: dev_id, + CONF_DEVICE_ID: device.unique_id, }, config)) hass.async_create_task( discovery.async_load_platform(hass, SENSOR, DOMAIN, { - CONF_USERNAME: username, - CONF_DEVICE_ID: dev_id, + CONF_DEVICE_ID: device.unique_id, }, config)) return True @@ -97,7 +95,7 @@ class JqController: # pylint: disable=R0913 def __init__(self, username, password, receive_tvoc_in_ppb=False, receive_hcho_in_ppb=False): - """Initialize configured device.""" + """Initialize configured controller.""" self.params = { 'uid': -1000, 'safeToken': 'anonymous', @@ -125,13 +123,13 @@ def __init__(self, username, password, receive_tvoc_in_ppb=False, self._units[sensor_id] = data[1] @property - def unique_id(self): - """Return a device unique ID.""" + def unique_id(self) -> str: + """Return a controller unique ID.""" return self._username @property - def name(self): - """Get custom device name.""" + def name(self) -> str: + """Get custom controller name.""" return 'JQ300' @property @@ -140,7 +138,7 @@ def available(self) -> bool: return self._login() @property - def units(self): + def units(self) -> dict: """Get list of units for sensors.""" return self._units @@ -392,3 +390,69 @@ def get_sensors(self, device_id) -> Optional[dict]: def get_sensors_raw(self, device_id) -> Optional[dict]: """Get raw values of states of available sensors for device.""" return self._sensors_raw.get(device_id) + + +class JqDevice: + """JQ device controller""" + + def __init__(self, controller: JqController, device_id): + """Initialize configured device.""" + self._controller = controller + self._device_id = device_id + + device = controller.get_devices_list() + # _LOGGER.debug(device) + if not device: + _LOGGER.error("Can't receive devices list from cloud.") + return + self._device = device[device_id] + + self._unique_id = '{}-{}'.format( + self._controller.unique_id, self._device_id) + self._available = False + + @property + def unique_id(self) -> str: + """Return a device unique ID.""" + return self._unique_id + + @property + def name(self) -> str: + """Get custom device name.""" + return self._device['pt_name'] + + @property + def available(self) -> bool: + """Return True if device is available.""" + return self._controller.available + # pylint: disable=W0511 + # todo: Add check for device availability + + @property + def units(self) -> dict: + """Get list of units for sensors.""" + return self._controller.units + + @property + def sensors(self) -> Optional[dict]: + """Get states of available sensors for device.""" + return self._controller.get_sensors(self._device_id) + + @property + def sensors_raw(self) -> Optional[dict]: + """Get raw values of states of available sensors for device.""" + return self._controller.get_sensors_raw(self._device_id) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + return { + ATTR_DEVICE_BRAND: self._device['brandname'], + ATTR_DEVICE_MODEL: self._device['pt_model'], + ATTR_DEVICE_ID: self._device['deviceid'], + } + + @property + def should_poll(self) -> bool: + """Return the polling state.""" + return True diff --git a/custom_components/jq300/binary_sensor.py b/custom_components/jq300/binary_sensor.py index b5c8c67..147dc7c 100644 --- a/custom_components/jq300/binary_sensor.py +++ b/custom_components/jq300/binary_sensor.py @@ -14,14 +14,11 @@ from homeassistant.components.binary_sensor import BinarySensorDevice, \ ENTITY_ID_FORMAT -from homeassistant.const import ( - CONF_USERNAME, - CONF_DEVICE_ID) +from homeassistant.const import CONF_DEVICE_ID from homeassistant.helpers.entity import async_generate_entity_id -from . import DATA_JQ300 -from .const import BINARY_SENSORS, ATTR_DEVICE_BRAND, ATTR_DEVICE_MODEL, \ - ATTR_DEVICE_ID +from . import JqDevice +from .const import DATA_JQ300, BINARY_SENSORS _LOGGER = logging.getLogger(__name__) @@ -33,62 +30,46 @@ async def async_setup_platform(hass, config, async_add_entities, if discovery_info is None: return - account_id = discovery_info[CONF_USERNAME] - device_id = discovery_info[CONF_DEVICE_ID] - _LOGGER.debug('Setup binary sensors for device %s', device_id) + device_uid = discovery_info[CONF_DEVICE_ID] + _LOGGER.debug('Setup binary sensors for device %s', device_uid) - controller = hass.data[DATA_JQ300][account_id] # type: JqController - device = controller.get_devices_list() + device = hass.data[DATA_JQ300][device_uid] # type: JqDevice + dev_sensors = device.sensors # _LOGGER.debug(device) - if not device: - _LOGGER.error("Can't receive devices list from cloud.") - return - device = device[device_id] - dev_name = device['pt_name'] - sensors_data = controller.get_sensors(device_id) - # _LOGGER.debug(sensors_data) - if not sensors_data: + if not dev_sensors: _LOGGER.error("Can't receive sensors list for device '%s' from cloud.", - device['pt_name']) + device_uid) return sensor_id = 1 ent_name = BINARY_SENSORS.get(sensor_id)[4] \ or BINARY_SENSORS.get(sensor_id)[0] entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, f"{dev_name}_{ent_name}", hass=hass) - _LOGGER.debug("Initialize %s for account %s", entity_id, account_id) + ENTITY_ID_FORMAT, f"{device.name}_{ent_name}", hass=hass) + _LOGGER.debug("Initialize %s", entity_id) async_add_entities([JqBinarySensor( - hass, controller, device, sensor_id, sensors_data[sensor_id], - entity_id)]) + entity_id, device, sensor_id, dev_sensors[sensor_id])]) # pylint: disable=R0902 class JqBinarySensor(BinarySensorDevice): """A binary sensor implementation for JQ device""" - # pylint: disable=R0913,W0613 - def __init__(self, hass, controller, device, sensor_id, sensor_state, - entity_id): - """Initialize a sensor""" + def __init__(self, entity_id, device: JqDevice, sensor_id, sensor_state): + """Initialize a binary sensor""" super().__init__() - self._controller = controller # type: JqController - self._device_id = device['deviceid'] - self._device_brand = device['brandname'] - self._device_model = device['pt_model'] + self.entity_id = entity_id + + self._device = device self._sensor_id = sensor_id + self._unique_id = '{}-{}'.format(device.unique_id, sensor_id) self._name = "{0} {1}".format( - device['pt_name'], BINARY_SENSORS.get(sensor_id)[0]) + device.name, BINARY_SENSORS.get(sensor_id)[0]) self._state = sensor_state - self._units = controller.units[sensor_id] self._icon = BINARY_SENSORS.get(sensor_id)[2] - self._unique_id = '{}-{}-{}'.format( - self._controller.unique_id, self._device_id, sensor_id) self._device_class = BINARY_SENSORS.get(sensor_id)[3] - self.entity_id = entity_id - @property def name(self): """Return the name of the binary sensor.""" @@ -97,7 +78,7 @@ def name(self): @property def available(self) -> bool: """Return True if entity is available.""" - return self._controller.available + return self._device.available @property def is_on(self): @@ -117,11 +98,7 @@ def device_class(self): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = { - ATTR_DEVICE_BRAND: self._device_brand, - ATTR_DEVICE_MODEL: self._device_model, - ATTR_DEVICE_ID: self._device_id, - } + attrs = self._device.device_state_attributes return attrs @property @@ -129,19 +106,14 @@ def icon(self): """Icon to use in the frontend, if any.""" return self._icon - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return self._units - @property def should_poll(self): """Return the polling state.""" - return True + return self._device.should_poll def update(self): """Update the sensor state if it needed.""" - ret = self._controller.get_sensors_raw(self._device_id) + ret = self._device.sensors_raw if ret: self._state = ret[self._sensor_id] _LOGGER.debug('Update state: %s = %s', self.entity_id, self._state) diff --git a/custom_components/jq300/const.py b/custom_components/jq300/const.py index 52b9d6a..1f48f4d 100644 --- a/custom_components/jq300/const.py +++ b/custom_components/jq300/const.py @@ -18,7 +18,7 @@ # Base component constants DOMAIN = "jq300" -VERSION = "0.7.5" +VERSION = "0.7.6" ISSUE_URL = "https://github.com/Limych/ha-jq300/issues" ATTRIBUTION = None DATA_JQ300 = 'jq300' @@ -36,9 +36,6 @@ QUERY_TYPE_API = 'API' QUERY_TYPE_DEVICE = 'DEVICE' -QUERY_METHOD_GET = 'GET' -QUERY_METHOD_POST = 'POST' - BASE_URL_API = "http://www.youpinyuntai.com:32086/ypyt-api/api/app/" BASE_URL_DEVICE = "https://www.youpinyuntai.com:31447/device/" @@ -111,7 +108,7 @@ ATTR_RAW_STATE = 'raw_state' UPDATE_MIN_TIME = 20 # seconds -SENSORS_FILTER_TIME = 600 # seconds +SENSORS_FILTER_TIME = 900 # seconds -MWEIGTH_TVOC = 56.1060 # g/mol -MWEIGTH_HCHO = 30.0260 # g/mol +MWEIGTH_TVOC = 56.1060 # g/mol +MWEIGTH_HCHO = 30.0260 # g/mol diff --git a/custom_components/jq300/sensor.py b/custom_components/jq300/sensor.py index af85f02..73e6e75 100644 --- a/custom_components/jq300/sensor.py +++ b/custom_components/jq300/sensor.py @@ -14,12 +14,11 @@ import logging from homeassistant.components.sensor import ENTITY_ID_FORMAT -from homeassistant.const import CONF_USERNAME, CONF_DEVICE_ID +from homeassistant.const import CONF_DEVICE_ID from homeassistant.helpers.entity import Entity, async_generate_entity_id -from . import JqController -from .const import DATA_JQ300, SENSORS, ATTR_DEVICE_ID, ATTR_DEVICE_BRAND, \ - ATTR_DEVICE_MODEL, ATTR_RAW_STATE +from . import JqDevice +from .const import DATA_JQ300, SENSORS, ATTR_RAW_STATE _LOGGER = logging.getLogger(__name__) @@ -31,35 +30,26 @@ async def async_setup_platform(hass, config, async_add_entities, if discovery_info is None: return - account_id = discovery_info[CONF_USERNAME] - device_id = discovery_info[CONF_DEVICE_ID] - _LOGGER.debug('Setup sensors for device %s', device_id) + device_uid = discovery_info[CONF_DEVICE_ID] + _LOGGER.debug('Setup sensors for device %s', device_uid) - controller = hass.data[DATA_JQ300][account_id] # type: JqController - device = controller.get_devices_list() - # _LOGGER.debug(device) - if not device: - _LOGGER.error("Can't receive devices list from cloud.") - return - device = device[device_id] - dev_name = device['pt_name'] - sensors_data = controller.get_sensors(device_id) + device = hass.data[DATA_JQ300][device_uid] # type: JqDevice + dev_sensors = device.sensors # _LOGGER.debug(sensors_data) - if not sensors_data: + if not dev_sensors: _LOGGER.error("Can't receive sensors list for device '%s' from cloud.", - device['pt_name']) + device_uid) return sensors = [] - for sensor_id, sensor_state in sensors_data.items(): + for sensor_id, sensor_state in dev_sensors.items(): if sensor_id not in SENSORS.keys(): continue ent_name = SENSORS.get(sensor_id)[4] or SENSORS.get(sensor_id)[0] entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, f"{dev_name}_{ent_name}", hass=hass) - _LOGGER.debug("Initialize %s for account %s", entity_id, account_id) - sensors.append(JqSensor( - hass, controller, device, sensor_id, sensor_state, entity_id)) + ENTITY_ID_FORMAT, f"{device.name}_{ent_name}", hass=hass) + _LOGGER.debug("Initialize %s", entity_id) + sensors.append(JqSensor(entity_id, device, sensor_id, sensor_state)) async_add_entities(sensors) @@ -68,29 +58,22 @@ async def async_setup_platform(hass, config, async_add_entities, class JqSensor(Entity): """A sensor implementation for JQ device""" - # pylint: disable=R0913,W0613 - def __init__(self, hass, controller, device, sensor_id, sensor_state, - entity_id): + def __init__(self, entity_id, device: JqDevice, sensor_id, sensor_state): """Initialize a sensor""" super().__init__() - self._controller = controller # type: JqController - self._device_id = device['deviceid'] - self._device_brand = device['brandname'] - self._device_model = device['pt_model'] + self.entity_id = entity_id + + self._device = device self._sensor_id = sensor_id - self._name = "{0} {1}".format( - device['pt_name'], SENSORS.get(sensor_id)[0]) + self._unique_id = '{}-{}'.format(device.unique_id, sensor_id) + self._name = "{0} {1}".format(device.name, SENSORS.get(sensor_id)[0]) self._state = sensor_state self._state_raw = sensor_state - self._units = controller.units[sensor_id] + self._units = device.units[sensor_id] self._icon = SENSORS.get(sensor_id)[2] - self._unique_id = '{}-{}-{}'.format( - self._controller.unique_id, self._device_id, sensor_id) self._device_class = SENSORS.get(sensor_id)[3] - self.entity_id = entity_id - @property def name(self): """Return the name of the sensor.""" @@ -99,7 +82,7 @@ def name(self): @property def available(self) -> bool: """Return True if entity is available.""" - return self._controller.available + return self._device.available @property def state(self): @@ -119,12 +102,8 @@ def device_class(self): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = { - ATTR_DEVICE_BRAND: self._device_brand, - ATTR_DEVICE_MODEL: self._device_model, - ATTR_DEVICE_ID: self._device_id, - ATTR_RAW_STATE: self._state_raw, - } + attrs = self._device.device_state_attributes + attrs[ATTR_RAW_STATE] = self._state_raw return attrs @property @@ -140,14 +119,13 @@ def unit_of_measurement(self): @property def should_poll(self): """Return the polling state.""" - return True + return self._device.should_poll def update(self): """Update the sensor state if it needed.""" - ret = self._controller.get_sensors(self._device_id) + ret = self._device.sensors if ret: self._state = ret[self._sensor_id] - self._state_raw = self._controller.get_sensors_raw( - self._device_id)[self._sensor_id] + self._state_raw = self._device.sensors_raw[self._sensor_id] _LOGGER.debug('Update state: %s = %s (%s)', self.entity_id, self._state, self._state_raw) diff --git a/tracker.json b/tracker.json index 058faf4..6564a2e 100644 --- a/tracker.json +++ b/tracker.json @@ -1,6 +1,6 @@ { "jq300": { - "version": "0.7.5", + "version": "0.7.6", "local_location": "/custom_components/jq300/__init__.py", "remote_location": "https://raw.githubusercontent.com/Limych/ha-jq300/master/custom_components/jq300/__init__.py", "visit_repo": "https://github.com/Limych/ha-jq300",