Initial commit.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""Camera that loads a picture from a local file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST
|
||||
from homeassistant.core import ServiceCall
|
||||
|
||||
from .const import (
|
||||
ATTR_AMAZON_IMAGE,
|
||||
ATTR_IMAGE_NAME,
|
||||
ATTR_IMAGE_PATH,
|
||||
CAMERA,
|
||||
CAMERA_DATA,
|
||||
CONF_CUSTOM_IMG,
|
||||
CONF_CUSTOM_IMG_FILE,
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
SENSOR_NAME,
|
||||
VERSION,
|
||||
)
|
||||
|
||||
SERVICE_UPDATE_IMAGE = "update_image"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config, async_add_entities):
|
||||
"""Set up the Camera that works with local files."""
|
||||
if CAMERA not in hass.data[DOMAIN][config.entry_id]:
|
||||
hass.data[DOMAIN][config.entry_id][CAMERA] = []
|
||||
|
||||
coordinator = hass.data[DOMAIN][config.entry_id][COORDINATOR]
|
||||
camera = []
|
||||
if not config.data.get(CONF_CUSTOM_IMG):
|
||||
file_path = f"{os.path.dirname(__file__)}/mail_none.gif"
|
||||
else:
|
||||
file_path = config.data.get(CONF_CUSTOM_IMG_FILE)
|
||||
|
||||
for variable in CAMERA_DATA:
|
||||
temp_cam = MailCam(hass, variable, config, coordinator, file_path)
|
||||
camera.append(temp_cam)
|
||||
hass.data[DOMAIN][config.entry_id][CAMERA].append(temp_cam)
|
||||
|
||||
async def _update_image(service: ServiceCall) -> None:
|
||||
"""Refresh camera image."""
|
||||
_LOGGER.debug("Updating image: %s", service)
|
||||
cameras = hass.data[DOMAIN][config.entry_id][CAMERA]
|
||||
entity_id = None
|
||||
|
||||
if ATTR_ENTITY_ID in service.data.keys():
|
||||
entity_id = service.data[ATTR_ENTITY_ID]
|
||||
|
||||
# Update all cameras if no entity_id
|
||||
if entity_id is None:
|
||||
for cam in cameras:
|
||||
cam.update_file_path()
|
||||
|
||||
else:
|
||||
for cam in cameras:
|
||||
if cam.entity_id in entity_id:
|
||||
cam.update_file_path()
|
||||
return True
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_UPDATE_IMAGE,
|
||||
_update_image,
|
||||
schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ENTITY_ID): vol.Coerce(str),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async_add_entities(camera)
|
||||
|
||||
|
||||
class MailCam(Camera):
|
||||
"""Representation of a local file camera."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
name: str,
|
||||
config: ConfigEntry,
|
||||
coordinator,
|
||||
file_path: str,
|
||||
) -> None:
|
||||
"""Initialize Local File Camera component."""
|
||||
super().__init__()
|
||||
|
||||
self.hass = hass
|
||||
self._name = CAMERA_DATA[name][SENSOR_NAME]
|
||||
self._type = name
|
||||
self.check_file_path_access(file_path)
|
||||
self._file_path = file_path
|
||||
self._coordinator = coordinator
|
||||
self._host = config.data.get(CONF_HOST)
|
||||
self._unique_id = config.entry_id
|
||||
self._no_mail = (
|
||||
None
|
||||
if not config.data.get(CONF_CUSTOM_IMG)
|
||||
else config.data.get(CONF_CUSTOM_IMG_FILE)
|
||||
)
|
||||
|
||||
async def async_camera_image(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
"""Return image response."""
|
||||
try:
|
||||
file = await self.hass.async_add_executor_job(open, self._file_path, "rb")
|
||||
return file.read()
|
||||
except FileNotFoundError:
|
||||
_LOGGER.info(
|
||||
"Could not read camera %s image from file: %s",
|
||||
self._name,
|
||||
self._file_path,
|
||||
)
|
||||
|
||||
def check_file_path_access(self, file_path: str) -> None:
|
||||
"""Check that filepath given is readable."""
|
||||
if not os.access(file_path, os.R_OK):
|
||||
_LOGGER.info(
|
||||
"Could not read camera %s image from file: %s", self._name, file_path
|
||||
)
|
||||
|
||||
def update_file_path(self) -> None:
|
||||
"""Update the file_path."""
|
||||
_LOGGER.debug("Camera Update: %s", self._type)
|
||||
_LOGGER.debug("Custom No Mail: %s", self._no_mail)
|
||||
file_path = None
|
||||
|
||||
if not self._coordinator.last_update_success:
|
||||
_LOGGER.debug("Update to update camera image. Unavailable.")
|
||||
return
|
||||
|
||||
if self._coordinator.data is None:
|
||||
_LOGGER.debug("Unable to update camera image, no data.")
|
||||
return
|
||||
|
||||
if self._type == "usps_camera":
|
||||
# Update camera image for USPS informed delivery images
|
||||
file_path = f"{os.path.dirname(__file__)}/mail_none.gif"
|
||||
s1 = set([ATTR_IMAGE_NAME, ATTR_IMAGE_PATH])
|
||||
if s1.issubset(self._coordinator.data.keys()):
|
||||
image = self._coordinator.data[ATTR_IMAGE_NAME]
|
||||
path = self._coordinator.data[ATTR_IMAGE_PATH]
|
||||
file_path = f"{self.hass.config.path()}/{path}{image}"
|
||||
else:
|
||||
if self._no_mail:
|
||||
file_path = self._no_mail
|
||||
|
||||
elif self._type == "amazon_camera":
|
||||
# Update camera image for Amazon deliveries
|
||||
file_path = f"{os.path.dirname(__file__)}/no_deliveries.jpg"
|
||||
s1 = set([ATTR_AMAZON_IMAGE, ATTR_IMAGE_PATH])
|
||||
if s1.issubset(self._coordinator.data.keys()):
|
||||
image = self._coordinator.data[ATTR_AMAZON_IMAGE]
|
||||
path = f"{self._coordinator.data[ATTR_IMAGE_PATH]}amazon/"
|
||||
file_path = f"{self.hass.config.path()}/{path}{image}"
|
||||
|
||||
self.check_file_path_access(file_path)
|
||||
self._file_path = file_path
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
async def async_on_demand_update(self):
|
||||
"""Update state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
"""Return device information about the mailbox."""
|
||||
return {
|
||||
"connections": {(DOMAIN, self._unique_id)},
|
||||
"name": self._host,
|
||||
"manufacturer": "IMAP E-Mail",
|
||||
"sw_version": VERSION,
|
||||
}
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||
return f"{self._host}_{self._name}_{self._unique_id}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the camera state attributes."""
|
||||
return {"file_path": self._file_path}
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return True if entity has to be polled for state.
|
||||
|
||||
False if entity pushes its state to HA.
|
||||
"""
|
||||
return True
|
||||
|
||||
async def async_update(self):
|
||||
"""Update camera entity and refresh attributes."""
|
||||
self.update_file_path()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return self._coordinator.last_update_success
|
||||
Reference in New Issue
Block a user