diff --git a/.coveragerc b/.coveragerc index c4051af5136..a18ec476010 100644 --- a/.coveragerc +++ b/.coveragerc @@ -29,6 +29,9 @@ omit = homeassistant/components/arlo.py homeassistant/components/*/arlo.py + homeassistant/components/asterisk_mbox.py + homeassistant/components/*/asterisk_mbox.py + homeassistant/components/axis.py homeassistant/components/*/axis.py @@ -172,7 +175,7 @@ omit = homeassistant/components/twilio.py homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_call.py - + homeassistant/components/velbus.py homeassistant/components/*/velbus.py @@ -197,7 +200,11 @@ omit = homeassistant/components/*/wink.py homeassistant/components/xiaomi.py - homeassistant/components/*/xiaomi.py + homeassistant/components/binary_sensor/xiaomi.py + homeassistant/components/cover/xiaomi.py + homeassistant/components/light/xiaomi.py + homeassistant/components/sensor/xiaomi.py + homeassistant/components/switch/xiaomi.py homeassistant/components/zabbix.py homeassistant/components/*/zabbix.py @@ -214,6 +221,7 @@ omit = homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/concord232.py + homeassistant/components/alarm_control_panel/egardia.py homeassistant/components/alarm_control_panel/manual_mqtt.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/simplisafe.py @@ -264,6 +272,7 @@ omit = homeassistant/components/device_tracker/cisco_ios.py homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/gpslogger.py + homeassistant/components/device_tracker/huawei_router.py homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/linksys_ap.py homeassistant/components/device_tracker/linksys_smart.py @@ -297,6 +306,7 @@ omit = homeassistant/components/light/blinkt.py homeassistant/components/light/blinksticklight.py homeassistant/components/light/decora.py + homeassistant/components/light/decora_wifi.py homeassistant/components/light/flux_led.py homeassistant/components/light/hue.py homeassistant/components/light/hyperion.py @@ -322,6 +332,7 @@ omit = homeassistant/components/media_extractor.py homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/aquostv.py + homeassistant/components/media_player/bluesound.py homeassistant/components/media_player/braviatv.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/clementine.py @@ -351,6 +362,7 @@ omit = homeassistant/components/media_player/pioneer.py homeassistant/components/media_player/plex.py homeassistant/components/media_player/roku.py + homeassistant/components/media_player/russound_rio.py homeassistant/components/media_player/russound_rnet.py homeassistant/components/media_player/samsungtv.py homeassistant/components/media_player/snapcast.py @@ -437,6 +449,7 @@ omit = homeassistant/components/sensor/fixer.py homeassistant/components/sensor/fritzbox_callmonitor.py homeassistant/components/sensor/fritzbox_netmonitor.py + homeassistant/components/sensor/geizhals.py homeassistant/components/sensor/gitter.py homeassistant/components/sensor/glances.py homeassistant/components/sensor/google_travel_time.py @@ -483,6 +496,7 @@ omit = homeassistant/components/sensor/scrape.py homeassistant/components/sensor/sensehat.py homeassistant/components/sensor/serial_pm.py + homeassistant/components/sensor/shodan.py homeassistant/components/sensor/skybeacon.py homeassistant/components/sensor/sma.py homeassistant/components/sensor/snmp.py @@ -527,17 +541,18 @@ omit = homeassistant/components/switch/orvibo.py homeassistant/components/switch/pilight.py homeassistant/components/switch/pulseaudio_loopback.py + homeassistant/components/switch/rainmachine.py homeassistant/components/switch/rest.py homeassistant/components/switch/rpi_rf.py homeassistant/components/switch/tplink.py homeassistant/components/switch/transmission.py homeassistant/components/switch/wake_on_lan.py - homeassistant/components/switch/xiaomi_vacuum.py homeassistant/components/telegram_bot/* homeassistant/components/thingspeak.py homeassistant/components/tts/amazon_polly.py homeassistant/components/tts/picotts.py homeassistant/components/upnp.py + homeassistant/components/vacuum/roomba.py homeassistant/components/weather/bom.py homeassistant/components/weather/buienradar.py homeassistant/components/weather/metoffice.py diff --git a/.gitignore b/.gitignore index d5c29180e09..26efcc25b85 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ pyvenv.cfg pip-selfcheck.json venv .venv +Pipfile* # vimmy stuff *.swp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a3238e665d..a9c0c21d0d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,11 +4,11 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot The process is straight-forward. - - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0) + - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0) - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant). - Write the code for your device, notification service, sensor, or IoT thing. - Ensure tests work. - Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant. -Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details. +Still interested? Then you should take a peek at the [developer documentation](https://home-assistant.io/developers/) to get more details. diff --git a/Dockerfile b/Dockerfile index a877f154037..f0d5accdf3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,7 @@ COPY requirements_all.txt requirements_all.txt # Uninstall enum34 because some depenndecies install it but breaks Python 3.4+. # See PR #8103 for more info. RUN pip3 install --no-cache-dir -r requirements_all.txt && \ - pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet && \ - pip3 uninstall -y enum34 + pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet # Copy source COPY . . diff --git a/homeassistant/components/alarm_control_panel/egardia.py b/homeassistant/components/alarm_control_panel/egardia.py new file mode 100644 index 00000000000..8ea472a7b19 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/egardia.py @@ -0,0 +1,182 @@ +""" +Interfaces with Egardia/Woonveilig alarm control panel. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/alarm_control_panel.egardia/ +""" +import logging + +import requests +import voluptuous as vol + +import homeassistant.components.alarm_control_panel as alarm +import homeassistant.exceptions as exc +import homeassistant.helpers.config_validation as cv +from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, + CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED) + +REQUIREMENTS = ['pythonegardia==1.0.17'] + +_LOGGER = logging.getLogger(__name__) + +CONF_REPORT_SERVER_CODES = 'report_server_codes' +CONF_REPORT_SERVER_ENABLED = 'report_server_enabled' +CONF_REPORT_SERVER_PORT = 'report_server_port' + +DEFAULT_NAME = 'Egardia' +DEFAULT_PORT = 80 +DEFAULT_REPORT_SERVER_ENABLED = False +DEFAULT_REPORT_SERVER_PORT = 85 +DOMAIN = 'egardia' + +NOTIFICATION_ID = 'egardia_notification' +NOTIFICATION_TITLE = 'Egardia' + +STATES = { + 'ARM': STATE_ALARM_ARMED_AWAY, + 'DAY HOME': STATE_ALARM_ARMED_HOME, + 'DISARM': STATE_ALARM_DISARMED, + 'HOME': STATE_ALARM_ARMED_HOME, + 'TRIGGERED': STATE_ALARM_TRIGGERED, + 'UNKNOWN': STATE_UNKNOWN, +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_REPORT_SERVER_CODES): vol.All(cv.ensure_list), + vol.Optional(CONF_REPORT_SERVER_ENABLED, + default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean, + vol.Optional(CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT): + cv.port, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Egardia platform.""" + from pythonegardia import egardiadevice + + name = config.get(CONF_NAME) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + rs_enabled = config.get(CONF_REPORT_SERVER_ENABLED) + rs_port = config.get(CONF_REPORT_SERVER_PORT) + rs_codes = config.get(CONF_REPORT_SERVER_CODES) + + try: + egardiasystem = egardiadevice.EgardiaDevice( + host, port, username, password, '') + except requests.exceptions.RequestException: + raise exc.PlatformNotReady() + except egardiadevice.UnauthorizedError: + _LOGGER.error("Unable to authorize. Wrong password or username") + return False + + add_devices([EgardiaAlarm( + name, egardiasystem, hass, rs_enabled, rs_port, rs_codes)], True) + + +class EgardiaAlarm(alarm.AlarmControlPanel): + """Representation of a Egardia alarm.""" + + def __init__(self, name, egardiasystem, hass, rs_enabled=False, + rs_port=None, rs_codes=None): + """Initialize object.""" + self._name = name + self._egardiasystem = egardiasystem + self._status = STATE_UNKNOWN + self._rs_enabled = rs_enabled + self._rs_port = rs_port + self._hass = hass + + if rs_codes is not None: + self._rs_codes = rs_codes[0] + else: + self._rs_codes = rs_codes + + if self._rs_enabled: + self.listen_to_system_status() + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._status + + def handle_system_status_event(self, event): + """Handle egardia_system_status_event.""" + if event.data.get('status') is not None: + statuscode = event.data.get('status') + status = self.lookupstatusfromcode(statuscode) + self.parsestatus(status) + + def listen_to_system_status(self): + """Subscribe to egardia_system_status event.""" + self._hass.bus.listen( + 'egardia_system_status', self.handle_system_status_event) + + def lookupstatusfromcode(self, statuscode): + """Look at the rs_codes and returns the status from the code.""" + status = 'UNKNOWN' + if self._rs_codes is not None: + statuscode = str(statuscode).strip() + for i in self._rs_codes: + val = str(self._rs_codes[i]).strip() + if ',' in val: + splitted = val.split(',') + for code in splitted: + code = str(code).strip() + if statuscode == code: + status = i.upper() + break + elif statuscode == val: + status = i.upper() + break + return status + + def parsestatus(self, status): + """Parse the status.""" + newstatus = ([v for k, v in STATES.items() + if status.upper() == k][0]) + self._status = newstatus + + def update(self): + """Update the alarm status.""" + status = self._egardiasystem.getstate() + self.parsestatus(status) + + def alarm_disarm(self, code=None): + """Send disarm command.""" + try: + self._egardiasystem.alarm_disarm() + except requests.exceptions.RequestException as err: + _LOGGER.error("Egardia device exception occurred when " + "sending disarm command: %s", err) + + def alarm_arm_home(self, code=None): + """Send arm home command.""" + try: + self._egardiasystem.alarm_arm_home() + except requests.exceptions.RequestException as err: + _LOGGER.error("Egardia device exception occurred when " + "sending arm home command: %s", err) + + def alarm_arm_away(self, code=None): + """Send arm away command.""" + try: + self._egardiasystem.alarm_arm_away() + except requests.exceptions.RequestException as err: + _LOGGER.error("Egardia device exception occurred when " + "sending arm away command: %s", err) diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index a600a01fcfd..5eb2e9fe7d3 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -16,7 +16,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['simplisafe-python==1.0.3'] +REQUIREMENTS = ['simplisafe-python==1.0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 3547e86dad1..25b6537e255 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -128,19 +128,18 @@ class AlexaIntentsView(http.HomeAssistantView): alexa_intent_info = req.get('intent') alexa_response = AlexaResponse(hass, alexa_intent_info) - if req_type == 'LaunchRequest': - alexa_response.add_speech( - SpeechType.plaintext, - "Hello, and welcome to the future. How may I help?") - return self.json(alexa_response) - - if req_type != 'IntentRequest': + if req_type != 'IntentRequest' and req_type != 'LaunchRequest': _LOGGER.warning('Received unsupported request: %s', req_type) return self.json_message( 'Received unsupported request: {}'.format(req_type), HTTP_BAD_REQUEST) - intent_name = alexa_intent_info['name'] + if req_type == 'LaunchRequest': + intent_name = data.get('session', {}) \ + .get('application', {}) \ + .get('applicationId') + else: + intent_name = alexa_intent_info['name'] try: intent_response = yield from intent.async_handle( diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py new file mode 100644 index 00000000000..c1dafb87a6d --- /dev/null +++ b/homeassistant/components/asterisk_mbox.py @@ -0,0 +1,82 @@ +"""Support for Asterisk Voicemail interface.""" + +import logging + +import voluptuous as vol + + +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import discovery +from homeassistant.const import (CONF_HOST, + CONF_PORT, CONF_PASSWORD) + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import (async_dispatcher_connect, + async_dispatcher_send) + +REQUIREMENTS = ['asterisk_mbox==0.4.0'] + +SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' +SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' + +DOMAIN = 'asterisk_mbox' + +_LOGGER = logging.getLogger(__name__) + + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): int, + vol.Required(CONF_PASSWORD): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up for the Asterisk Voicemail box.""" + conf = config.get(DOMAIN) + + host = conf.get(CONF_HOST) + port = conf.get(CONF_PORT) + password = conf.get(CONF_PASSWORD) + + hass.data[DOMAIN] = AsteriskData(hass, host, port, password) + + discovery.load_platform(hass, "mailbox", DOMAIN, {}, config) + + return True + + +class AsteriskData(object): + """Store Asterisk mailbox data.""" + + def __init__(self, hass, host, port, password): + """Init the Asterisk data object.""" + from asterisk_mbox import Client as asteriskClient + + self.hass = hass + self.client = asteriskClient(host, port, password, self.handle_data) + self.messages = [] + + async_dispatcher_connect( + self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages) + + @callback + def handle_data(self, command, msg): + """Handle changes to the mailbox.""" + from asterisk_mbox.commands import CMD_MESSAGE_LIST + + if command == CMD_MESSAGE_LIST: + _LOGGER.info("AsteriskVM sent updated message list") + self.messages = sorted(msg, + key=lambda item: item['info']['origtime'], + reverse=True) + async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, + self.messages) + + @callback + def _request_messages(self): + """Handle changes to the mailbox.""" + _LOGGER.info("Requesting message list") + self.client.messages() diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 27332bfaa9f..58c86ff0c6d 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -27,7 +27,6 @@ from homeassistant.helpers.restore_state import async_get_last_state from homeassistant.loader import get_platform from homeassistant.util.dt import utcnow import homeassistant.helpers.config_validation as cv -from homeassistant.components.frontend import register_built_in_panel DOMAIN = 'automation' DEPENDENCIES = ['group'] @@ -232,10 +231,6 @@ def async_setup(hass, config): DOMAIN, service, turn_onoff_service_handler, descriptions.get(service), schema=SERVICE_SCHEMA) - if 'frontend' in hass.config.components: - register_built_in_panel(hass, 'automation', 'Automations', - 'mdi:playlist-play') - return True diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 8f2b6bc59b3..4ba29e9b2ba 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -14,7 +14,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -from homeassistant.helpers.deprecation import deprecated_substitute DOMAIN = 'binary_sensor' SCAN_INTERVAL = timedelta(seconds=30) @@ -66,7 +65,6 @@ class BinarySensorDevice(Entity): return STATE_ON if self.is_on else STATE_OFF @property - @deprecated_substitute('sensor_class') def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" return None diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index 620b7fcc5de..412656a3b56 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -20,9 +20,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up an Online Status binary sensor.""" - add_entities((OnlineStatus(config, apcupsd.DATA),)) +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up an APCUPSd Online Status binary sensor.""" + add_devices([OnlineStatus(config, apcupsd.DATA)], True) class OnlineStatus(BinarySensorDevice): @@ -33,7 +33,6 @@ class OnlineStatus(BinarySensorDevice): self._config = config self._data = data self._state = None - self.update() @property def name(self): diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 5b58a6cbf6a..73751ef14bb 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -13,10 +13,9 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( - CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS) + CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS) from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated _LOGGER = logging.getLogger(__name__) @@ -26,7 +25,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RESOURCE): cv.url, vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_PIN): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -35,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the aREST binary sensor.""" resource = config.get(CONF_RESOURCE) pin = config.get(CONF_PIN) - device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = config.get(CONF_DEVICE_CLASS) try: response = requests.get(resource, timeout=10).json() diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py index 38f362fb1bb..5e69dcc9109 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -37,7 +37,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for device in bloomsky.BLOOMSKY.devices.values(): for variable in sensors: - add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)]) + add_devices( + [BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True) class BloomSkySensor(BinarySensorDevice): @@ -50,7 +51,7 @@ class BloomSkySensor(BinarySensorDevice): self._sensor_name = sensor_name self._name = '{} {}'.format(device['DeviceName'], sensor_name) self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name) - self.update() + self._state = None @property def name(self): diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index 6ed0e40409a..2289ad5d906 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -4,19 +4,18 @@ Support for custom shell commands to retrieve values. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.command_line/ """ -from datetime import timedelta import logging +from datetime import timedelta import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA) from homeassistant.components.sensor.command_line import CommandSensorData from homeassistant.const import ( CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, - CONF_SENSOR_CLASS, CONF_COMMAND, CONF_DEVICE_CLASS) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated + CONF_COMMAND, CONF_DEVICE_CLASS) _LOGGER = logging.getLogger(__name__) @@ -31,7 +30,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, }) @@ -44,15 +42,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): command = config.get(CONF_COMMAND) payload_off = config.get(CONF_PAYLOAD_OFF) payload_on = config.get(CONF_PAYLOAD_ON) - device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = config.get(CONF_DEVICE_CLASS) value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass - data = CommandSensorData(command) + data = CommandSensorData(hass, command) add_devices([CommandBinarySensor( hass, data, name, device_class, payload_on, payload_off, - value_template)]) + value_template)], True) class CommandBinarySensor(BinarySensorDevice): @@ -69,7 +67,6 @@ class CommandBinarySensor(BinarySensorDevice): self._payload_on = payload_on self._payload_off = payload_off self._value_template = value_template - self.update() @property def name(self): diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index fc8c0b81edf..7ba88f76611 100755 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -72,9 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) ) - add_devices(sensors) - - return True + add_devices(sensors, True) def get_opening_type(zone): @@ -100,7 +98,6 @@ class Concord232ZoneSensor(BinarySensorDevice): self._zone = zone self._number = zone['number'] self._zone_type = zone_type - self.update() @property def device_class(self): @@ -130,7 +127,7 @@ class Concord232ZoneSensor(BinarySensorDevice): if last_update > datetime.timedelta(seconds=1): self._client.zones = self._client.list_zones() self._client.last_zone_update = datetime.datetime.now() - _LOGGER.debug("Updated from Zone: %s", self._zone['name']) + _LOGGER.debug("Updated from zone: %s", self._zone['name']) if hasattr(self._client, 'zones'): self._zone = next((x for x in self._client.zones diff --git a/homeassistant/components/binary_sensor/digital_ocean.py b/homeassistant/components/binary_sensor/digital_ocean.py index d9a0ac6711b..140c84358c7 100644 --- a/homeassistant/components/binary_sensor/digital_ocean.py +++ b/homeassistant/components/binary_sensor/digital_ocean.py @@ -19,7 +19,7 @@ from homeassistant.components.digital_ocean import ( _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Droplet' -DEFAULT_SENSOR_CLASS = 'moving' +DEFAULT_DEVICE_CLASS = 'moving' DEPENDENCIES = ['digital_ocean'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -69,7 +69,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return DEFAULT_SENSOR_CLASS + return DEFAULT_DEVICE_CLASS @property def device_state_attributes(self): diff --git a/homeassistant/components/binary_sensor/ecobee.py b/homeassistant/components/binary_sensor/ecobee.py index d14a1124390..214efb870b9 100644 --- a/homeassistant/components/binary_sensor/ecobee.py +++ b/homeassistant/components/binary_sensor/ecobee.py @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev.append(EcobeeBinarySensor(sensor['name'], index)) - add_devices(dev) + add_devices(dev, True) class EcobeeBinarySensor(BinarySensorDevice): @@ -39,7 +39,6 @@ class EcobeeBinarySensor(BinarySensorDevice): self.index = sensor_index self._state = None self._device_class = 'occupancy' - self.update() @property def name(self): diff --git a/homeassistant/components/binary_sensor/enocean.py b/homeassistant/components/binary_sensor/enocean.py index 36574450e4d..d2ecd5e17e1 100644 --- a/homeassistant/components/binary_sensor/enocean.py +++ b/homeassistant/components/binary_sensor/enocean.py @@ -12,9 +12,8 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) from homeassistant.components import enocean from homeassistant.const import ( - CONF_NAME, CONF_ID, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS) + CONF_NAME, CONF_ID, CONF_DEVICE_CLASS) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated _LOGGER = logging.getLogger(__name__) @@ -24,7 +23,6 @@ DEFAULT_NAME = 'EnOcean binary sensor' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -33,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Binary Sensor platform for EnOcean.""" dev_id = config.get(CONF_ID) devname = config.get(CONF_NAME) - device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = config.get(CONF_DEVICE_CLASS) add_devices([EnOceanBinarySensor(dev_id, devname, device_class)]) diff --git a/homeassistant/components/binary_sensor/iss.py b/homeassistant/components/binary_sensor/iss.py index 44d2b16fcc9..3b927853c00 100644 --- a/homeassistant/components/binary_sensor/iss.py +++ b/homeassistant/components/binary_sensor/iss.py @@ -64,7 +64,6 @@ class IssBinarySensor(BinarySensorDevice): self._state = None self._name = name self._show_on_map = show - self.update() @property def name(self): diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index fe19523c5b2..3702b32d586 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -15,10 +15,9 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF, - CONF_SENSOR_CLASS, CONF_DEVICE_CLASS) + CONF_DEVICE_CLASS) from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated _LOGGER = logging.getLogger(__name__) @@ -31,7 +30,6 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -49,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): async_add_devices([MqttBinarySensor( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), - get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS), + config.get(CONF_DEVICE_CLASS), config.get(CONF_QOS), config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_OFF), diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index ac479ef4277..13b9fc1f005 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -44,18 +44,19 @@ CONF_WELCOME_SENSORS = 'welcome_sensors' CONF_PRESENCE_SENSORS = 'presence_sensors' CONF_TAG_SENSORS = 'tag_sensors' +DEFAULT_TIMEOUT = 15 +DEFAULT_OFFSET = 90 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOME): cv.string, - vol.Optional(CONF_TIMEOUT): cv.positive_int, - vol.Optional(CONF_OFFSET): cv.positive_int, vol.Optional(CONF_CAMERAS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional( - CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES.keys()): - vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]), - vol.Optional( - CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES.keys()): + vol.Optional(CONF_HOME): cv.string, + vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int, + vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES): vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]), + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES): + vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]), }) @@ -63,16 +64,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the access to Netatmo binary sensor.""" netatmo = get_component('netatmo') - home = config.get(CONF_HOME, None) - timeout = config.get(CONF_TIMEOUT, 15) - offset = config.get(CONF_OFFSET, 90) + home = config.get(CONF_HOME) + timeout = config.get(CONF_TIMEOUT) + offset = config.get(CONF_OFFSET) module_name = None import lnetatmo try: data = CameraData(netatmo.NETATMO_AUTH, home) - if data.get_camera_names() == []: + if not data.get_camera_names(): return None except lnetatmo.NoDevice: return None @@ -93,7 +94,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for variable in welcome_sensors: add_devices([NetatmoBinarySensor( data, camera_name, module_name, home, timeout, - offset, camera_type, variable)]) + offset, camera_type, variable)], True) if camera_type == 'NOC': if CONF_CAMERAS in config: if config[CONF_CAMERAS] != [] and \ @@ -102,14 +103,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for variable in presence_sensors: add_devices([NetatmoBinarySensor( data, camera_name, module_name, home, timeout, offset, - camera_type, variable)]) + camera_type, variable)], True) for module_name in data.get_module_names(camera_name): for variable in tag_sensors: camera_type = None add_devices([NetatmoBinarySensor( data, camera_name, module_name, home, timeout, offset, - camera_type, variable)]) + camera_type, variable)], True) class NetatmoBinarySensor(BinarySensorDevice): @@ -137,7 +138,7 @@ class NetatmoBinarySensor(BinarySensorDevice): self._unique_id = "Netatmo_binary_sensor {0} - {1}".format( self._name, camera_id) self._cameratype = camera_type - self.update() + self._state = None @property def name(self): diff --git a/homeassistant/components/binary_sensor/octoprint.py b/homeassistant/components/binary_sensor/octoprint.py index f4e4e04717d..129b5250431 100644 --- a/homeassistant/components/binary_sensor/octoprint.py +++ b/homeassistant/components/binary_sensor/octoprint.py @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0], SENSOR_TYPES[octo_type][1], 'flags') devices.append(new_sensor) - add_devices(devices) + add_devices(devices, True) class OctoPrintBinarySensor(BinarySensorDevice): @@ -69,8 +69,6 @@ class OctoPrintBinarySensor(BinarySensorDevice): self.api_endpoint = endpoint self.api_group = group self.api_tool = tool - # Set initial state - self.update() _LOGGER.debug("Created OctoPrint binary sensor %r", self) @property diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py index 1919c7ab64d..0830d86dc2a 100644 --- a/homeassistant/components/binary_sensor/ping.py +++ b/homeassistant/components/binary_sensor/ping.py @@ -28,7 +28,7 @@ CONF_PING_COUNT = 'count' DEFAULT_NAME = 'Ping Binary sensor' DEFAULT_PING_COUNT = 5 -DEFAULT_SENSOR_CLASS = 'connectivity' +DEFAULT_DEVICE_CLASS = 'connectivity' SCAN_INTERVAL = timedelta(minutes=5) @@ -73,7 +73,7 @@ class PingBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return DEFAULT_SENSOR_CLASS + return DEFAULT_DEVICE_CLASS @property def is_on(self): diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 6d1745700bd..1f8d0ebe2f7 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -14,11 +14,10 @@ from homeassistant.components.binary_sensor import ( from homeassistant.components.sensor.rest import RestData from homeassistant.const import ( CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE, - CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, + CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated _LOGGER = logging.getLogger(__name__) @@ -35,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_PAYLOAD): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, @@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) headers = config.get(CONF_HEADERS) - device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = config.get(CONF_DEVICE_CLASS) value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass @@ -74,7 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False add_devices([RestBinarySensor( - hass, rest, name, device_class, value_template)]) + hass, rest, name, device_class, value_template)], True) class RestBinarySensor(BinarySensorDevice): @@ -89,7 +87,6 @@ class RestBinarySensor(BinarySensorDevice): self._state = False self._previous_data = None self._value_template = value_template - self.update() @property def name(self): diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 989f8b358af..330e8eaea9d 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -15,11 +15,9 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, - CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS, - EVENT_HOMEASSISTANT_START, STATE_ON) + CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import async_get_last_state @@ -30,7 +28,6 @@ SENSOR_SCHEMA = vol.Schema({ vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -49,8 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): entity_ids = (device_config.get(ATTR_ENTITY_ID) or value_template.extract_entities()) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) - device_class = get_deprecated( - device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = device_config.get(CONF_DEVICE_CLASS) if value_template is not None: value_template.hass = hass diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py index b4891906a01..866e16ecbe2 100644 --- a/homeassistant/components/binary_sensor/threshold.py +++ b/homeassistant/components/binary_sensor/threshold.py @@ -13,10 +13,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS, + CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, ATTR_ENTITY_ID, CONF_DEVICE_CLASS) from homeassistant.core import callback -from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) @@ -38,7 +37,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_THRESHOLD): vol.Coerce(float), vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -50,7 +48,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): name = config.get(CONF_NAME) threshold = config.get(CONF_THRESHOLD) limit_type = config.get(CONF_TYPE) - device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = config.get(CONF_DEVICE_CLASS) async_add_devices( [ThresholdSensor(hass, entity_id, name, threshold, limit_type, diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index 82585acfc38..88fdb448330 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -16,9 +16,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_SENSOR_CLASS, - CONF_DEVICE_CLASS, STATE_UNKNOWN,) -from homeassistant.helpers.deprecation import get_deprecated + ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, STATE_UNKNOWN) from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import track_state_change @@ -32,7 +30,6 @@ SENSOR_SCHEMA = vol.Schema({ vol.Optional(CONF_ATTRIBUTE): cv.string, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(CONF_INVERT, default=False): cv.boolean, - vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, }) @@ -50,8 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): entity_id = device_config[ATTR_ENTITY_ID] attribute = device_config.get(CONF_ATTRIBUTE) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) - device_class = get_deprecated( - device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS) + device_class = device_config.get(CONF_DEVICE_CLASS) invert = device_config[CONF_INVERT] sensors.append( diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py index 471b3917054..39f520ddc6d 100644 --- a/homeassistant/components/binary_sensor/volvooncall.py +++ b/homeassistant/components/binary_sensor/volvooncall.py @@ -34,5 +34,5 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice): @property def device_class(self): - """Return the class of this sensor, from SENSOR_CLASSES.""" + """Return the class of this sensor, from DEVICE_CLASSES.""" return 'safety' diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index cc5fb3ed572..fc18648f907 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -23,9 +23,7 @@ def get_device(values, **kwargs): """Create Z-Wave entity device.""" device_mapping = workaround.get_device_mapping(values.primary) if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT: - # Default the multiplier to 4 - re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4 - return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8) + return ZWaveTriggerSensor(values, "motion") if workaround.get_device_component_mapping(values.primary) == DOMAIN: return ZWaveBinarySensor(values, None) @@ -62,15 +60,21 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): class ZWaveTriggerSensor(ZWaveBinarySensor): """Representation of a stateless sensor within Z-Wave.""" - def __init__(self, values, device_class, re_arm_sec=60): + def __init__(self, values, device_class): """Initialize the sensor.""" super(ZWaveTriggerSensor, self).__init__(values, device_class) - self.re_arm_sec = re_arm_sec + # Set default off delay to 60 sec + self.re_arm_sec = 60 self.invalidate_after = None def update_properties(self): """Handle value changes for this entity's node.""" self._state = self.values.primary.data + _LOGGER.debug('off_delay=%s', self.values.off_delay) + # Set re_arm_sec if off_delay is provided from the sensor + if self.values.off_delay: + _LOGGER.debug('off_delay.data=%s', self.values.off_delay.data) + self.re_arm_sec = self.values.off_delay.data * 8 # only allow this value to be true for re_arm secs if not self.hass: return diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index 86061aa3d1d..6e9f036074a 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -6,7 +6,6 @@ https://home-assistant.io/components/camera.foscam/ """ import logging -import requests import voluptuous as vol from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) @@ -16,11 +15,15 @@ from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['pyfoscam==1.2'] + CONF_IP = 'ip' DEFAULT_NAME = 'Foscam Camera' DEFAULT_PORT = 88 +FOSCAM_COMM_ERROR = -8 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_IP): cv.string, vol.Required(CONF_PASSWORD): cv.string, @@ -33,46 +36,60 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a Foscam IP Camera.""" - add_devices([FoscamCamera(config)]) + add_devices([FoscamCam(config)]) -class FoscamCamera(Camera): +class FoscamCam(Camera): """An implementation of a Foscam IP camera.""" def __init__(self, device_info): """Initialize a Foscam camera.""" - super(FoscamCamera, self).__init__() + super(FoscamCam, self).__init__() ip_address = device_info.get(CONF_IP) port = device_info.get(CONF_PORT) - - self._base_url = 'http://{}:{}/'.format(ip_address, port) - - uri_template = self._base_url \ - + 'cgi-bin/CGIProxy.fcgi?' \ - + 'cmd=snapPicture2&usr={}&pwd={}' - self._username = device_info.get(CONF_USERNAME) self._password = device_info.get(CONF_PASSWORD) - self._snap_picture_url = uri_template.format( - self._username, - self._password - ) self._name = device_info.get(CONF_NAME) + self._motion_status = False - _LOGGER.info("Using the following URL for %s: %s", - self._name, uri_template.format('***', '***')) + from foscam import FoscamCamera + + self._foscam_session = FoscamCamera(ip_address, port, self._username, + self._password) def camera_image(self): """Return a still image reponse from the camera.""" # Send the request to snap a picture and return raw jpg data # Handle exception if host is not reachable or url failed - try: - response = requests.get(self._snap_picture_url, timeout=10) - except requests.exceptions.ConnectionError: + result, response = self._foscam_session.snap_picture_2() + if result == FOSCAM_COMM_ERROR: return None + + return response + + @property + def motion_detection_enabled(self): + """Camera Motion Detection Status.""" + return self._motion_status + + def enable_motion_detection(self): + """Enable motion detection in camera.""" + ret, err = self._foscam_session.enable_motion_detection() + if ret == FOSCAM_COMM_ERROR: + _LOGGER.debug("Unable to communicate with Foscam Camera: %s", err) + self._motion_status = True else: - return response.content + self._motion_status = False + + def disable_motion_detection(self): + """Disable motion detection.""" + ret, err = self._foscam_session.disable_motion_detection() + if ret == FOSCAM_COMM_ERROR: + _LOGGER.debug("Unable to communicate with Foscam Camera: %s", err) + self._motion_status = True + else: + self._motion_status = False @property def name(self): diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6dd66817d43..1f919301254 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -86,13 +86,17 @@ SET_AUX_HEAT_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_AUX_HEAT): cv.boolean, }) -SET_TEMPERATURE_SCHEMA = vol.Schema({ - vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_OPERATION_MODE): cv.string, -}) +SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( + cv.has_at_least_one_key( + ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW), + { + vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_OPERATION_MODE): cv.string, + } +)) SET_FAN_MODE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_FAN_MODE): cv.string, diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 65e30e3cb26..4ff87aa67ab 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -75,7 +75,8 @@ def _setup_round(username, password, config, add_devices): zones = evo_api.temperatures(force_refresh=True) for i, zone in enumerate(zones): add_devices( - [RoundThermostat(evo_api, zone['id'], i == 0, away_temp)] + [RoundThermostat(evo_api, zone['id'], i == 0, away_temp)], + True ) except socket.error: _LOGGER.error( @@ -115,9 +116,9 @@ def _setup_us(username, password, config, add_devices): class RoundThermostat(ClimateDevice): """Representation of a Honeywell Round Connected thermostat.""" - def __init__(self, device, zone_id, master, away_temp): + def __init__(self, client, zone_id, master, away_temp): """Initialize the thermostat.""" - self.device = device + self.client = client self._current_temperature = None self._target_temperature = None self._name = 'round connected' @@ -126,7 +127,6 @@ class RoundThermostat(ClimateDevice): self._is_dhw = False self._away_temp = away_temp self._away = False - self.update() @property def name(self): @@ -155,12 +155,12 @@ class RoundThermostat(ClimateDevice): temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return - self.device.set_temperature(self._name, temperature) + self.client.set_temperature(self._name, temperature) @property def current_operation(self: ClimateDevice) -> str: """Get the current operation of the system.""" - return getattr(self.device, ATTR_SYSTEM_MODE, None) + return getattr(self.client, ATTR_SYSTEM_MODE, None) @property def is_away_mode_on(self): @@ -169,8 +169,8 @@ class RoundThermostat(ClimateDevice): def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: """Set the HVAC mode for the thermostat.""" - if hasattr(self.device, ATTR_SYSTEM_MODE): - self.device.system_mode = operation_mode + if hasattr(self.client, ATTR_SYSTEM_MODE): + self.client.system_mode = operation_mode def turn_away_mode_on(self): """Turn away on. @@ -180,19 +180,19 @@ class RoundThermostat(ClimateDevice): it doesn't get overwritten when away mode is switched on. """ self._away = True - self.device.set_temperature(self._name, self._away_temp) + self.client.set_temperature(self._name, self._away_temp) def turn_away_mode_off(self): """Turn away off.""" self._away = False - self.device.cancel_temp_override(self._name) + self.client.cancel_temp_override(self._name) def update(self): """Get the latest date.""" try: # Only refresh if this is the "master" device, # others will pick up the cache - for val in self.device.temperatures(force_refresh=self._master): + for val in self.client.temperatures(force_refresh=self._master): if val['id'] == self._id: data = val @@ -210,6 +210,12 @@ class RoundThermostat(ClimateDevice): self._name = data['name'] self._is_dhw = False + # The underlying library doesn't expose the thermostat's mode + # but we can pull it out of the big dictionary of information. + device = self.client.devices[self._id] + self.client.system_mode = device[ + 'thermostat']['changeableValues']['mode'] + class HoneywellUSThermostat(ClimateDevice): """Representation of a Honeywell US Thermostat.""" diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py index 3706a24bb52..369b01e53de 100755 --- a/homeassistant/components/climate/netatmo.py +++ b/homeassistant/components/climate/netatmo.py @@ -36,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_callback_devices, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the NetAtmo Thermostat.""" netatmo = get_component('netatmo') device = config.get(CONF_RELAY) @@ -49,7 +49,7 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None): if config[CONF_THERMOSTAT] != [] and \ module_name not in config[CONF_THERMOSTAT]: continue - add_callback_devices([NetatmoThermostat(data, module_name)]) + add_devices([NetatmoThermostat(data, module_name)], True) except lnetatmo.NoDevice: return None @@ -64,7 +64,6 @@ class NetatmoThermostat(ClimateDevice): self._name = module_name self._target_temperature = None self._away = None - self.update() @property def name(self): diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 22e09ad0161..6daeebf9f55 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.exception("Unable to connect to Radio Thermostat: %s", host) - add_devices(tstats) + add_devices(tstats, True) class RadioThermostat(ClimateDevice): @@ -89,7 +89,6 @@ class RadioThermostat(ClimateDevice): self._away = False self._away_temps = away_temps self._prev_temp = None - self.update() self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF] @property diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/climate/tado.py index 459cbec0497..00bed936bd7 100644 --- a/homeassistant/components/climate/tado.py +++ b/homeassistant/components/climate/tado.py @@ -288,7 +288,7 @@ class TadoClimate(ClimateDevice): if 'setting' in overlay_data: setting_data = overlay_data['setting'] - setting = setting is not None + setting = setting_data is not None if setting: if 'mode' in setting_data: diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index 576ff09bf2b..a40e1f64043 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -1,12 +1,17 @@ """Provide configuration end points for Z-Wave.""" import asyncio +import homeassistant.core as ha +from homeassistant.const import HTTP_NOT_FOUND +from homeassistant.components.http import HomeAssistantView from homeassistant.components.config import EditKeyBasedConfigView -from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY +from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY import homeassistant.helpers.config_validation as cv CONFIG_PATH = 'zwave_device_config.yaml' +OZW_LOG_FILENAME = 'OZW_Log.txt' +URL_API_OZW_LOG = '/api/zwave/ozwlog' @asyncio.coroutine @@ -16,4 +21,123 @@ def async_setup(hass): 'zwave', 'device_config', CONFIG_PATH, cv.entity_id, DEVICE_CONFIG_SCHEMA_ENTRY )) + hass.http.register_view(ZWaveNodeValueView) + hass.http.register_view(ZWaveNodeGroupView) + hass.http.register_view(ZWaveNodeConfigView) + hass.http.register_view(ZWaveUserCodeView) + hass.http.register_static_path( + URL_API_OZW_LOG, hass.config.path(OZW_LOG_FILENAME), False) + return True + + +class ZWaveNodeValueView(HomeAssistantView): + """View to return the node values.""" + + url = r"/api/zwave/values/{node_id:\d+}" + name = "api:zwave:values" + + @ha.callback + def get(self, request, node_id): + """Retrieve groups of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + values_list = hass.data[const.DATA_ENTITY_VALUES] + + values_data = {} + # Return a list of values for this node that are used as a + # primary value for an entity + for entity_values in values_list: + if entity_values.primary.node.node_id != nodeid: + continue + + values_data[entity_values.primary.value_id] = { + 'label': entity_values.primary.label, + 'index': entity_values.primary.index, + 'instance': entity_values.primary.instance, + } + return self.json(values_data) + + +class ZWaveNodeGroupView(HomeAssistantView): + """View to return the nodes group configuration.""" + + url = r"/api/zwave/groups/{node_id:\d+}" + name = "api:zwave:groups" + + @ha.callback + def get(self, request, node_id): + """Retrieve groups of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(const.DATA_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + groupdata = node.groups + groups = {} + for key, value in groupdata.items(): + groups[key] = {'associations': value.associations, + 'association_instances': + value.associations_instances, + 'label': value.label, + 'max_associations': value.max_associations} + return self.json(groups) + + +class ZWaveNodeConfigView(HomeAssistantView): + """View to return the nodes configuration options.""" + + url = r"/api/zwave/config/{node_id:\d+}" + name = "api:zwave:config" + + @ha.callback + def get(self, request, node_id): + """Retrieve configurations of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(const.DATA_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + config = {} + for value in ( + node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION) + .values()): + config[value.index] = {'label': value.label, + 'type': value.type, + 'help': value.help, + 'data_items': value.data_items, + 'data': value.data, + 'max': value.max, + 'min': value.min} + return self.json(config) + + +class ZWaveUserCodeView(HomeAssistantView): + """View to return the nodes usercode configuration.""" + + url = r"/api/zwave/usercodes/{node_id:\d+}" + name = "api:zwave:usercodes" + + @ha.callback + def get(self, request, node_id): + """Retrieve usercodes of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(const.DATA_NETWORK) + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + usercodes = {} + if not node.has_command_class(const.COMMAND_CLASS_USER_CODE): + return self.json(usercodes) + for value in ( + node.get_values(class_id=const.COMMAND_CLASS_USER_CODE) + .values()): + if value.genre != const.GENRE_USER: + continue + usercodes[value.index] = {'code': value.data, + 'label': value.label, + 'length': len(value.data)} + return self.json(usercodes) diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/cover/garadget.py index 02e970a0d0b..22f5fd889a2 100644 --- a/homeassistant/components/cover/garadget.py +++ b/homeassistant/components/cover/garadget.py @@ -18,10 +18,10 @@ from homeassistant.const import ( _LOGGER = logging.getLogger(__name__) -ATTR_AVAILABLE = "available" -ATTR_SENSOR_STRENGTH = "sensor reflection rate" -ATTR_SIGNAL_STRENGTH = "wifi signal strength (dB)" -ATTR_TIME_IN_STATE = "time in state" +ATTR_AVAILABLE = 'available' +ATTR_SENSOR_STRENGTH = 'sensor_reflection_rate' +ATTR_SIGNAL_STRENGTH = 'wifi_signal_strength' +ATTR_TIME_IN_STATE = 'time_in_state' DEFAULT_NAME = 'Garadget' diff --git a/homeassistant/components/cover/lutron_caseta.py b/homeassistant/components/cover/lutron_caseta.py index 6857aaebf9b..648dba98ca6 100644 --- a/homeassistant/components/cover/lutron_caseta.py +++ b/homeassistant/components/cover/lutron_caseta.py @@ -8,11 +8,10 @@ import logging from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE) + CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION) from homeassistant.components.lutron_caseta import ( LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) - _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['lutron_caseta'] @@ -38,13 +37,18 @@ class LutronCasetaCover(LutronCasetaDevice, CoverDevice): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION @property def is_closed(self): """Return if the cover is closed.""" return self._state["current_state"] < 1 + @property + def current_cover_position(self): + """Return the current position of cover.""" + return self._state["current_state"] + def close_cover(self): """Close the cover.""" self._smartbridge.set_value(self._device_id, 0) diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py index 07be998035c..1ee3ea00476 100644 --- a/homeassistant/components/cover/rpi_gpio.py +++ b/homeassistant/components/cover/rpi_gpio.py @@ -24,9 +24,13 @@ CONF_RELAY_PIN = 'relay_pin' CONF_RELAY_TIME = 'relay_time' CONF_STATE_PIN = 'state_pin' CONF_STATE_PULL_MODE = 'state_pull_mode' +CONF_INVERT_STATE = 'invert_state' +CONF_INVERT_RELAY = 'invert_relay' DEFAULT_RELAY_TIME = .2 DEFAULT_STATE_PULL_MODE = 'UP' +DEFAULT_INVERT_STATE = False +DEFAULT_INVERT_RELAY = False DEPENDENCIES = ['rpi_gpio'] _COVERS_SCHEMA = vol.All( @@ -45,6 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE): cv.string, vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int, + vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean, + vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean, }) @@ -53,13 +59,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the RPi cover platform.""" relay_time = config.get(CONF_RELAY_TIME) state_pull_mode = config.get(CONF_STATE_PULL_MODE) + invert_state = config.get(CONF_INVERT_STATE) + invert_relay = config.get(CONF_INVERT_RELAY) covers = [] covers_conf = config.get(CONF_COVERS) for cover in covers_conf: covers.append(RPiGPIOCover( cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN], - state_pull_mode, relay_time)) + state_pull_mode, relay_time, invert_state, invert_relay)) add_devices(covers) @@ -67,7 +75,7 @@ class RPiGPIOCover(CoverDevice): """Representation of a Raspberry GPIO cover.""" def __init__(self, name, relay_pin, state_pin, state_pull_mode, - relay_time): + relay_time, invert_state, invert_relay): """Initialize the cover.""" self._name = name self._state = False @@ -75,9 +83,11 @@ class RPiGPIOCover(CoverDevice): self._state_pin = state_pin self._state_pull_mode = state_pull_mode self._relay_time = relay_time + self._invert_state = invert_state + self._invert_relay = invert_relay rpi_gpio.setup_output(self._relay_pin) rpi_gpio.setup_input(self._state_pin, self._state_pull_mode) - rpi_gpio.write_output(self._relay_pin, True) + rpi_gpio.write_output(self._relay_pin, not self._invert_relay) @property def unique_id(self): @@ -96,13 +106,13 @@ class RPiGPIOCover(CoverDevice): @property def is_closed(self): """Return true if cover is closed.""" - return self._state + return self._state != self._invert_state def _trigger(self): """Trigger the cover.""" - rpi_gpio.write_output(self._relay_pin, False) + rpi_gpio.write_output(self._relay_pin, self._invert_relay) sleep(self._relay_time) - rpi_gpio.write_output(self._relay_pin, True) + rpi_gpio.write_output(self._relay_pin, not self._invert_relay) def close_cover(self): """Close the cover.""" diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 187e899aacd..2f1dde05bab 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -31,6 +31,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [ 'sensor', 'switch', 'tts', + 'mailbox', ] diff --git a/homeassistant/components/device_tracker/huawei_router.py b/homeassistant/components/device_tracker/huawei_router.py new file mode 100644 index 00000000000..b78683696cf --- /dev/null +++ b/homeassistant/components/device_tracker/huawei_router.py @@ -0,0 +1,145 @@ +""" +Support for HUAWEI routers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.huawei/ +""" +import base64 +import logging +import re +from collections import namedtuple + +import requests +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME + +_LOGGER = logging.getLogger(__name__) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string +}) + + +# pylint: disable=unused-argument +def get_scanner(hass, config): + """Validate the configuration and return a HUAWEI scanner.""" + scanner = HuaweiDeviceScanner(config[DOMAIN]) + + return scanner + + +Device = namedtuple('Device', ['name', 'ip', 'mac', 'state']) + + +class HuaweiDeviceScanner(DeviceScanner): + """This class queries a router running HUAWEI firmware.""" + + ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);') + DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),') + DEVICE_ATTR_REGEX = re.compile( + '"(?P.*?)","(?P.*?)",' + '"(?P.*?)","(?P.*?)",' + '"(?P.*?)","(?P.*?)",' + '"(?P.*?)","(?P.*?)",' + '"(?P