-
-
Notifications
You must be signed in to change notification settings - Fork 38k
Added Xeoma camera platform #11619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Xeoma camera platform #11619
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| """ | ||
| Support for Xeoma Cameras. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/camera.xeoma/ | ||
| """ | ||
| import asyncio | ||
| import logging | ||
|
|
||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.components.camera import ( | ||
| PLATFORM_SCHEMA, Camera) | ||
| from homeassistant.const import ( | ||
| CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_HOST) | ||
| from homeassistant.helpers import config_validation as cv | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| REQUIREMENTS = ['pyxeoma==1.2'] | ||
|
|
||
| CONF_CAMERAS = 'cameras' | ||
| CONF_HIDE = 'hide' | ||
| CONF_IMAGE_NAME = 'image_name' | ||
| CONF_NEW_VERSION = 'new_version' | ||
|
|
||
| CAMERAS_SCHEMA = vol.Schema({ | ||
| vol.Required(CONF_IMAGE_NAME): cv.string, | ||
| vol.Optional(CONF_HIDE, default=False): cv.boolean, | ||
| vol.Optional(CONF_NAME): cv.string | ||
| }, required=False) | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_HOST): cv.string, | ||
| vol.Optional(CONF_CAMERAS, default={}): | ||
| vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])), | ||
| vol.Optional(CONF_PASSWORD): cv.string, | ||
| vol.Optional(CONF_USERNAME): cv.string, | ||
| vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean | ||
| }) | ||
|
|
||
|
|
||
| @asyncio.coroutine | ||
| # pylint: disable=unused-argument | ||
| def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | ||
| """Discover and setup Xeoma Cameras.""" | ||
| host = config[CONF_HOST] | ||
| login = config[CONF_USERNAME] if CONF_USERNAME in config else None | ||
| password = config[CONF_PASSWORD] if CONF_PASSWORD in config else None | ||
| new_version = config[CONF_NEW_VERSION] | ||
|
|
||
| from pyxeoma.xeoma import Xeoma, XeomaError | ||
| xeoma = Xeoma(host, new_version, login, password) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that there will be a trackback if the credentials are wrong or the camera is not reachable. Please catch that, log an error that the users knows what's going on and return. Otherwise the users end up with a non-functional platform.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Xeoma is an NVR similar to BlueIris or Zoneminder so the connection being made is not to individual cameras, but to a web interface that can be enabled within Xeoma which provides direct access to jpgs from all connected cameras. The constructor for the Xeoma object doesn't make any attempt to connect to the Xeoma web server. All of the calls to the library that can fail are inside the following As far as authentication, there are 2 possible failure scenarios that I can see:
In both cases the platform will catch and log the error and since it will only add entities to home assistant for cameras it is able to discover, no non-working camera entities will be added. The main issue is that the web server that Xeoma provides doesn't handle HTTP status codes correctly. In scenario 2 above, you still get a 200 response which makes it difficult to determine exactly why camera discovery failed since incorrect configuration of the web server module within Xeoma can also create a situation in which no cameras will be discovered (though this should be obvious since the user won't be able to access their cameras in the Xeoma web ui either). The error message provided in this scenario simply informs the user that no cameras were discovered.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| try: | ||
| yield from xeoma.async_test_connection() | ||
| discovered_image_names = yield from xeoma.async_get_image_names() | ||
| discovered_cameras = [ | ||
| { | ||
| CONF_IMAGE_NAME: image_name, | ||
| CONF_HIDE: False, | ||
| CONF_NAME: image_name | ||
| } | ||
| for image_name in discovered_image_names | ||
| ] | ||
|
|
||
| for cam in config[CONF_CAMERAS]: | ||
| camera = next( | ||
| (dc for dc in discovered_cameras | ||
| if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None) | ||
|
|
||
| if camera is not None: | ||
| if CONF_NAME in cam: | ||
| camera[CONF_NAME] = cam[CONF_NAME] | ||
| if CONF_HIDE in cam: | ||
| camera[CONF_HIDE] = cam[CONF_HIDE] | ||
|
|
||
| cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras)) | ||
| async_add_devices( | ||
| [XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME]) | ||
| for camera in cameras]) | ||
| except XeomaError as err: | ||
| _LOGGER.error('XeomaError: %s', err.message) | ||
|
|
||
|
|
||
| class XeomaCamera(Camera): | ||
| """Implementation of a Xeoma camera.""" | ||
|
|
||
| def __init__(self, xeoma, image, name): | ||
| """Initialize a Xeoma camera.""" | ||
| super().__init__() | ||
| self._xeoma = xeoma | ||
| self._name = name | ||
| self._image = image | ||
| self._last_image = None | ||
|
|
||
| @asyncio.coroutine | ||
| def async_camera_image(self): | ||
| """Return a still image response from the camera.""" | ||
| from pyxeoma.xeoma import XeomaError | ||
| try: | ||
| image = yield from self._xeoma.async_get_camera_image(self._image) | ||
| self._last_image = image | ||
| except XeomaError as err: | ||
| _LOGGER.error("Error fetching image from Xeoma: %s", err.message) | ||
|
|
||
| return self._last_image | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of this device.""" | ||
| return self._name | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
login = config.get(CONF_USERNAME). This way it will beNoneif no username was set in the configuration.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done