P2P baby monitor: child device (near baby) streams audio/video/sensors to parent device over WiFi
  • Dart 89.3%
  • Kotlin 7.4%
  • C++ 1.5%
  • CMake 1.2%
  • Makefile 0.3%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-21 15:53:01 +02:00
.forgejo/workflows ci: materialize Flutter dependencies in the android-test job 2026-09-18 11:59:52 +00:00
.githooks chore: add fast pre-commit smoke gate and make targets 2026-09-05 07:33:22 +00:00
.opencode chore: add session-restart-after-chunk skill and plugin permission 2026-09-16 13:35:13 +00:00
android feat(android): setQuality control updates the encoder without rebinding 2026-09-20 18:08:42 +00:00
docs docs(networking): describe the configurable adaptive video stream (issue #177) 2026-09-20 18:17:57 +00:00
integration_test feat(child): live capability cards replace toggle rows (issue #164) 2026-09-19 12:36:17 +00:00
ios feat(ios): declare camera, mic, local-network usage descriptions (issue #12) 2026-09-15 22:22:40 +00:00
lib feat(child): resolution selection row and fps slider in the camera settings sheet 2026-09-21 13:47:27 +00:00
linux fix(security): route the device private key through OS secure storage 2026-09-08 16:42:13 +00:00
macos build(deps): add shared_preferences for camera facing persistence 2026-09-20 11:18:09 +00:00
test feat(child): resolution selection row and fps slider in the camera settings sheet 2026-09-21 13:47:27 +00:00
test_driver test: add unit/widget/golden pyramid and integration screenshot path 2026-09-05 07:33:13 +00:00
tool fix(ci): harden coverage and manifest gates per review findings (issue #153) 2026-09-17 17:37:16 +00:00
windows fix(security): route the device private key through OS secure storage 2026-09-08 16:42:13 +00:00
.gitignore feat(parent): device dashboard when no child is selected 2026-09-15 21:08:55 +00:00
.metadata feat: initialize Flutter project with clean architecture 2026-09-05 06:51:45 +00:00
analysis_options.yaml feat: initialize Flutter project with clean architecture 2026-09-05 06:51:45 +00:00
CONTRIBUTING.md ci: run Robolectric Android unit tests as a separate CI job (issue #80) 2026-09-18 11:59:52 +00:00
Makefile ci: run Robolectric Android unit tests as a separate CI job (issue #80) 2026-09-18 11:59:52 +00:00
pubspec.lock build(deps): add shared_preferences for camera facing persistence 2026-09-20 11:18:09 +00:00
pubspec.yaml build(deps): add shared_preferences for camera facing persistence 2026-09-20 11:18:09 +00:00
README.md ci: run Robolectric Android unit tests as a separate CI job (issue #80) 2026-09-18 11:59:52 +00:00

Lullalink

Peer-to-peer baby monitor for Flutter.

  • The child device (one phone, near the baby) streams audio, video, and gyro/accelerometer data to the parent device (the other phone, near the parent) over WiFi — no cloud, no server.
  • Secure by default: all streams use mutual TLS (mTLS) with self-signed certificates. Two phones trust each other by scanning a QR code of either device to pin each other's certificate/fingerprint. No PIN or password is needed — mTLS alone is sufficient.
  • Audio and video streaming are each independently toggleable on the transmit side (child) and the receive side (parent).
  • The parent sees a live audio gain graph, a frequency spectrum of the captured audio, and a movement graph from the child phone's gyro/accelerometer (phone rests on the bed → baby movement visible).
  • The parent can configure notification thresholds (e.g. sound level, movement frequency).
  • Future (v0.2): optional on-device ML on either device for movement classification and audio/sound-type classification.

Development

All tests — including the full UI suite (golden/widget tests, fake plugins, no physical device) — run and are verifiable entirely in headless CI.

The canonical testing strategy is documented in CONTRIBUTING.md. Every feature follows it.

Architecture

The P2P networking, discovery, mTLS trust model, and domain model are designed in docs/architecture/networking.md.

Clean architecture with three layers:

lib/
├── core/           Constants, utilities, extensions
├── data/           Data sources, models, repository implementations
├── domain/         Entities, use cases, repository interfaces
└── presentation/   UI screens, widgets, providers, routing

Key decisions

Concern Choice Rationale
State management Riverpod Testable, no BuildContext needed, compile-safe providers
Routing GoRouter Declarative, deep-link friendly, nested routes
Immutable models Freezed Code-generated value objects, union types for states
Serialization json_serializable Pairs with Freezed, compile-time safety

Platform support

The app targets Android + desktop:

  • Child device: Android phone — streams audio, video, sensors
  • Parent device: Android phone or desktop (macOS/Linux/Windows) — receives streams

iOS is not supported. The native capture/discovery handlers are implemented for Android only (android/app/src/main/kotlin/se/jilits/emidab/lullalink/LullalinkCaptureChannels.kt); the ios/ runner ships as the stock Flutter template and is neither built nor tested.

Desktop networking uses mDNS/broadcast discovery and manual configuration (no camera needed for pairing trust). Platform-specific channels are abstracted in data/datasources/ so the domain layer stays platform-neutral.

Running

flutter pub get
flutter run              # default platform
flutter run -d linux     # Linux desktop
flutter run -d macos     # macOS desktop

Verification

make check               # analyze + format + full headless test suite
make test                # unit + widget + golden tests (headless)
make android-test        # Robolectric tests for native Android handlers
make integration-test    # full-app flow + screenshots (needs a target)
make hooks               # install pre-commit / commit-msg git hooks

Integration tests need a target (Linux desktop headless, emulator, or device); see CONTRIBUTING.md. Interactive widgets expose stable Keys (in lib/core/constants/app_keys.dart) so tests and external automation/AI agents can drive and screenshot the app headlessly (frames land in build/screenshots/).