diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 92e8f3d2b4f..c6858d29e71 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -25,7 +25,6 @@ Upcoming features -Expanded documentation """ import requests -from requests.auth import HTTPBasicAuth import logging import time import re @@ -34,6 +33,8 @@ from homeassistant.const import ( ATTR_ENTITY_PICTURE, HTTP_NOT_FOUND, ATTR_ENTITY_ID, + HTTP_HEADER_CONTENT_TYPE, + CONTENT_TYPE_MULTIPART ) from homeassistant.helpers.entity_component import EntityComponent @@ -66,6 +67,13 @@ REC_IMG_PREFIX = 'recording_image-' STATE_STREAMING = 'streaming' STATE_IDLE = 'idle' +CAMERA_PROXY_URL = '/api/camera_proxy_stream/{0}' +CAMERA_STILL_URL = '/api/camera_proxy/{0}' +ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?time={1}' + +MULTIPART_BOUNDARY = '--jpegboundary' +MJPEG_START_HEADER = 'Content-type: {0}\r\n\r\n' + # pylint: disable=too-many-branches def setup(hass, config): @@ -121,49 +129,43 @@ def setup(hass, config): if entity_id in component.entities.keys(): camera = component.entities[entity_id] - if camera: + if not camera: + self.handler.send_response(HTTP_NOT_FOUND) + self.handler.end_headers() + return - try: - camera.is_streaming = True - camera.update_ha_state() + try: + camera.is_streaming = True + camera.update_ha_state() - handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8')) - handler.request.sendall(bytes( - 'Content-type: multipart/x-mixed-replace; \ - boundary=--jpgboundary\r\n\r\n', 'utf-8')) + handler.request.sendall(bytes('HTTP/1.1 200 OK\r\n', 'utf-8')) + handler.request.sendall(bytes( + 'Content-type: multipart/x-mixed-replace; \ + boundary=--jpgboundary\r\n\r\n', 'utf-8')) + handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8')) - handler.request.sendall(bytes('--jpgboundary\r\n', 'utf-8')) + # MJPEG_START_HEADER.format() - while True: + while True: - if camera.username and camera.password: - response = requests.get( - camera.still_image_url, - auth=HTTPBasicAuth( - camera.username, - camera.password)) - else: - response = requests.get(camera.still_image_url) + img_bytes = camera.get_camera_image() - headers_str = '\r\n'.join(( - 'Content-length: {}'.format(len(response.content)), - 'Content-type: image/jpeg', - )) + '\r\n\r\n' + headers_str = '\r\n'.join(( + 'Content-length: {}'.format(len(img_bytes)), + 'Content-type: image/jpeg', + )) + '\r\n\r\n' - handler.request.sendall( - bytes(headers_str, 'utf-8') + - response.content + - bytes('\r\n', 'utf-8')) + handler.request.sendall( + bytes(headers_str, 'utf-8') + + img_bytes + + bytes('\r\n', 'utf-8')) - handler.request.sendall( - bytes('--jpgboundary\r\n', 'utf-8')) + handler.request.sendall( + bytes('--jpgboundary\r\n', 'utf-8')) - except (requests.RequestException, IOError): - camera.is_streaming = False - camera.update_ha_state() - - else: - handler.send_response(HTTP_NOT_FOUND) + except (requests.RequestException, IOError): + camera.is_streaming = False + camera.update_ha_state() camera.is_streaming = False @@ -207,36 +209,12 @@ class Camera(Entity): """ Returns string of camera model """ return None - @property - # pylint: disable=no-self-use - def base_url(self): - """ Return the configured base URL for the camera """ - return None - @property # pylint: disable=no-self-use def image_url(self): """ Return the still image segment of the URL """ return None - @property - # pylint: disable=no-self-use - def device_info(self): - """ Get the configuration object """ - return None - - @property - # pylint: disable=no-self-use - def username(self): - """ Get the configured username """ - return None - - @property - # pylint: disable=no-self-use - def password(self): - """ Get the configured password """ - return None - @property # pylint: disable=no-self-use def still_image_url(self): @@ -261,13 +239,13 @@ class Camera(Entity): def state_attributes(self): """ Returns optional state attributes. """ attr = super().state_attributes - attr['model_name'] = self.device_info.get('model', 'generic') - attr['brand'] = self.device_info.get('brand', 'generic') - attr['still_image_url'] = '/api/camera_proxy/' + self.entity_id - attr[ATTR_ENTITY_PICTURE] = ( - '/api/camera_proxy/' + - self.entity_id + '?time=' + + attr['model_name'] = self.model + attr['brand'] = self.brand + CAMERA_PROXY_URL.format(self.entity_id) + attr['still_image_url'] = CAMERA_STILL_URL.format(self.entity_id) + attr[ATTR_ENTITY_PICTURE] = ENTITY_IMAGE_URL.format( + self.entity_id, str(time.time())) - attr['stream_url'] = '/api/camera_proxy_stream/' + self.entity_id + attr['stream_url'] = CAMERA_PROXY_URL.format(self.entity_id) return attr diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index ab09126568d..d7e5bf8a2f6 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -76,6 +76,7 @@ the password for accessing your camera """ import logging +from requests.auth import HTTPBasicAuth from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.helpers import validate_config from homeassistant.components.camera import DOMAIN @@ -125,9 +126,14 @@ class GenericCamera(Camera): def get_camera_image(self): """ Return a still image reponse from the camera """ - response = requests.get( - self.still_image_url, - auth=(self._username, self._password)) + if self.username and self.password: + response = requests.get( + self.still_image_url, + auth=HTTPBasicAuth( + self.username, + self.password)) + else: + response = requests.get(self.still_image_url) return response.content diff --git a/homeassistant/components/frontend/www_static/polymer/dialogs/more-info-dialog.html b/homeassistant/components/frontend/www_static/polymer/dialogs/more-info-dialog.html index 7fb4577f926..a2484abed8d 100644 --- a/homeassistant/components/frontend/www_static/polymer/dialogs/more-info-dialog.html +++ b/homeassistant/components/frontend/www_static/polymer/dialogs/more-info-dialog.html @@ -108,7 +108,7 @@ var newState = this.entityId ? stateStore.get(this.entityId) : null; if (newState !== this.stateObj) { - this.stateObj = newState; + this.stateObj = newState; } if(this.stateObj) { if(DOMAINS_WITH_NO_HISTORY.indexOf(this.stateObj.domain) !== -1) { @@ -136,11 +136,11 @@ } }, - onIronOverlayOpened: function() { + onIronOverlayOpened: function() { this.dialogOpen = true; }, - onIronOverlayClosed: function() { + onIronOverlayClosed: function() { this.dialogOpen = false; }, @@ -160,7 +160,7 @@ this.changeEntityId(entityId); this.debounce('showDialogAfterRender', function() { - this.$.dialog.toggle(); + this.$.dialog.toggle(); }.bind(this)); }, }); diff --git a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-camera.html b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-camera.html index 219cb280d52..0b16860f011 100644 --- a/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-camera.html +++ b/homeassistant/components/frontend/www_static/polymer/more-infos/more-info-camera.html @@ -13,19 +13,19 @@ } @media (max-width: 720px) { :host .camera-image { - max-width: calc(100%); + max-width: 100%; height: initial } :host .camera-page { - max-width: calc(100%); - max-height: calc(100%); + max-width: 100%; + max-height: 100%; } } @media (max-width: 620px) { :host .camera-image { - max-width: calc(100%); + max-width: 100%; height: initial } } @@ -38,9 +38,9 @@ @@ -58,46 +58,20 @@ properties: { stateObj: { - type: Object, - observer: 'stateObjChanged', + type: Object }, dialogOpen: { - type: Object, - observer: 'dialogOpenChanged', + type: Boolean, }, camera_image_url: { type: String, } }, - stateObjChanged: function(newVal, oldVal) { - if (newVal) { - - } - - }, - - computeClassNames: function(stateObj) { - return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES); - }, - - dialogOpenChanged: function(newVal, oldVal) { - if (newVal) { - this.startImageStream(); - } - else { - this.stopImageStream(); - } - }, - - startImageStream: function() { - this.camera_image_url = this.stateObj.attributes['stream_url']; - this.isStreaming = true; - }, - - stopImageStream: function() { - this.camera_image_url = this.stateObj.attributes['still_image_url'] + '?t=' + Date.now(); - this.isStreaming = false; + computeCameraImageUrl: function(dialogOpen) { + return dialogOpen ? + this.stateObj.attributes['stream_url'] : + this.stateObj.attributes['still_image_url']; }, });