def set_status(self, status): """Set device status.""" if self._json_state['control_url']: url = CONST.BASE_URL + self._json_state['control_url'] status_data = {'status': str(status)} response = self._abode.send_request(method="put", url=url, data=status_data) response_object = json.loads(response.text) _LOGGER.debug("Set Status Response: %s", response.text) if response_object['id'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) if response_object['status'] != str(status): raise AbodeException((ERROR.SET_STATUS_STATE)) # Note: Status result is of int type, not of new status of device. # Seriously, why would you do that? # So, can't set status here must be done at device level. _LOGGER.info("Set device %s status to: %s", self.device_id, status) return True return False
def set_level(self, level): """Set device level.""" if self._json_state['control_url']: url = CONST.BASE_URL + self._json_state['control_url'] level_data = {'level': str(level)} response = self._abode.send_request("put", url, data=level_data) response_object = json.loads(response.text) _LOGGER.debug("Set Level Response: %s", response.text) if response_object['id'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) if response_object['level'] != str(level): raise AbodeException((ERROR.SET_STATUS_STATE)) # TODO: Figure out where level is indicated in device json object self.update(response_object) _LOGGER.info("Set device %s level to: %s", self.device_id, level) return True return False
def set_mode(self, mode): """Set Abode alarm mode.""" if not mode: raise AbodeException(ERROR.MISSING_ALARM_MODE) elif mode.lower() not in CONST.ALL_MODES: raise AbodeException(ERROR.INVALID_ALARM_MODE, CONST.ALL_MODES) mode = mode.lower() response = self._abode.send_request( "put", CONST.get_panel_mode_url(self._area, mode)) _LOGGER.debug("Set Alarm Home Response: %s", response.text) response_object = json.loads(response.text) if response_object['area'] != self._area: raise AbodeException(ERROR.SET_MODE_AREA) if response_object['mode'] != mode: raise AbodeException(ERROR.SET_MODE_MODE) self._json_state['mode'][(self.device_id)] = response_object['mode'] _LOGGER.info("Set alarm %s mode to: %s", self._device_id, response_object['mode']) return True
def set_color_temp(self, color_temp): """Set device color.""" if self._json_state['control_url']: url = CONST.INTEGRATIONS_URL + self._device_uuid color_data = { 'action': 'setcolortemperature', 'colorTemperature': int(color_temp) } response = self._abode.send_request("post", url, data=color_data) response_object = json.loads(response.text) _LOGGER.debug("Set Color Temp Response: %s", response.text) if response_object['idForPanel'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) if response_object['colorTemperature'] != int(color_temp): raise AbodeException((ERROR.SET_STATUS_STATE)) self.update(response_object) _LOGGER.info("Set device %s color_temp to: %s", self.device_id, color_temp) return True return False
def set_color(self, color): """Set device color.""" if self._json_state['control_url']: url = CONST.INTEGRATIONS_URL + self._device_uuid hue, saturation = color color_data = { 'action': 'setcolor', 'hue': int(hue), 'saturation': int(saturation) } response = self._abode.send_request("post", url, data=color_data) response_object = json.loads(response.text) _LOGGER.debug("Set Color Response: %s", response.text) if response_object['idForPanel'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) if (response_object['hue'] != int(hue) or response_object['saturation'] != int(saturation)): raise AbodeException((ERROR.SET_STATUS_STATE)) self.update(response_object) _LOGGER.info("Set device %s hue to: %s", self.device_id, hue) return True return False
def privacy_mode(self, enable): """Set camera privacy mode (camera on/off).""" if self._json_state['privacy']: privacy = '1' if enable else '0' url = CONST.PARAMS_URL + self.device_id camera_data = { 'mac': self._json_state['camera_mac'], 'privacy': privacy, 'action': 'setParam', 'id': self.device_id } response = self._abode.send_request(method="put", url=url, data=camera_data) response_object = json.loads(response.text) _LOGGER.debug("Camera Privacy Mode Response: %s", response.text) if response_object['id'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) if response_object['privacy'] != str(privacy): raise AbodeException((ERROR.SET_PRIVACY_MODE)) _LOGGER.info("Set camera %s privacy mode to: %s", self.device_id, privacy) return True return False
def _area_settings(area, setting, value, validate_value): """Will validate area settings and values, returns data packet.""" if validate_value: # Exit delay has some specific limitations apparently if (setting == CONST.SETTING_EXIT_DELAY_AWAY and value not in CONST.VALID_SETTING_EXIT_AWAY): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.VALID_SETTING_EXIT_AWAY) elif value not in CONST.ALL_SETTING_ENTRY_EXIT_DELAY: raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.ALL_SETTING_ENTRY_EXIT_DELAY) return {'area': area, setting: value}
def _panel_settings(setting, value, validate_value): """Will validate panel settings and values, returns data packet.""" if validate_value: if (setting == CONST.SETTING_CAMERA_RESOLUTION and value not in CONST.SETTING_ALL_CAMERA_RES): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.SETTING_ALL_CAMERA_RES) elif (setting in [ CONST.SETTING_CAMERA_GRAYSCALE, CONST.SETTING_SILENCE_SOUNDS ] and value not in CONST.SETTING_DISABLE_ENABLE): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.SETTING_DISABLE_ENABLE) return {setting: value}
def enable(self, enable): """Enable or disable the automation.""" url = str.replace(CONST.AUTOMATION_ID_URL, '$AUTOMATIONID$', self.automation_id) self._automation['enabled'] = enable response = self._abode.send_request(method="patch", url=url, data={'enabled': enable}) response_object = json.loads(response.text) if isinstance(response_object, (tuple, list)): response_object = response_object[0] if (str(response_object['id']) != str(self._automation['id']) or str(response_object['enabled']) != str( self._automation['enabled'])): raise AbodeException((ERROR.INVALID_AUTOMATION_EDIT_RESPONSE)) self.update(response_object) _LOGGER.info("Set automation %s enable to: %s", self.name, self.is_enabled) _LOGGER.debug("Automation response: %s", response.text) return True
def send_request(self, method, url, headers=None, data=None, is_retry=False): """Send requests to Abode.""" if not self._token: self.login() if not headers: headers = {} headers['ABODE-API-KEY'] = self._token try: response = getattr(self._session, method)(url, headers=headers, data=data) if response and response.status_code < 400: return response except RequestException: _LOGGER.info("Abode connection reset...") if not is_retry: # Delete our current token and try again -- will force a login # attempt. self._token = None return self.send_request(method, url, headers, data, True) raise AbodeException((ERROR.REQUEST))
def remove_all_device_callbacks(self, devices): """Unregister all callbacks for a device.""" if not devices: return False if not isinstance(devices, (tuple, list)): devices = [devices] for device in devices: device_id = device if isinstance(device, AbodeDevice): device_id = device.device_id if not self._abode.get_device(device_id): raise AbodeException((ERROR.EVENT_DEVICE_INVALID)) if device_id not in self._device_callbacks: return False _LOGGER.debug( "Unsubscribing from all updates for device_id: %s", device_id) self._device_callbacks[device_id].clear() return True
def new_device(device_json, abode): """Create new device object for the given type.""" type_tag = device_json.get('type_tag') if not type_tag: raise AbodeException((ERROR.UNABLE_TO_MAP_DEVICE)) generic_type = CONST.get_generic_type(type_tag.lower()) device_json['generic_type'] = generic_type if generic_type == CONST.TYPE_CONNECTIVITY or \ generic_type == CONST.TYPE_MOISTURE or \ generic_type == CONST.TYPE_OPENING or \ generic_type == CONST.TYPE_VIBRATION: return AbodeBinarySensor(device_json, abode) elif generic_type == CONST.TYPE_CAMERA: return AbodeCamera(device_json, abode) elif generic_type == CONST.TYPE_COVER: return AbodeCover(device_json, abode) elif generic_type == CONST.TYPE_LIGHT: return AbodeLight(device_json, abode) elif generic_type == CONST.TYPE_LOCK: return AbodeLock(device_json, abode) elif generic_type == CONST.TYPE_SWITCH: return AbodeSwitch(device_json, abode) elif generic_type == CONST.TYPE_UNKNOWN_SENSOR: return _new_sensor(device_json, abode) return AbodeDevice(device_json, abode)
def add_device_callback(self, devices, callback): """Register a device callback.""" if not devices: return False if not isinstance(devices, (tuple, list)): devices = [devices] for device in devices: # Device may be a device_id device_id = device # If they gave us an actual device, get that devices ID if isinstance(device, AbodeDevice): device_id = device.device_id # Validate the device is valid if not self._abode.get_device(device_id): raise AbodeException((ERROR.EVENT_DEVICE_INVALID)) _LOGGER.debug("Subscribing to updated for device_id: %s", device_id) self._device_callbacks[device_id].append((callback)) return True
def _siren_settings(setting, value, validate_value): """Will validate siren settings and values, returns data packet.""" if validate_value: if value not in CONST.SETTING_DISABLE_ENABLE: raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.SETTING_DISABLE_ENABLE) return {'action': setting, 'option': value}
def _sound_settings(area, setting, value, validate_value): """Will validate sound settings and values, returns data packet.""" if validate_value: if (setting in CONST.VALID_SOUND_SETTINGS and value not in CONST.ALL_SETTING_SOUND): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.ALL_SETTING_SOUND) elif (setting == CONST.SETTING_ALARM_LENGTH and value not in CONST.ALL_SETTING_ALARM_LENGTH): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.ALL_SETTING_ALARM_LENGTH) elif (setting == CONST.SETTING_FINAL_BEEPS and value not in CONST.ALL_SETTING_FINAL_BEEPS): raise AbodeException(ERROR.INVALID_SETTING_VALUE, CONST.ALL_SETTING_FINAL_BEEPS) return {'area': area, setting: value}
def trigger(self, only_manual=True): """Trigger a quick-action automation.""" if not self.is_quick_action and only_manual: raise AbodeException((ERROR.TRIGGER_NON_QUICKACTION)) url = CONST.AUTOMATION_APPLY_URL url = url.replace('$AUTOMATIONID$', self.automation_id) self._abode.send_request(method="put", url=url, data=self._automation) return True
async def test_raise_config_entry_not_ready_when_offline(hass): """Config entry state is SETUP_RETRY when abode is offline.""" with patch( "homeassistant.components.abode.Abode", side_effect=AbodeException("any"), ): config_entry = await setup_platform(hass, ALARM_DOMAIN) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY assert hass.config_entries.flow.async_progress() == []
def update_image_location(self, timeline_json): """Update the image location.""" if not timeline_json: return False # If we get a list of objects back (likely) # then we just want the first one as it should be the "newest" if isinstance(timeline_json, (tuple, list)): timeline_json = timeline_json[0] # Verify that the event code is of the "CAPTURE IMAGE" event event_code = timeline_json.get('event_code') if event_code != TIMELINE.CAPTURE_IMAGE['event_code']: raise AbodeException((ERROR.CAM_TIMELINE_EVENT_INVALID)) # The timeline response has an entry for "file_path" that acts as the # location of the image within the Abode servers. file_path = timeline_json.get('file_path') if not file_path: raise AbodeException((ERROR.CAM_IMAGE_REFRESH_NO_FILE)) # Perform a "head" request for the image and look for a # 302 Found response url = CONST.BASE_URL + file_path response = self._abode.send_request("head", url) if response.status_code != 302: _LOGGER.warning("Unexected response code %s with body: %s", str(response.status_code), response.text) raise AbodeException((ERROR.CAM_IMAGE_UNEXPECTED_RESPONSE)) # The response should have a location header that is the actual # location of the image stored on AWS location = response.headers.get('location') if not location: raise AbodeException((ERROR.CAM_IMAGE_NO_LOCATION_HEADER)) self._image_url = location return True
def add_timeline_callback(self, timeline_events, callback): """Register a callback for a specific timeline event.""" if not timeline_events: return False if not isinstance(timeline_events, (tuple, list)): timeline_events = [timeline_events] for timeline_event in timeline_events: if not isinstance(timeline_event, dict): raise AbodeException((ERROR.EVENT_CODE_MISSING)) event_code = timeline_event.get('event_code') if not event_code: raise AbodeException((ERROR.EVENT_CODE_MISSING)) _LOGGER.debug("Subscribing to timeline event: %s", timeline_event) self._timeline_callbacks[event_code].append((callback)) return True
def refresh(self): """Refresh the automation.""" url = str.replace(CONST.AUTOMATION_ID_URL, '$AUTOMATIONID$', self.automation_id) response = self._abode.send_request(method="get", url=url) response_object = json.loads(response.text) if isinstance(response_object, (tuple, list)): response_object = response_object[0] if str(response_object['id']) != self.automation_id: raise AbodeException((ERROR.INVALID_AUTOMATION_REFRESH_RESPONSE)) self.update(response_object)
def add_event_callback(self, event_groups, callback): """Register callback for a group of timeline events.""" if not event_groups: return False if not isinstance(event_groups, (tuple, list)): event_groups = [event_groups] for event_group in event_groups: if event_group not in TIMELINE.ALL_EVENT_GROUPS: raise AbodeException(ERROR.EVENT_GROUP_INVALID, TIMELINE.ALL_EVENT_GROUPS) _LOGGER.debug("Subscribing to event group: %s", event_group) self._event_callbacks[event_group].append((callback)) return True
def image_to_file(self, path, get_image=True): """Write the image to a file.""" if not self.image_url or get_image: if not self.refresh_image(): return False response = requests.get(self.image_url, stream=True) if response.status_code != 200: _LOGGER.warning( "Unexpected response code %s when requesting image: %s", str(response.status_code), response.text) raise AbodeException((ERROR.CAM_IMAGE_REQUEST_INVALID)) with open(path, 'wb') as imgfile: copyfileobj(response.raw, imgfile) return True
def set_color(self, color): """Set device color.""" if self._json_state['control_url']: url = CONST.INTEGRATIONS_URL + self._device_uuid hue, saturation = color color_data = { 'action': 'setcolor', 'hue': int(hue), 'saturation': int(saturation) } response = self._abode.send_request("post", url, data=color_data) response_object = json.loads(response.text) _LOGGER.debug("Set Color Response: %s", response.text) if response_object['idForPanel'] != self.device_id: raise AbodeException((ERROR.SET_STATUS_DEV_ID)) # Abode will sometimes return hue value off by 1 (rounding error) hue_comparison = math.isclose(response_object["hue"], int(hue), abs_tol=1) if not hue_comparison or (response_object["saturation"] != int(saturation)): _LOGGER.warning( ("Set color mismatch for device %s. " "Request val: %s, Response val: %s "), self.device_id, (hue, saturation), (response_object['hue'], response_object['saturation'])) hue = response_object['hue'] saturation = response_object['saturation'] self.update({'statuses': {'hue': hue, 'saturation': saturation}}) _LOGGER.info("Set device %s color to: %s", self.device_id, (hue, saturation)) return True return False
def set_setting(self, setting, value, area='1', validate_value=True): """Set an abode system setting to a given value.""" setting = setting.lower() if setting not in CONST.ALL_SETTINGS: raise AbodeException(ERROR.INVALID_SETTING, CONST.ALL_SETTINGS) if setting in CONST.PANEL_SETTINGS: url = CONST.SETTINGS_URL data = self._panel_settings(setting, value, validate_value) elif setting in CONST.AREA_SETTINGS: url = CONST.AREAS_URL data = self._area_settings(area, setting, value, validate_value) elif setting in CONST.SOUND_SETTINGS: url = CONST.SOUNDS_URL data = self._sound_settings(area, setting, value, validate_value) elif setting in CONST.SIREN_SETTINGS: url = CONST.SIREN_URL data = self._siren_settings(setting, value, validate_value) return self.send_request(method="put", url=url, data=data)
def set_active(self, active): """Activate and deactivate an automation.""" url = CONST.AUTOMATION_EDIT_URL url = url.replace('$AUTOMATIONID$', self.automation_id) self._automation['is_active'] = str(int(active)) response = self._abode.send_request(method="put", url=url, data=self._automation) response_object = json.loads(response.text) if isinstance(response_object, (tuple, list)): response_object = response_object[0] if (str(response_object['id']) != str(self._automation['id']) or response_object['is_active'] != self._automation['is_active']): raise AbodeException((ERROR.INVALID_AUTOMATION_EDIT_RESPONSE)) self.update(response_object) return True
def capture(self): """Request a new camera image.""" # Abode IP cameras use a different URL for image captures. if 'control_url_snapshot' in self._json_state: url = CONST.BASE_URL + self._json_state['control_url_snapshot'] elif 'control_url' in self._json_state: url = CONST.BASE_URL + self._json_state['control_url'] else: raise AbodeException((ERROR.MISSING_CONTROL_URL)) try: response = self._abode.send_request("put", url) _LOGGER.debug("Capture image response: %s", response.text) return True except AbodeException as exc: _LOGGER.warning("Failed to capture image: %s", exc) return False
def set_default_mode(self, default_mode): """Set the default mode when alarms are turned 'on'.""" if default_mode.lower() not in (CONST.MODE_AWAY, CONST.MODE_HOME): raise AbodeException(ERROR.INVALID_DEFAULT_ALARM_MODE) self._default_alarm_mode = default_mode.lower()