The Problem
Local‑area‑network monitoring that repurposes unused Android devices often stalls because the capture pipeline, AI inference, and dashboard are built as separate, poorly‑documented pieces. Teams end up stitching together video streams, manually parsing frames, and lack a reproducible way to lock dependency versions, which breaks CI and makes onboarding new engineers painful.
What This Does
Sentinel is a LAN‑based, smartphone‑powered AI monitoring framework that moves image acquisition to Android phones and does PC‑side processing. The repo is split into three logical layers:
- Capture layer –
PhoneCamSender/(Android Kotlin) runsMainActivity.kt, discovers the PC on the LAN viaNetworkDiscover.kt, and streams JPEG frames over HTTP. - Processing layer – The PC side (
server.py, the entry point) importsapp.core.frame_buffer,app.core.logger,app.net.net_discoveryand the AI stack (app/ai/vision_client.py,app/ai/ai_monitor_worker.py,app/ai/ai_store.py,app/ai/motion_trigger.py). Frames are buffered, motion‑triggered events are generated, and results are stored locally or shipped to a downstream analytics pipeline. - Control layer – A Flask‑based web dashboard (
app/web/webapp.py, templates inapp/web/templates/, static JS/CSS) lets an operator start/stop recording, view live preview, and inspect structured event output (JSON lines written byapp/ai/ai_monitor_worker.py).
Files such as requirements.txt, app/config/config_store.py and app/core/runtime.py centralise version metadata and runtime configuration (e.g., API keys, recording codecs). The Android APKs are built with Gradle (PhoneCamSender/build.gradle.kts, PhoneCamSender/gradle/) and the source is versioned in version.properties.
How It Is Wired
Execution flow (entry → work → side‑effects):
server.pystarts a Flask app (flask.Flask) bound to0.0.0.0on a port read from config. On startup it callsapp.net.net_discovery.discover()to announce itself on the LAN and loadsapp/config/config_store.pyfor any API keys.- The dashboard (
app/web/webapp.py) opens a WebSocket connection to/stream; the Flask route handler forwards incoming JPEG frames from connected Android clients toapp.core.frame_buffer.update(frame). app.core.frame_buffer(a simple circular buffer) pushes each new frame toapp/ai/vision_client.py, which sends the image to a user‑supplied model (e.g., an Ark‑AI endpoint) and returns a JSON event.app/ai/ai_monitor_worker.pyconsumes the event, firesmotion_trigger.pyif confidence exceeds a threshold, and writes a structured record toapp/ai/ai_store.py(SQLite or CSV depending on config).- The recorder worker (
app/recorder/recorder_worker.py) periodically flushes the buffer to an MP4 file using the codec selected inapp/config/config_store.py; fallback tolibx264is provided for Windows compatibility. - All runtime logs (including redacted API keys) go to
app/core/logger.py.
Files that cross process boundaries:
server.py– orchestrates the PC side, imports most core modules.app/core/frame_buffer.py– shared mutable state between network receiver and AI pipeline.app/ai/vision_client.py– the only code that leaves the LAN to call an external model API.app/recorder/recorder.py– writes video to disk; called by the UI viarecorder_worker.py.
A hub exists at server.py; any change to the discovery protocol or frame format ripples to the Android client (NetworkDiscover.kt), the buffer, and the UI. No cycles were detected, but the centrality of server.py means a rewrite of the networking layer requires coordinated updates on both sides.
How To Use It
| Step | Command / Action | Reference |
|---|---|---|
| Install Python deps | pip install -r requirements.txt | requirements.txt lists Flask, OpenCV, etc. |
| Start PC server | python server.py | server.py is the entry point; it reads app/config/config_store.py for host/port. |
| Configure | Edit app/config/config_store.py (or copy config/ defaults) to set LAN IP, model endpoint, recording path. | config_store.py holds MODEL_URL, RECORDING_CODEC, MAX_UPLOAD_SIZE. |
| Build Android client | From repo root: cd PhoneCamSender && ./gradlew assembleDebug | build.gradle.kts and gradle/ present. |
| Run Android app | Install the generated CamFlow‑v1.1.1.apk on a phone (Android 8.0+). Open the app; it will auto‑discover the PC IP via NetworkDiscover.kt and start streaming. | NetworkDiscover.kt uses mDNS/LAN broadcast. |
| Open dashboard | Point a browser to http://<PC‑IP>:<PORT>; the dashboard loads app/web/templates/dashboard.html and connects to the WebSocket stream. | dashboard.html references static/dashboard.js. |
Missing: A Dockerfile or Makefile for containerised deployment; the README only describes manual Python/gradle runs.
Real‑World Use
A factory floor wants to monitor assembly quality without installing expensive CCTV. Engineers spin up a single‑board PC (Raspberry Pi or x86) running server.py, attach four old Android phones running the CamFlow APK, and point the dashboard at the PC. Motion events from ai_monitor_worker.py trigger an alarm in the factory’s MES system via a webhook written to app/ai/ai_store.py. The operator can review recorded MP4 clips and export the JSON event log for downstream quality‑analysis scripts.
Code Health & Issues
Low – dependencies declared without a lockfile – requirements.txt: Packages are listed without a requirements.lock or pip-compiled file, so pip install -r requirements.txt may pull different versions across machines, breaking reproducibility of the AI model pipeline. Adding a requirements.lock (generated via pip freeze > requirements.lock) or migrating to a pyproject.toml with uv/poetry would mitigate this.
No other measured findings were flagged by the static analysis; the repo contains 9 test files under tests/ and 7 doc files, but CI coverage is not enforced (no coverage report in the .github/workflows/ci.yml excerpt).
The Bottom Line
Sentinel provides a functional, LAN‑based vision pipeline that turns spare Android phones into a distributed camera network with AI‑driven event output. The codebase is reasonably modular (capture → buffer → AI → dashboard) and the README gives clear, if manual, deployment steps. The main risk is the un‑locked Python dependency list and the single‑point‑of‑failure nature of server.py; teams that need reproducible builds or high‑availability clustering will want to add a lockfile and consider containerising the Flask process. It’s a solid prototype platform for local visual data acquisition and quick AI prototyping, but production roll‑out should address dependency management and redundancy.