homesec 1.1.2__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
homesec/models/source.py CHANGED
@@ -30,6 +30,7 @@ class RTSPSourceConfig(BaseModel):
30
30
  heartbeat_s: float = 30.0
31
31
  rtsp_connect_timeout_s: float = 2.0
32
32
  rtsp_io_timeout_s: float = 2.0
33
+ ffmpeg_flags: list[str] = Field(default_factory=list)
33
34
 
34
35
 
35
36
  class LocalFolderSourceConfig(BaseModel):
@@ -1,4 +1,4 @@
1
- """YOLOv8 object detection filter plugin."""
1
+ """YOLO object detection filter plugin."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -151,7 +151,7 @@ def _get_model(model_path: str, device: str) -> Any:
151
151
 
152
152
 
153
153
  @plugin(plugin_type=PluginType.FILTER, name="yolo")
154
- class YOLOv8Filter(ObjectFilter):
154
+ class YOLOFilter(ObjectFilter):
155
155
  """YOLO-based object detection filter.
156
156
 
157
157
  Uses ProcessPoolExecutor internally for CPU/GPU-bound inference.
homesec/sources/rtsp.py CHANGED
@@ -219,6 +219,7 @@ class RTSPSource(ThreadedClipSource):
219
219
  self.reconnect_backoff_s = float(config.reconnect_backoff_s)
220
220
  self.rtsp_connect_timeout_s = float(config.rtsp_connect_timeout_s)
221
221
  self.rtsp_io_timeout_s = float(config.rtsp_io_timeout_s)
222
+ self.ffmpeg_flags = list(config.ffmpeg_flags)
222
223
 
223
224
  if config.disable_hwaccel:
224
225
  logger.info("Hardware acceleration manually disabled")
@@ -551,9 +552,31 @@ class RTSPSource(ThreadedClipSource):
551
552
  "-f",
552
553
  "mp4",
553
554
  "-y",
554
- str(output_file),
555
555
  ]
556
556
 
557
+ user_flags = self.ffmpeg_flags
558
+
559
+ # Naive check to see if user overrode defaults
560
+ # If user supplies ANY -loglevel, we don't add ours.
561
+ # If user supplies ANY -fflags, we don't add ours (to avoid concatenation complexity).
562
+ # This allows full user control.
563
+ has_loglevel = any(x == "-loglevel" for x in user_flags)
564
+ if not has_loglevel:
565
+ cmd.extend(["-loglevel", "warning"])
566
+
567
+ has_fflags = any(x == "-fflags" for x in user_flags)
568
+ if not has_fflags:
569
+ cmd.extend(["-fflags", "+genpts+igndts"])
570
+
571
+ has_fps_mode = any(x == "-fps_mode" or x == "-vsync" for x in user_flags)
572
+ if not has_fps_mode:
573
+ cmd.extend(["-vsync", "0"])
574
+
575
+ # Add user flags last so they can potentially override or add to the above
576
+ cmd.extend(user_flags)
577
+
578
+ cmd.extend([str(output_file)])
579
+
557
580
  safe_cmd = list(cmd)
558
581
  try:
559
582
  idx = safe_cmd.index("-i")
@@ -753,6 +776,7 @@ class RTSPSource(ThreadedClipSource):
753
776
 
754
777
  cmd = ["ffmpeg"]
755
778
 
779
+ # 1. Global Flags (Hardware Acceleration)
756
780
  if self.hwaccel_config.is_available and not self._hwaccel_failed:
757
781
  hwaccel = self.hwaccel_config.hwaccel
758
782
  if hwaccel is not None:
@@ -762,6 +786,23 @@ class RTSPSource(ThreadedClipSource):
762
786
  elif self._hwaccel_failed:
763
787
  logger.info("Hardware acceleration disabled due to previous failures")
764
788
 
