diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..1e4ceac0edf396c0e80c7923a71817c8da0aa400 --- /dev/null +++ b/homeassistant/components/climate/melissa.py @@ -0,0 +1,274 @@ +""" +Support for Melissa Climate A/C. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/climate.melissa/ +""" +import logging + +from homeassistant.components.climate import ClimateDevice, \ + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_ON_OFF, \ + STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, \ + SUPPORT_FAN_MODE +from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH +from homeassistant.components.melissa import DATA_MELISSA, DOMAIN +from homeassistant.const import TEMP_CELSIUS, STATE_ON, STATE_OFF, \ + STATE_UNKNOWN, STATE_IDLE, ATTR_TEMPERATURE, PRECISION_WHOLE + +DEPENDENCIES = [DOMAIN] + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | + SUPPORT_ON_OFF | SUPPORT_FAN_MODE) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Iterate through and add all Melissa devices.""" + api = hass.data[DATA_MELISSA] + devices = api.fetch_devices().values() + + all_devices = [] + + for device in devices: + all_devices.append(MelissaClimate( + api, device['serial_number'], device)) + + add_devices(all_devices) + + +class MelissaClimate(ClimateDevice): + """Representation of a Melissa Climate device.""" + + def __init__(self, api, serial_number, init_data): + """Initialize the climate device.""" + self._name = init_data['name'] + self._api = api + self._serial_number = serial_number + self._data = init_data['controller_log'] + self._state = None + self._cur_settings = None + + @property + def name(self): + """Return the name of the thermostat, if any.""" + return self._name + + @property + def is_on(self): + """Return current state.""" + if self._cur_settings is not None: + return self._cur_settings[self._api.STATE] in ( + self._api.STATE_ON, self._api.STATE_IDLE) + else: + _LOGGER.info("Can't determine state of %s", self.entity_id) + return STATE_UNKNOWN + + @property + def current_fan_mode(self): + """Return the current fan mode.""" + if self._cur_settings is not None: + return self.melissa_fan_to_hass( + self._cur_settings[self._api.FAN]) + else: + _LOGGER.info( + "Can't determine current fan mode for %s", self.entity_id) + return STATE_UNKNOWN + + @property + def current_temperature(self): + """Return the current temperature.""" + if self._data: + return self._data[self._api.TEMP] + else: + _LOGGER.info( + "Can't determine current temperature for %s", self.entity_id) + return None + + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + return PRECISION_WHOLE + + @property + def current_operation(self): + """Return the current operation mode.""" + if self._cur_settings is not None: + return self.melissa_op_to_hass( + self._cur_settings[self._api.MODE]) + else: + _LOGGER.info( + "Can't determine current operation mode of %s", self.entity_id) + return STATE_UNKNOWN + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return [ + STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY + ] + + @property + def fan_list(self): + """List of available fan modes.""" + return [ + STATE_AUTO, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH + ] + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + if self._cur_settings is not None: + return self._cur_settings[self._api.TEMP] + else: + _LOGGER.info( + "Can not determine current target temperature for %s", + self.entity_id) + return STATE_UNKNOWN + + @property + def state(self): + """Return current state.""" + if self._cur_settings is not None: + return self.melissa_state_to_hass( + self._cur_settings[self._api.STATE]) + else: + _LOGGER.info("Cant determine current state for %s", self.entity_id) + return STATE_UNKNOWN + + @property + def temperature_unit(self): + """Return the unit of measurement which this thermostat uses.""" + return TEMP_CELSIUS + + @property + def min_temp(self): + """Return the minimum supported temperature for the thermostat.""" + return 16 + + @property + def max_temp(self): + """Return the maximum supported temperature for the thermostat.""" + return 30 + + @property + def supported_features(self): + """Return the list of supported features.""" + return SUPPORT_FLAGS + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + temp = kwargs.get(ATTR_TEMPERATURE) + return self.send({self._api.TEMP: temp}) + + def set_fan_mode(self, fan): + """Set fan mode.""" + fan_mode = self.hass_fan_to_melissa(fan) + return self.send({self._api.FAN: fan_mode}) + + def set_operation_mode(self, operation_mode): + """Set operation mode.""" + mode = self.hass_mode_to_melissa(operation_mode) + return self.send({self._api.MODE: mode}) + + def turn_on(self): + """Turn on device.""" + return self.send({self._api.STATE: self._api.STATE_ON}) + + def turn_off(self): + """Turn off device.""" + return self.send({self._api.STATE: self._api.STATE_OFF}) + + def send(self, value): + """Sending action to service.""" + try: + old_value = self._cur_settings.copy() + self._cur_settings.update(value) + except AttributeError: + old_value = None + if not self._api.send(self._serial_number, self._cur_settings): + self._cur_settings = old_value + return False + else: + return True + + def update(self): + """Get latest data from Melissa.""" + try: + self._data = self._api.status(cached=True)[self._serial_number] + self._cur_settings = self._api.cur_settings( + self._serial_number + )['controller']['_relation']['command_log'] + except KeyError: + _LOGGER.warning( + 'Unable to update component %s', self.entity_id) + + def melissa_state_to_hass(self, state): + """Translate Melissa states to hass states.""" + if state == self._api.STATE_ON: + return STATE_ON + elif state == self._api.STATE_OFF: + return STATE_OFF + elif state == self._api.STATE_IDLE: + return STATE_IDLE + else: + return STATE_UNKNOWN + + def melissa_op_to_hass(self, mode): + """Translate Melissa modes to hass states.""" + if mode == self._api.MODE_AUTO: + return STATE_AUTO + elif mode == self._api.MODE_HEAT: + return STATE_HEAT + elif mode == self._api.MODE_COOL: + return STATE_COOL + elif mode == self._api.MODE_DRY: + return STATE_DRY + elif mode == self._api.MODE_FAN: + return STATE_FAN_ONLY + else: + _LOGGER.warning( + "Operation mode %s could not be mapped to hass", mode) + return STATE_UNKNOWN + + def melissa_fan_to_hass(self, fan): + """Translate Melissa fan modes to hass modes.""" + if fan == self._api.FAN_AUTO: + return STATE_AUTO + elif fan == self._api.FAN_LOW: + return SPEED_LOW + elif fan == self._api.FAN_MEDIUM: + return SPEED_MEDIUM + elif fan == self._api.FAN_HIGH: + return SPEED_HIGH + else: + _LOGGER.warning("Fan mode %s could not be mapped to hass", fan) + return STATE_UNKNOWN + + def hass_mode_to_melissa(self, mode): + """Translate hass states to melissa modes.""" + if mode == STATE_AUTO: + return self._api.MODE_AUTO + elif mode == STATE_HEAT: + return self._api.MODE_HEAT + elif mode == STATE_COOL: + return self._api.MODE_COOL + elif mode == STATE_DRY: + return self._api.MODE_DRY + elif mode == STATE_FAN_ONLY: + return self._api.MODE_FAN + else: + _LOGGER.warning("Melissa have no setting for %s mode", mode) + + def hass_fan_to_melissa(self, fan): + """Translate hass fan modes to melissa modes.""" + if fan == STATE_AUTO: + return self._api.FAN_AUTO + elif fan == SPEED_LOW: + return self._api.FAN_LOW + elif fan == SPEED_MEDIUM: + return self._api.FAN_MEDIUM + elif fan == SPEED_HIGH: + return self._api.FAN_HIGH + else: + _LOGGER.warning("Melissa have no setting for %s fan mode", fan) diff --git a/homeassistant/components/melissa.py b/homeassistant/components/melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..ae82b96222ea5dfea8488783ed82368cc6d0c640 --- /dev/null +++ b/homeassistant/components/melissa.py @@ -0,0 +1,44 @@ +""" +Support for Melissa climate. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/melissa/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.discovery import load_platform + +REQUIREMENTS = ["py-melissa-climate==1.0.1"] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "melissa" +DATA_MELISSA = 'MELISSA' + + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the Melissa Climate component.""" + import melissa + + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + + api = melissa.Melissa(username=username, password=password) + hass.data[DATA_MELISSA] = api + + load_platform(hass, 'sensor', DOMAIN, {}) + load_platform(hass, 'climate', DOMAIN, {}) + return True diff --git a/homeassistant/components/sensor/melissa.py b/homeassistant/components/sensor/melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..97645cb9dd4f1398f0f4cf8813433ff487bf2de1 --- /dev/null +++ b/homeassistant/components/sensor/melissa.py @@ -0,0 +1,98 @@ +""" +Support for Melissa climate Sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.melissa/ +""" +import logging + +from homeassistant.components.melissa import DOMAIN, DATA_MELISSA +from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN +from homeassistant.helpers.entity import Entity + +DEPENDENCIES = [DOMAIN] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the melissa sensor platform.""" + sensors = [] + api = hass.data[DATA_MELISSA] + devices = api.fetch_devices().values() + + for device in devices: + sensors.append(MelissaTemperatureSensor(device, api)) + sensors.append(MelissaHumiditySensor(device, api)) + add_devices(sensors) + + +class MelissaSensor(Entity): + """Representation of a Melissa Sensor.""" + + _type = 'generic' + + def __init__(self, device, api): + """Initialize the sensor.""" + self._api = api + self._state = STATE_UNKNOWN + self._name = '{0} {1}'.format( + device['name'], + self._type + ) + self._serial = device['serial_number'] + self._data = device['controller_log'] + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + def update(self): + """Fetch status from melissa.""" + self._data = self._api.status(cached=True) + + +class MelissaTemperatureSensor(MelissaSensor): + """Representation of a Melissa temperature Sensor.""" + + _type = 'temperature' + _unit = TEMP_CELSIUS + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit + + def update(self): + """Fetch new state data for the sensor.""" + super().update() + try: + self._state = self._data[self._serial]['temp'] + except KeyError: + _LOGGER.warning("Unable to get temperature for %s", self.entity_id) + + +class MelissaHumiditySensor(MelissaSensor): + """Representation of a Melissa humidity Sensor.""" + + _type = 'humidity' + _unit = '%' + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit + + def update(self): + """Fetch new state data for the sensor.""" + super().update() + try: + self._state = self._data[self._serial]['humidity'] + except KeyError: + _LOGGER.warning("Unable to get humidity for %s", self.entity_id) diff --git a/requirements_all.txt b/requirements_all.txt index 20dc82cb95a6f717a1f008cd9ec49b50c2e776d4..3bf11f9e27c12f3d61cd9e3dda71f17ecfa20e27 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -623,6 +623,9 @@ py-canary==0.2.3 # homeassistant.components.sensor.cpuspeed py-cpuinfo==3.3.0 +# homeassistant.components.melissa +py-melissa-climate==1.0.1 + # homeassistant.components.camera.synology py-synology==0.1.5 diff --git a/tests/components/climate/test_melissa.py b/tests/components/climate/test_melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..ef5cbff5087faba71df1761c0deda552b2bc69c3 --- /dev/null +++ b/tests/components/climate/test_melissa.py @@ -0,0 +1,264 @@ +"""Test for Melissa climate component.""" +import unittest +from unittest.mock import Mock, patch +import json + +from asynctest import mock + +from homeassistant.components.climate import melissa, \ + SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF, \ + SUPPORT_FAN_MODE, STATE_HEAT, STATE_FAN_ONLY, STATE_DRY, STATE_COOL, \ + STATE_AUTO +from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH +from homeassistant.components.melissa import DATA_MELISSA +from homeassistant.const import TEMP_CELSIUS, STATE_ON, ATTR_TEMPERATURE, \ + STATE_OFF, STATE_IDLE, STATE_UNKNOWN +from tests.common import get_test_home_assistant, load_fixture + + +class TestMelissa(unittest.TestCase): + """Tests for Melissa climate.""" + + def setUp(self): # pylint: disable=invalid-name + """Set up test variables.""" + self.hass = get_test_home_assistant() + self._serial = '12345678' + + self.api = Mock() + self.api.fetch_devices.return_value = json.loads(load_fixture( + 'melissa_fetch_devices.json' + )) + self.api.cur_settings.return_value = json.loads(load_fixture( + 'melissa_cur_settings.json' + )) + self.api.status.return_value = json.loads(load_fixture( + 'melissa_status.json' + )) + self.api.STATE_OFF = 0 + self.api.STATE_ON = 1 + self.api.STATE_IDLE = 2 + + self.api.MODE_AUTO = 0 + self.api.MODE_FAN = 1 + self.api.MODE_HEAT = 2 + self.api.MODE_COOL = 3 + self.api.MODE_DRY = 4 + + self.api.FAN_AUTO = 0 + self.api.FAN_LOW = 1 + self.api.FAN_MEDIUM = 2 + self.api.FAN_HIGH = 3 + + self.api.STATE = 'state' + self.api.MODE = 'mode' + self.api.FAN = 'fan' + self.api.TEMP = 'temp' + + device = self.api.fetch_devices()[self._serial] + self.thermostat = melissa.MelissaClimate( + self.api, device['serial_number'], device) + self.thermostat.update() + + def tearDown(self): # pylint: disable=invalid-name + """Teardown this test class. Stop hass.""" + self.hass.stop() + + @patch("homeassistant.components.climate.melissa.MelissaClimate") + def test_setup_platform(self, mocked_thermostat): + """Test setup_platform.""" + device = self.api.fetch_devices()[self._serial] + thermostat = mocked_thermostat(self.api, device['serial_number'], + device) + thermostats = [thermostat] + + self.hass.data[DATA_MELISSA] = self.api + + config = {} + add_devices = Mock() + discovery_info = {} + + melissa.setup_platform(self.hass, config, add_devices, discovery_info) + add_devices.assert_called_once_with(thermostats) + + def test_get_name(self): + """Test name property.""" + self.assertEqual("Melissa 12345678", self.thermostat.name) + + def test_is_on(self): + """Test name property.""" + self.assertEqual(self.thermostat.is_on, True) + self.thermostat._cur_settings = None + self.assertEqual(STATE_UNKNOWN, self.thermostat.is_on) + + def test_current_fan_mode(self): + """Test current_fan_mode property.""" + self.thermostat.update() + self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) + self.thermostat._cur_settings = None + self.assertEqual(STATE_UNKNOWN, self.thermostat.current_fan_mode) + + def test_current_temperature(self): + """Test current temperature.""" + self.assertEqual(27.4, self.thermostat.current_temperature) + + def test_current_temperature_no_data(self): + """Test current temperature without data.""" + self.thermostat._data = None + self.assertIsNone(self.thermostat.current_temperature) + + def test_target_temperature_step(self): + """Test current target_temperature_step.""" + self.assertEqual(1, self.thermostat.target_temperature_step) + + def test_current_operation(self): + """Test current operation.""" + self.thermostat.update() + self.assertEqual(self.thermostat.current_operation, STATE_HEAT) + self.thermostat._cur_settings = None + self.assertEqual(STATE_UNKNOWN, self.thermostat.current_operation) + + def test_operation_list(self): + """Test the operation list.""" + self.assertEqual( + [STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY], + self.thermostat.operation_list + ) + + def test_fan_list(self): + """Test the fan list.""" + self.assertEqual( + [STATE_AUTO, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + self.thermostat.fan_list + ) + + def test_target_temperature(self): + """Test target temperature.""" + self.assertEqual(16, self.thermostat.target_temperature) + self.thermostat._cur_settings = None + self.assertEqual(STATE_UNKNOWN, self.thermostat.target_temperature) + + def test_state(self): + """Test state.""" + self.assertEqual(STATE_ON, self.thermostat.state) + self.thermostat._cur_settings = None + self.assertEqual(STATE_UNKNOWN, self.thermostat.state) + + def test_temperature_unit(self): + """Test temperature unit.""" + self.assertEqual(TEMP_CELSIUS, self.thermostat.temperature_unit) + + def test_min_temp(self): + """Test min temp.""" + self.assertEqual(16, self.thermostat.min_temp) + + def test_max_temp(self): + """Test max temp.""" + self.assertEqual(30, self.thermostat.max_temp) + + def test_supported_features(self): + """Test supported_features property.""" + features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | + SUPPORT_ON_OFF | SUPPORT_FAN_MODE) + self.assertEqual(features, self.thermostat.supported_features) + + def test_set_temperature(self): + """Test set_temperature.""" + self.api.send.return_value = True + self.thermostat.update() + self.assertTrue(self.thermostat.set_temperature( + **{ATTR_TEMPERATURE: 25})) + self.assertEqual(25, self.thermostat.target_temperature) + + def test_fan_mode(self): + """Test set_fan_mode.""" + self.api.send.return_value = True + self.assertTrue(self.thermostat.set_fan_mode(SPEED_LOW)) + self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) + + def test_set_operation_mode(self): + """Test set_operation_mode.""" + self.api.send.return_value = True + self.assertTrue(self.thermostat.set_operation_mode(STATE_COOL)) + self.assertEqual(STATE_COOL, self.thermostat.current_operation) + + def test_turn_on(self): + """Test turn_on.""" + self.assertTrue(self.thermostat.turn_on()) + + def test_turn_off(self): + """Test turn_off.""" + self.assertTrue(self.thermostat.turn_off()) + + def test_send(self): + """Test send.""" + self.thermostat.update() + self.assertTrue(self.thermostat.send( + {'fan': self.api.FAN_MEDIUM})) + self.assertEqual(SPEED_MEDIUM, self.thermostat.current_fan_mode) + self.api.send.return_value = False + self.thermostat._cur_settings = None + self.assertFalse(self.thermostat.send({ + 'fan': self.api.FAN_LOW})) + self.assertNotEquals(SPEED_LOW, self.thermostat.current_fan_mode) + self.assertIsNone(self.thermostat._cur_settings) + + @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') + def test_update(self, mocked_warning): + """Test update.""" + self.thermostat.update() + self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) + self.assertEqual(STATE_HEAT, self.thermostat.current_operation) + self.thermostat._api.status.side_effect = KeyError('boom') + self.thermostat.update() + mocked_warning.assert_called_once_with( + 'Unable to update component %s', self.thermostat.entity_id) + + def test_melissa_state_to_hass(self): + """Test for translate melissa states to hass.""" + self.assertEqual(STATE_OFF, self.thermostat.melissa_state_to_hass(0)) + self.assertEqual(STATE_ON, self.thermostat.melissa_state_to_hass(1)) + self.assertEqual(STATE_IDLE, self.thermostat.melissa_state_to_hass(2)) + self.assertEqual(STATE_UNKNOWN, + self.thermostat.melissa_state_to_hass(3)) + + def test_melissa_op_to_hass(self): + """Test for translate melissa operations to hass.""" + self.assertEqual(STATE_AUTO, self.thermostat.melissa_op_to_hass(0)) + self.assertEqual(STATE_FAN_ONLY, self.thermostat.melissa_op_to_hass(1)) + self.assertEqual(STATE_HEAT, self.thermostat.melissa_op_to_hass(2)) + self.assertEqual(STATE_COOL, self.thermostat.melissa_op_to_hass(3)) + self.assertEqual(STATE_DRY, self.thermostat.melissa_op_to_hass(4)) + self.assertEqual( + STATE_UNKNOWN, self.thermostat.melissa_op_to_hass(5)) + + def test_melissa_fan_to_hass(self): + """Test for translate melissa fan state to hass.""" + self.assertEqual(STATE_AUTO, self.thermostat.melissa_fan_to_hass(0)) + self.assertEqual(SPEED_LOW, self.thermostat.melissa_fan_to_hass(1)) + self.assertEqual(SPEED_MEDIUM, self.thermostat.melissa_fan_to_hass(2)) + self.assertEqual(SPEED_HIGH, self.thermostat.melissa_fan_to_hass(3)) + self.assertEqual(STATE_UNKNOWN, self.thermostat.melissa_fan_to_hass(4)) + + @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') + def test_hass_mode_to_melissa(self, mocked_warning): + """Test for hass operations to melssa.""" + self.assertEqual(0, self.thermostat.hass_mode_to_melissa(STATE_AUTO)) + self.assertEqual( + 1, self.thermostat.hass_mode_to_melissa(STATE_FAN_ONLY)) + self.assertEqual(2, self.thermostat.hass_mode_to_melissa(STATE_HEAT)) + self.assertEqual(3, self.thermostat.hass_mode_to_melissa(STATE_COOL)) + self.assertEqual(4, self.thermostat.hass_mode_to_melissa(STATE_DRY)) + self.thermostat.hass_mode_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s mode", "test") + + @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') + def test_hass_fan_to_melissa(self, mocked_warning): + """Test for translate melissa states to hass.""" + self.assertEqual(0, self.thermostat.hass_fan_to_melissa(STATE_AUTO)) + self.assertEqual(1, self.thermostat.hass_fan_to_melissa(SPEED_LOW)) + self.assertEqual(2, self.thermostat.hass_fan_to_melissa(SPEED_MEDIUM)) + self.assertEqual(3, self.thermostat.hass_fan_to_melissa(SPEED_HIGH)) + self.thermostat.hass_fan_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s fan mode", "test") diff --git a/tests/components/sensor/test_melissa.py b/tests/components/sensor/test_melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..3a13020438f035448d790ea4b6b8c6fabcaf5968 --- /dev/null +++ b/tests/components/sensor/test_melissa.py @@ -0,0 +1,89 @@ +"""Test for Melissa climate component.""" +import unittest +import json +from unittest.mock import Mock + +from homeassistant.components.melissa import DATA_MELISSA +from homeassistant.components.sensor import melissa +from homeassistant.components.sensor.melissa import MelissaTemperatureSensor, \ + MelissaHumiditySensor +from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN +from tests.common import get_test_home_assistant, load_fixture + + +class TestMelissa(unittest.TestCase): + """Tests for Melissa climate.""" + + def setUp(self): # pylint: disable=invalid-name + """Set up test variables.""" + self.hass = get_test_home_assistant() + self._serial = '12345678' + + self.api = Mock() + self.api.fetch_devices.return_value = json.loads(load_fixture( + 'melissa_fetch_devices.json' + )) + self.api.status.return_value = json.loads(load_fixture( + 'melissa_status.json' + )) + + self.api.TEMP = 'temp' + self.api.HUMIDITY = 'humidity' + device = self.api.fetch_devices()[self._serial] + self.temp = MelissaTemperatureSensor(device, self.api) + self.hum = MelissaHumiditySensor(device, self.api) + + def tearDown(self): # pylint: disable=invalid-name + """Teardown this test class. Stop hass.""" + self.hass.stop() + + def test_setup_platform(self): + """Test setup_platform.""" + self.hass.data[DATA_MELISSA] = self.api + + config = {} + add_devices = Mock() + discovery_info = {} + + melissa.setup_platform(self.hass, config, add_devices, discovery_info) + + def test_name(self): + """Test name property.""" + device = self.api.fetch_devices()[self._serial] + self.assertEqual(self.temp.name, '{0} {1}'.format( + device['name'], + self.temp._type + )) + self.assertEqual(self.hum.name, '{0} {1}'.format( + device['name'], + self.hum._type + )) + + def test_state(self): + """Test state property.""" + device = self.api.status()[self._serial] + self.temp.update() + self.assertEqual(self.temp.state, device[self.api.TEMP]) + self.hum.update() + self.assertEqual(self.hum.state, device[self.api.HUMIDITY]) + + def test_unit_of_measurement(self): + """Test unit of measurement property.""" + self.assertEqual(self.temp.unit_of_measurement, TEMP_CELSIUS) + self.assertEqual(self.hum.unit_of_measurement, '%') + + def test_update(self): + """Test for update.""" + self.temp.update() + self.assertEqual(self.temp.state, 27.4) + self.hum.update() + self.assertEqual(self.hum.state, 18.7) + + def test_update_keyerror(self): + """Test for faulty update.""" + self.temp._api.status.return_value = {} + self.temp.update() + self.assertEqual(STATE_UNKNOWN, self.temp.state) + self.hum._api.status.return_value = {} + self.hum.update() + self.assertEqual(STATE_UNKNOWN, self.hum.state) diff --git a/tests/components/test_melissa.py b/tests/components/test_melissa.py new file mode 100644 index 0000000000000000000000000000000000000000..e39ceb1add15147444ff9f4aa1b9c93cc44548de --- /dev/null +++ b/tests/components/test_melissa.py @@ -0,0 +1,38 @@ +"""The test for the Melissa Climate component.""" +import unittest +from tests.common import get_test_home_assistant, MockDependency + +from homeassistant.components import melissa + +VALID_CONFIG = { + "melissa": { + "username": "********", + "password": "********", + } +} + + +class TestMelissa(unittest.TestCase): + """Test the Melissa component.""" + + def setUp(self): # pylint: disable=invalid-name + """Initialize the values for this test class.""" + self.hass = get_test_home_assistant() + self.config = VALID_CONFIG + + def tearDown(self): # pylint: disable=invalid-name + """Teardown this test class. Stop hass.""" + self.hass.stop() + + @MockDependency("melissa") + def test_setup(self, mocked_melissa): + """Test setting up the Melissa component.""" + melissa.setup(self.hass, self.config) + + mocked_melissa.Melissa.assert_called_with( + username="********", password="********") + self.assertIn(melissa.DATA_MELISSA, self.hass.data) + self.assertIsInstance( + self.hass.data[melissa.DATA_MELISSA], type( + mocked_melissa.Melissa()) + ) diff --git a/tests/fixtures/melissa_cur_settings.json b/tests/fixtures/melissa_cur_settings.json new file mode 100644 index 0000000000000000000000000000000000000000..9d7fb61533042c61fd7e841f10758bc83bb09f19 --- /dev/null +++ b/tests/fixtures/melissa_cur_settings.json @@ -0,0 +1,28 @@ +{ + "controller": { + "id": 1, + "user_id": 1, + "serial_number": "12345678", + "mac": "12345678", + "firmware_version": "V1SHTHF", + "name": "Melissa 12345678", + "type": "melissa", + "room_id": null, + "created": "2016-07-06 18:59:46", + "deleted_at": null, + "online": true, + "_relation": { + "command_log": { + "state": 1, + "mode": 2, + "temp": 16, + "fan": 1 + } + } + }, + "_links": { + "self": { + "href": "/v1/controllers/12345678" + } + } +} diff --git a/tests/fixtures/melissa_fetch_devices.json b/tests/fixtures/melissa_fetch_devices.json new file mode 100644 index 0000000000000000000000000000000000000000..4b106a613f7b2be36b677f882334c57a23fc46d7 --- /dev/null +++ b/tests/fixtures/melissa_fetch_devices.json @@ -0,0 +1,27 @@ +{ + "12345678": { + "user_id": 1, + "serial_number": "12345678", + "mac": "12345678", + "firmware_version": "V1SHTHF", + "name": "Melissa 12345678", + "type": "melissa", + "room_id": null, + "created": "2016-07-06 18:59:46", + "id": 1, + "online": true, + "brand_id": 1, + "controller_log": { + "temp": 27.4, + "created": "2018-01-08T21:01:14.281Z", + "raw_temperature": 28928, + "humidity": 18.7, + "raw_humidity": 12946 + }, + "_links": { + "self": { + "href": "/v1/controllers" + } + } + } +} diff --git a/tests/fixtures/melissa_status.json b/tests/fixtures/melissa_status.json new file mode 100644 index 0000000000000000000000000000000000000000..ac240b3df1226e724273a671e8cdb6fc6918185b --- /dev/null +++ b/tests/fixtures/melissa_status.json @@ -0,0 +1,8 @@ +{ + "12345678": { + "temp": 27.4, + "raw_temperature": 28928, + "humidity": 18.7, + "raw_humidity": 12946 + } +}