1 """Component providing support for Xiaomi Cameras."""
3 from __future__
import annotations
5 from ftplib
import FTP, error_perm
8 from haffmpeg.camera
import CameraMjpeg
9 import voluptuous
as vol
13 PLATFORM_SCHEMA
as CAMERA_PLATFORM_SCHEMA,
33 _LOGGER = logging.getLogger(__name__)
35 DEFAULT_BRAND =
"Xiaomi Home Camera"
36 DEFAULT_PATH =
"/media/mmcblk0p1/record"
38 DEFAULT_USERNAME =
"root"
39 DEFAULT_ARGUMENTS =
"-pred 1"
41 CONF_FFMPEG_ARGUMENTS =
"ffmpeg_arguments"
44 MODEL_XIAOFANG =
"xiaofang"
46 PLATFORM_SCHEMA = CAMERA_PLATFORM_SCHEMA.extend(
48 vol.Required(CONF_NAME): cv.string,
49 vol.Required(CONF_HOST): cv.template,
50 vol.Required(CONF_MODEL): vol.Any(MODEL_YI, MODEL_XIAOFANG),
51 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
52 vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
53 vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
54 vol.Required(CONF_PASSWORD): cv.string,
55 vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
63 async_add_entities: AddEntitiesCallback,
64 discovery_info: DiscoveryInfoType |
None =
None,
66 """Set up a Xiaomi Camera."""
67 _LOGGER.debug(
"Received configuration for model %s", config[CONF_MODEL])
72 """Define an implementation of a Xiaomi Camera."""
81 self.
_name_name = config[CONF_NAME]
82 self.
hosthost = config[CONF_HOST]
83 self.
_model_model = config[CONF_MODEL]
84 self.
portport = config[CONF_PORT]
85 self.
pathpath = config[CONF_PATH]
86 self.
useruser = config[CONF_USERNAME]
87 self.
passwdpasswd = config[CONF_PASSWORD]
91 """Return the name of this camera."""
92 return self.
_name_name
96 """Return the camera brand."""
101 """Return the camera model."""
105 """Retrieve the latest video file from the Xiaomi Camera FTP server."""
110 except error_perm
as exc:
111 _LOGGER.error(
"Camera login failed: %s", exc)
115 ftp.cwd(self.
pathpath)
116 except error_perm
as exc:
117 _LOGGER.error(
"Unable to find path: %s - %s", self.
pathpath, exc)
120 dirs = [d
for d
in ftp.nlst()
if "." not in d]
122 _LOGGER.warning(
"There don't appear to be any folders")
125 first_dir = latest_dir = dirs[-1]
128 except error_perm
as exc:
129 _LOGGER.error(
"Unable to find path: %s - %s", first_dir, exc)
132 if self.
_model_model == MODEL_XIAOFANG:
133 dirs = [d
for d
in ftp.nlst()
if "." not in d]
135 _LOGGER.warning(
"There don't appear to be any uploaded videos")
138 latest_dir = dirs[-1]
141 videos = [v
for v
in ftp.nlst()
if ".tmp" not in v]
143 _LOGGER.debug(
'Video folder "%s" is empty; delaying', latest_dir)
146 if self.
_model_model == MODEL_XIAOFANG:
151 return f
"ftp://{self.user}:{self.passwd}@{host}:{self.port}{ftp.pwd()}/{video}"
154 self, width: int |
None =
None, height: int |
None =
None
156 """Return a still image response from the camera."""
159 host = self.
hosthost.async_render(parse_result=
False)
160 except TemplateError
as exc:
161 _LOGGER.error(
"Error parsing template %s: %s", self.
hosthost, exc)
166 self.
_last_image_last_image = await ffmpeg.async_get_image(
178 """Generate an HTTP MJPEG stream from the camera."""
180 stream = CameraMjpeg(self.
_manager_manager.binary)
184 stream_reader = await stream.get_reader()
189 self.
_manager_manager.ffmpeg_stream_content_type,
def handle_async_mjpeg_stream(self, request)
def __init__(self, hass, config)
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
def get_latest_video_url(self, host)
FFmpegManager get_ffmpeg_manager(HomeAssistant hass)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
web.StreamResponse async_aiohttp_proxy_stream(HomeAssistant hass, web.BaseRequest request, aiohttp.StreamReader stream, str|None content_type, int buffer_size=102400, int timeout=10)