789
+ # 2. Global Flags (Robustness & Logging)
790
+ user_flags = self.ffmpeg_flags
791
+
792
+ has_loglevel = any(x == "-loglevel" for x in user_flags)
793
+ if not has_loglevel:
794
+ cmd.extend(["-loglevel", "warning"])
795
+
796
+ has_fflags = any(x == "-fflags" for x in user_flags)
797
+ if not has_fflags:
798
+ cmd.extend(["-fflags", "+genpts+igndts"])
799
+
800
+ # Add all user flags to global scope.
801
+ # Users who want input-specific flags (before -i) must rely on ffmpeg parsing them correctly
802
+ # or we would need a more complex config structure.
803
+ # For now, most robustness flags (-re, -rtsp_transport, etc) work as global or are handled below.
804
+ cmd.extend(user_flags)
805
+
765
806
  base_input_args = [
766
807
  "-rtsp_transport",
767
808
  "tcp",
@@ -778,11 +819,10 @@ class RTSPSource(ThreadedClipSource):
778
819
  ]
779
820
 
780
821
  timeout_us_connect = str(int(max(0.1, self.rtsp_connect_timeout_s) * 1_000_000))
781
- timeout_us_io = str(int(max(0.1, self.rtsp_io_timeout_s) * 1_000_000))
782
822
  attempts: list[tuple[str, list[str]]] = [
783
823
  (
784
- "stimeout+rw_timeout",
785
- ["-stimeout", timeout_us_connect, "-rw_timeout", timeout_us_io] + base_input_args,
824
+ "stimeout",
825
+ ["-stimeout", timeout_us_connect] + base_input_args,
786
826
  ),
787
827
  ("stimeout", ["-stimeout", timeout_us_connect] + base_input_args),
788
828
  ("no_timeouts", base_input_args),
@@ -790,6 +830,7 @@ class RTSPSource(ThreadedClipSource):
790
830
 
791
831
  process: subprocess.Popen[bytes] | None = None
792
832
  stderr_file: Any | None = None
833
+
793
834
  for label, extra_args in attempts:
794
835
  cmd_attempt = list(cmd) + extra_args
795
836
  logger.debug("Starting frame pipeline (%s), logging to: %s", label, stderr_log)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: homesec
3
- Version: 1.1.2
3
+ Version: 1.2.1
4
4
  Summary: Pluggable async home security camera pipeline with detection, VLM analysis, and alerts.
5
5
  Project-URL: Homepage, https://github.com/lan17/homesec
6
6
  Project-URL: Source, https://github.com/lan17/homesec
@@ -243,25 +243,70 @@ Description-Content-Type: text/markdown
243
243
 
244
244
  # HomeSec
245
245
 
246
+ [![PyPI](https://img.shields.io/pypi/v/homesec)](https://pypi.org/project/homesec/)
246
247
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
247
248
  [![Python: 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
248
249
  [![Typing: Typed](https://img.shields.io/badge/typing-typed-2b825b)](https://peps.python.org/pep-0561/)
249
250
  [![codecov](https://codecov.io/gh/lan17/HomeSec/branch/main/graph/badge.svg)](https://codecov.io/gh/lan17/HomeSec)
250
251
 
251
- HomeSec is a self-hosted, extensible network video recorder that puts you in control. Store clips wherever you want, analyze them with AI, and get smart notifications—all while keeping your footage private and off third-party clouds.
252
+ HomeSec is a self-hosted, extensible video pipeline for home security cameras. You can connect cameras directly via RTSP, receive clips over FTP, or implement your own ClipSource. From there, the pipeline filters events with AI and sends smart notifications. Your footage stays private and off third-party clouds.
253
+
254
+ ## Design Principles
255
+
256
+ - **Local-Only Data Processing**: Video footage remains on the local network by default. Cloud usage (Storage, VLM/OpenAI) is strictly opt-in.
257
+ - **Modular Architecture**: All major components (sources, filters, analyzers, notifiers) are decoupled plugins defined by strict interfaces. If you want to use a different AI model or storage backend, you can swap it out with a few lines of Python.
258
+ - **Resilience**: The primary resilience feature is backing up clips to storage. The pipeline handles intermittent stream failures and network instability without crashing or stalling.
259
+
260
+ ## Pipeline at a glance
261
+
262
+
263
+
264
+ ```mermaid
265
+ graph TD
266
+ %% Layout Wrapper for horizontal alignment
267
+ subgraph Wrapper [" "]
268
+ direction LR
269
+ style Wrapper fill:none,stroke:none
270
+
271
+ S[Clip Source]
272
+
273
+ subgraph Pipeline [Media Processing Pipeline]
274
+ direction TB
275
+ C(Clip File) --> U([Upload to Storage])
276
+ C --> F([Detect objects: YOLO])
277
+ F -->|Detected objects| AI{Trigger classes filter}
278
+ AI -->|Yes| V([VLM Analysis])
279
+ AI -->|No| D([Discard])
280
+ V -->|Risk level, detected objects| P{Alert Policy filter}
281
+ P -->|No| D
282
+ P -->|YES| N[Notifiers]
283
+ end
284
+
285
+ S -->|New Clip File| Pipeline
286
+
287
+ PG[(Postgres)]
288
+ Pipeline -.->|State & Events| PG
289
+ end
290
+ ```
291
+
292
+ - **Parallel Processing**: Upload and filter run in parallel.
293
+ - **Resilience**: Upload failures do not block alerts; filter failures stop expensive VLM calls.
294
+ - **State**: Metadata is stored in Postgres (`clip_states` + `clip_events`) for full observability.
252
295
 
253
- Under the hood, it's a pluggable async pipeline for home security cameras. It records short clips, runs object detection, optionally calls a vision-language model ([VLM](https://en.wikipedia.org/wiki/Vision%E2%80%93language_model)) for a structured summary, and sends alerts via [MQTT](https://en.wikipedia.org/wiki/MQTT) or email. The design prioritizes reliability and extensibility.
254
296
 
255
297
  ## Table of Contents
256
298
 
257
299
  - [Highlights](#highlights)
258
300
  - [Pipeline at a glance](#pipeline-at-a-glance)
259
301
  - [Quickstart](#quickstart)
302
+ - [30-Second Start (Docker)](#30-second-start-docker)
303
+ - [Manual Setup](#manual-setup)
260
304
  - [Configuration](#configuration)
261
- - [Extensible by design](#extensible-by-design)
262
- - [CLI](#cli)
263
- - [Built-in plugins](#built-in-plugins)
264
- - [Writing a plugin](#writing-a-plugin)
305
+ - [Commands](#commands)
306
+ - [Plugins](#plugins)
307
+ - [Built-in plugins](#built-in-plugins)
308
+ - [Plugin interfaces](#plugin-interfaces)
309
+ - [Writing a custom plugin](#writing-a-custom-plugin)
265
310
  - [Observability](#observability)
266
311
  - [Development](#development)
267
312
  - [Contributing](#contributing)
@@ -270,227 +315,229 @@ Under the hood, it's a pluggable async pipeline for home security cameras. It re
270
315
  ## Highlights
271
316
 
272
317
  - Multiple pluggable video clip sources: [RTSP](https://en.wikipedia.org/wiki/Real-Time_Streaming_Protocol) motion detection, [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol) uploads, or a watched folder
273
- - Parallel upload + filter ([YOLOv8](https://en.wikipedia.org/wiki/You_Only_Look_Once)) with frame sampling and early exit
318
+ - Parallel upload + filter ([YOLO](https://en.wikipedia.org/wiki/You_Only_Look_Once)) with frame sampling and early exit
274
319
  - OpenAI-compatible VLM analysis with structured output
275
320
  - Policy-driven alerts with per-camera overrides
276
321
  - Fan-out notifiers (MQTT for Home Assistant, SendGrid email)
277
322
  - Postgres-backed state + events with graceful degradation
278
- - Built around small, stable interfaces so new plugins drop in cleanly
279
323
  - Health endpoint plus optional Postgres telemetry logging
280
324
 
281
- ## Pipeline at a glance
282
325
 
283
- ```
284
- ClipSource -> (Upload + Filter) -> VLM (optional) -> Alert Policy -> Notifier(s)
285
- ```
286
-
287
- - Upload and filter run in parallel; VLM runs only when trigger classes are detected.
288
- - Upload failures do not block alerts; filter failures stop processing.
289
- - State is stored in Postgres (`clip_states` + `clip_events`) when available.
290
326
 
291
327
  ## Quickstart
292
328
 
293
- ### Requirements
329
+ ### 30-Second Start (Docker)
330
+ The fastest way to see it in action. Includes a pre-configured Postgres and a dummy local source.
331
+
332
+ ```bash
333
+ git clone https://github.com/lan17/homesec.git
334
+ cd homesec
335
+ make up
336
+ ```
337
+ *Modify `config/config.yaml` to add your real cameras, then restart.*
294
338
 
295
- - Raspberry Pi 4 (or equivalent) or higher; any x86_64 system works as well
296
- - Docker and Docker Compose
297
- - Optional: MQTT broker, Dropbox credentials, OpenAI-compatible API key
339
+ ### Manual Setup
340
+ For standard production usage without Docker Compose:
298
341
 
299
- ### Setup
342
+ 1. **Prerequisites**:
343
+ - Python 3.10+
344
+ - ffmpeg
345
+ - PostgreSQL (running and accessible)
300
346
 
301
- 1. Create a config file:
347
+ 2. **Install**
302
348
  ```bash
303
- cp config/example.yaml config/config.yaml
304
- # Edit config/config.yaml with your settings
349
+ pip install homesec
305
350
  ```
306
- 2. Set environment variables:
351
+
352
+ 3. **Configure**
307
353
  ```bash
308
- cp .env.example .env
309
- # Edit .env with your credentials
354
+ # Download example config & env
355
+ curl -O https://raw.githubusercontent.com/lan17/homesec/main/config/example.yaml
356
+ mv example.yaml config.yaml
357
+
358
+ curl -O https://raw.githubusercontent.com/lan17/homesec/main/.env.example
359
+ mv .env.example .env
360
+
361
+ # Setup environment (DB_DSN is required)
362
+ # Edit .env to set your secrets!
363
+ export DB_DSN="postgresql://user:pass@localhost/homesec"
310
364
  ```
311
- 3. Start HomeSec + Postgres:
365
+
366
+ 4. **Run**
312
367
  ```bash
313
- make up
368
+ homesec run --config config.yaml
314
369
  ```
315
- 4. Stop:
370
+
371
+ ### Developer Setup
372
+ If you are contributing or running from source:
373
+
374
+ 1. **Install dependencies**
316
375
  ```bash
317
- make down
376
+ uv sync
318
377
  ```
319
378
 
320
- ### Running without Docker
379
+ 2. **Start Infrastructure**
380
+ ```bash
381
+ make db # Starts just Postgres in Docker
382
+ ```
321
383
 
322
- If you prefer to run locally:
384
+ 3. **Run**
385
+ ```bash
386
+ uv run python -m homesec.cli run --config config/config.yaml
387
+ ```
323
388
 
324
- 1. Install Python 3.10+ and ffmpeg
325
- 2. `uv sync`
326
- 3. `make db` (starts Postgres)
327
- 4. `make run`
328
389
 
329
390
  ## Configuration
330
391
 
331
- Configs are YAML and validated with Pydantic. See `config/example.yaml` for all options.
392
+ Configuration is YAML-based and strictly validated. Secrets (API keys, passwords) should always be loaded from environment variables (`_env` suffix).
332
393
 
333
- Minimal example (RTSP + Dropbox + MQTT):
394
+ ### Configuration Examples
334
395
 
335
- ```yaml
336
- version: 1
396
+ #### 1. The "Power User" (Robust RTSP)
397
+ Best for real-world setups with flaky cameras.
337
398
 
399
+ ```yaml
338
400
  cameras:
339
- - name: front_door
401
+ - name: driveway
340
402
  source:
341
403
  type: rtsp
342
404
  config:
343
- rtsp_url_env: FRONT_DOOR_RTSP_URL
405
+ rtsp_url_env: DRIVEWAY_RTSP_URL
344
406
  output_dir: "./recordings"
407
+ # Critical for camera compatibility:
408
+ ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
409
+ reconnect_backoff_s: 5
345
410
 
411
+ filter:
412
+ plugin: yolo
413
+ config:
414
+ classes: ["person", "car"]
415
+ min_confidence: 0.6
416
+ ```
417
+
418
+ In your `.env`:
419
+ ```bash
420
+ DRIVEWAY_RTSP_URL="rtsp://user:pass@192.168.1.100:554/stream"
421
+ ```
422
+
423
+ #### 2. The "Cloud Storage" (Dropbox)
424
+ Uploads to Cloud but keeps analysis local.
425
+
426
+ ```yaml
346
427
  storage:
347
428
  backend: dropbox
348
429
  dropbox:
349
- root: "/homecam"
350
430
  token_env: DROPBOX_TOKEN
351
- app_key_env: DROPBOX_APP_KEY
352
- app_secret_env: DROPBOX_APP_SECRET
353
- refresh_token_env: DROPBOX_REFRESH_TOKEN
354
-
355
- state_store:
356
- dsn_env: DB_DSN
431
+ root: "/SecurityCam"
357
432
 
358
433
  notifiers:
359
- - backend: mqtt
360
- config:
361
- host: "localhost"
362
- port: 1883
434
+ - backend: sendgrid_email
435
+ config:
436
+ api_key_env: SENDGRID_API_KEY
437
+ to_emails: ["me@example.com"]
438
+ ```
363
439
 
364
- filter:
365
- plugin: yolo
366
- config:
367
- classes: ["person"]
368
- min_confidence: 0.5
369
-
370
- vlm:
371
- backend: openai
372
- llm:
373
- api_key_env: OPENAI_API_KEY
374
- model: gpt-4o
375
-
376
- alert_policy:
377
- backend: default
378
- enabled: true
379
- config:
380
- min_risk_level: medium
440
+ In your `.env`:
441
+ ```bash
442
+ DROPBOX_TOKEN="sl.Al..."
443
+ SENDGRID_API_KEY="SG.xyz..."
444
+ ```
445
+
446
+ See [`config/example.yaml`](config/example.yaml) for a complete reference of all options.
447
+
448
+ ### Tips
449
+
450
+ - **Secrets**: Never put secrets in YAML. Use env vars (`*_env`) and set them in your shell or `.env`.
451
+ - **Notifiers**: At least one notifier (mqtt/email) must be enabled unless `alert_policy.enabled` is false.
452
+ - **YOLO Classes**: Built-in classes include `person`, `car`, `truck`, `motorcycle`, `bicycle`, `dog`, `cat`, `bird`, `backpack`, `handbag`, `suitcase`.
381
453
 
382
- per_camera_alert:
383
- front_door:
384
- min_risk_level: low
385
- notify_on_activity_types: ["person_at_door", "delivery"]
454
+ After installation, the `homesec` command is available:
455
+
456
+ ```bash
457
+ homesec --help
386
458
  ```
387
459
 
388
- A few things worth knowing:
389
- - Secrets never go in YAML. Use env var names (`*_env`) and set values in your shell or `.env`.
390
- - At least one notifier must be enabled (`mqtt` or `sendgrid_email`).
391
- - Built-in YOLO classes: `person`, `car`, `truck`, `motorcycle`, `bicycle`,
392
- `dog`, `cat`, `bird`, `backpack`, `handbag`, `suitcase`.
393
- - Local storage for development:
460
+ ### Commands
394
461
 
395
- ```yaml
396
- storage:
397
- backend: local
398
- local:
399
- root: "./storage"
462
+ **Run the pipeline:**
463
+ ```bash
464
+ homesec run --config config.yaml
400
465
  ```
401
466
 
402
- - Set `alert_policy.enabled: false` to disable notifications (a noop policy is used).
403
- - For a quick local run, pair `local_folder` with `local` storage and drop a clip
404
- into `recordings/`.
405
-
406
- ## Extensible by design
407
-
408
- HomeSec is intentionally modular. Each major capability is an interface
409
- (`ClipSource`, `StorageBackend`, `ObjectFilter`, `VLMAnalyzer`, `AlertPolicy`,
410
- `Notifier`) defined in `src/homesec/interfaces.py`, and plugins are discovered at
411
- runtime via entry points. This keeps the core pipeline small while making it
412
- easy to add new backends without editing core code.
413
-
414
- What this means in practice:
415
- - Swap storage or notifications by changing config, not code.
416
- - Add a new plugin type as a separate package and register it.
417
- - Keep config validation strict by pairing each plugin with a Pydantic model.
418
-
419
- Extension points (all pluggable):
420
- - Sources: RTSP motion detection, FTP uploads, local folders
421
- - Storage backends: Dropbox, local disk, or your own
422
- - Filters: object detection (YOLO or custom models)
423
- - VLM analyzers: OpenAI-compatible APIs or local models
424
- - Alert policies: per-camera rules and thresholds
425
- - Notifiers: MQTT, email, or anything else you can send from Python
426
-
427
- ## CLI
428
-
429
- - Run the pipeline:
430
- `uv run python -m homesec.cli run --config config/config.yaml --log_level INFO`
431
- - Validate config:
432
- `uv run python -m homesec.cli validate --config config/config.yaml`
433
- - Cleanup (reanalyze and optionally delete empty clips):
434
- `uv run python -m homesec.cli cleanup --config config/config.yaml --older_than_days 7 --dry_run True`
435
-
436
- ## Built-in plugins
437
-
438
- - Filters: `yolo`
439
- - VLM analyzers: `openai` (OpenAI-compatible API)
440
- - Storage: `dropbox`, `local`
441
- - Notifiers: `mqtt`, `sendgrid_email`
442
- - Alert policies: `default`, `noop`
443
-
444
- ## Writing a plugin
445
-
446
- HomeSec discovers plugins via entry points in the `homesec.plugins` group. A plugin
447
- module just needs to import and register itself.
448
-
449
- Each plugin provides:
450
- - A unique name (used in config)
451
- - A Pydantic config model for validation
452
- - A factory that builds the concrete implementation
453
-
454
- ```python
455
- # my_package/filters/custom.py
456
- from pydantic import BaseModel
457
- from homesec.interfaces import ObjectFilter
458
- from homesec.plugins.filters import FilterPlugin, filter_plugin
459
-
460
- class CustomConfig(BaseModel):
461
- threshold: float = 0.5
462
-
463
- class CustomFilter(ObjectFilter):
464
- ...
465
-
466
- @filter_plugin(name="custom")
467
- def register() -> FilterPlugin:
468
- return FilterPlugin(
469
- name="custom",
470
- config_model=CustomConfig,
471
- factory=lambda cfg: CustomFilter(cfg),
472
- )
467
+ **Validate config:**
468
+ ```bash
469
+ homesec validate --config config.yaml
473
470
  ```
474
471
 
475
- ```toml
476
- # pyproject.toml
477
- [project.entry-points."homesec.plugins"]
478
- my_filters = "my_package.filters.custom"
472
+ **Cleanup old clips** (reanalyze and optionally delete empty clips):
473
+ ```bash
474
+ homesec cleanup --config config.yaml --older_than_days 7 --dry_run=False
479
475
  ```
480
476
 
477
+ Use `homesec <command> --help` for detailed options on each command.
478
+
479
+ ## Plugins
480
+
481
+ ### Extensible by design
482
+
483
+ We designed HomeSec to be modular. Each major capability is an interface (`ClipSource`, `StorageBackend`, `ObjectFilter`, `VLMAnalyzer`, `AlertPolicy`, `Notifier`) defined in `src/homesec/interfaces.py`. This means you can swap out components (like replacing YOLO with a different detector) without changing the core pipeline.
484
+
485
+ HomeSec uses a plugin architecture where every component is discovered at runtime via entry points.
486
+
487
+ ### Built-in plugins
488
+
489
+ | Type | Plugins |
490
+ |------|---------|
491
+ | Sources | [`rtsp`](src/homesec/sources/rtsp.py), [`ftp`](src/homesec/sources/ftp.py), [`local_folder`](src/homesec/sources/local_folder.py) |
492
+ | Filters | [`yolo`](src/homesec/plugins/filters/yolo.py) |
493
+ | Storage | [`dropbox`](src/homesec/plugins/storage/dropbox.py), [`local`](src/homesec/plugins/storage/local.py) |
494
+ | VLM analyzers | [`openai`](src/homesec/plugins/analyzers/openai.py) |
495
+ | Notifiers | [`mqtt`](src/homesec/plugins/notifiers/mqtt.py), [`sendgrid_email`](src/homesec/plugins/notifiers/sendgrid_email.py) |
496
+ | Alert policies | [`default`](src/homesec/plugins/alert_policies/default.py), [`noop`](src/homesec/plugins/alert_policies/noop.py) |
497
+
498
+ ### Plugin interfaces
499
+
500
+ All interfaces are defined in [`src/homesec/interfaces.py`](src/homesec/interfaces.py).
501
+
502
+ | Type | Interface | Decorator |
503
+ |------|-----------|-----------|
504
+ | Sources | `ClipSource` | `@source_plugin` |
505
+ | Filters | `ObjectFilter` | `@filter_plugin` |
506
+ | Storage | `StorageBackend` | `@storage_plugin` |
507
+ | VLM analyzers | `VLMAnalyzer` | `@vlm_plugin` |
508
+ | Notifiers | `Notifier` | `@notifier_plugin` |
509
+ | Alert policies | `AlertPolicy` | `@alert_policy_plugin` |
510
+
511
+ ### Writing a custom plugin
512
+
513
+ Extending HomeSec is designed to be easy. You can write custom sources, filters, storage backends, and more.
514
+
515
+ 👉 **See [PLUGIN_DEVELOPMENT.md](PLUGIN_DEVELOPMENT.md) for a complete guide.**
516
+
481
517
  ## Observability
482
518
 
483
- - Health endpoint: `GET /health` (configurable in `health.host`/`health.port`)
484
- - Optional telemetry logs to Postgres when `DB_DSN` is set:
485
- - Start local DB: `make db`
486
- - Run migrations: `make db-migrate`
519
+ - Health endpoint: `GET /health` (configurable via `health.host`/`health.port` in config)
520
+ - Telemetry logs to Postgres when `DB_DSN` is set
487
521
 
488
522
  ## Development
489
523
 
524
+ ### Setup
525
+
526
+ 1. Clone the repository
527
+ 2. Install [uv](https://docs.astral.sh/uv/) for dependency management
528
+ 3. `uv sync` to install dependencies
529
+ 4. `make db` to start Postgres locally
530
+
531
+ ### Commands
532
+
490
533
  - Run tests: `make test`
491
534
  - Run type checking (strict): `make typecheck`
492
535
  - Run both: `make check`
493
- - Tests must include Given/When/Then comments.
536
+ - Run the pipeline: `make run`
537
+
538
+ ### Notes
539
+
540
+ - Tests must include Given/When/Then comments
494
541
  - Architecture notes: `DESIGN.md`
495
542
 
496
543
  ## Contributing
@@ -20,7 +20,7 @@ homesec/models/config.py,sha256=Z70xKrbDHi96j8IoRXPBw7DYAI7sjHR4M6nqReHTNds,1418
20
20
  homesec/models/enums.py,sha256=WQk1PvYnJd5iaz51P5GM3XayqCf0VpQnLYmSuw1hrZk,3293
21
21
  homesec/models/events.py,sha256=sgPDCSp9w60VUKKREYxNBZaxrFWW_bYyLwMFGHBawjk,4942
22
22
  homesec/models/filter.py,sha256=6NS1rBI2zmrswK9NtMn0vZlbwbeMGCPuLCDKP9XWN0I,2259
23
- homesec/models/source.py,sha256=PDtANsPaVzLz_VvR3cFVkQcfvema-7TrcuTY8a-gVq0,2309
23
+ homesec/models/source.py,sha256=F8ksGrOa09bxx5IEgQmxPWJNWOm8zvFLsRJxihJTlM4,2367
24
24
  homesec/models/storage.py,sha256=63wyHdDt3QrfdsP0SmhrxtOeWRllZ1O2GPrA4jI7XmU,235
25
25
  homesec/models/vlm.py,sha256=Uk6TPwqbKxzyAsOlBSzZru74nKjp2-LLyzIp5b3wM_c,3293
26
26
  homesec/pipeline/__init__.py,sha256=kiQLECc6JIPmeIdBJrVpTApPs0GBAgWoZ1kU4XZyJVY,214
@@ -35,7 +35,7 @@ homesec/plugins/alert_policies/noop.py,sha256=xcdyZAlQf6X_AqQWUPkRMHLK8J6d7PTXWo
35
35
  homesec/plugins/analyzers/__init__.py,sha256=GLC01lACmqZjKjMbkUnB_yYnOTxW_XEOwOfHK-I9SX0,915
36
36
  homesec/plugins/analyzers/openai.py,sha256=qLN9Qhj-DvqEE6nHXIfW0QPp2EhSVwIVVXzq-QSqdzQ,15979
37
37
  homesec/plugins/filters/__init__.py,sha256=JgPJTcW8yKa6JRIED4w8o5j_n_k5J7xwQbhAqDYofA4,866
38
- homesec/plugins/filters/yolo.py,sha256=gCuFHtqZsJVimaX1p7XvP9MTP9vliLiwniZWioAH5RA,11130
38
+ homesec/plugins/filters/yolo.py,sha256=RUJqP9vtHDEoTqR9U3zt2ae_4Y8DGaJ2bsmJGVlfYMw,11126
39
39
  homesec/plugins/notifiers/__init__.py,sha256=6T3OX4Vg1XKZeSG6AlZzw0o2KlaRr_mePBDIZEbr5Rw,1066
40
40
  homesec/plugins/notifiers/mqtt.py,sha256=1zUKUHFvT65ysawFXEwWHvY8rg310fRsSSKhIFeaUL4,5946
41
41
  homesec/plugins/notifiers/multiplex.py,sha256=LlnwozjkMDQwz7__v7mT4AohZbiWZK39CZunamRp7FM,3676
@@ -53,7 +53,7 @@ homesec/sources/__init__.py,sha256=wuCtiF44ceo7n3wJN51VHHcDavko3ubUDICtFbWmaRI,5
53
53
  homesec/sources/base.py,sha256=dKTxJxcDwJtykWDN3WYzkW5mtkRqlOJxJLWcLy82_Zo,7582
54
54
  homesec/sources/ftp.py,sha256=ynIPbgcbIi1jub8yr4H1259Y1HbNM42RFDBBivXD4mg,7308
55
55
  homesec/sources/local_folder.py,sha256=eW7ghgRsqTnZ5ZMPbsXh9ntqfue1UeM29ZpvRvLthPA,8461
56
- homesec/sources/rtsp.py,sha256=3TOFDfIqadH3-DepB1xgv6mtOd0h-FYppZXBXSTBBi0,47304
56
+ homesec/sources/rtsp.py,sha256=T_DoAExu58pukRDjPNZ7wELDIebbQpVFrg29L9xACa8,48866
57
57
  homesec/state/__init__.py,sha256=Evt1jqTebmpJD1NUzNh3vwt5pbjDlLjQ0DgMCSAZOuM,255
58
58
  homesec/state/postgres.py,sha256=I-cXqW5cgz-hpaHc0JIv3DnIBTmGxE28P8ZxBAGabSw,17765
59
59
  homesec/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -61,8 +61,8 @@ homesec/telemetry/db_log_handler.py,sha256=KM8g4kcOyPzFJbpGxpSzecx_hrEWY0YfpoIKy
61
61
  homesec/telemetry/postgres_settings.py,sha256=EVD2_oi_KReFJvQmXxW026aurl_YD-KexT7rkbGQPHc,1198
62
62
  homesec/telemetry/db/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
63
63
  homesec/telemetry/db/log_table.py,sha256=wcZLwRht7FMa0z2gf37f_RxdVTNIdDiK4i_N3c_ibwg,473
64
- homesec-1.1.2.dist-info/METADATA,sha256=hdgJS5FnVtvipMsjSCaG_f9T2s_QLEdePmTDrcQF9yI,23274
65
- homesec-1.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
66
- homesec-1.1.2.dist-info/entry_points.txt,sha256=8ocCj_fP1qxIuL-DVDAUiaUbEdTMX_kg_BzVrJsbQYg,45
67
- homesec-1.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
68
- homesec-1.1.2.dist-info/RECORD,,
64
+ homesec-1.2.1.dist-info/METADATA,sha256=UqWtHMZ6XjaykxI7ya_Fu-fwUTHVAEMl15sBfEbysjI,25121
65
+ homesec-1.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
66
+ homesec-1.2.1.dist-info/entry_points.txt,sha256=8ocCj_fP1qxIuL-DVDAUiaUbEdTMX_kg_BzVrJsbQYg,45
67
+ homesec-1.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
68
+ homesec-1.2.1.dist-info/RECORD,,