{"id":50767015,"url":"https://github.com/akaihola/flashphoto-android7","last_synced_at":"2026-06-11T14:32:16.649Z","repository":{"id":346180129,"uuid":"1188548733","full_name":"akaihola/flashphoto-android7","owner":"akaihola","description":"Autonomous flash photography on Honor NEM-L21 (Android 7, EMUI) – custom APK with Camera2 TORCH mode workaround, built entirely by AI coding agents","archived":false,"fork":false,"pushed_at":"2026-03-22T17:39:15.000Z","size":75,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-23T07:47:12.884Z","etag":null,"topics":["ai-generated"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akaihola.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-22T08:33:25.000Z","updated_at":"2026-03-22T17:39:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/akaihola/flashphoto-android7","commit_stats":null,"previous_names":["akaihola/flashphoto-android7"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/akaihola/flashphoto-android7","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akaihola%2Fflashphoto-android7","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akaihola%2Fflashphoto-android7/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akaihola%2Fflashphoto-android7/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akaihola%2Fflashphoto-android7/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akaihola","download_url":"https://codeload.github.com/akaihola/flashphoto-android7/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akaihola%2Fflashphoto-android7/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34204179,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-generated"],"created_at":"2026-06-11T14:32:15.867Z","updated_at":"2026-06-11T14:32:16.643Z","avatar_url":"https://github.com/akaihola.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FlashPhoto for Android 7 (EMUI)\n\n[![Built with Claude Code](https://img.shields.io/badge/Built_with-Claude_Code-6f42c1?logo=anthropic\u0026logoColor=white)](https://claude.ai/code)\n\n\u003e This project is developed by an AI coding agent ([Claude][claude-code], via\n\u003e [Pi][pi-agent]), with human oversight and direction.\n\nA minimal Android APK that takes flash-illuminated photos via Camera2 API,\ntriggered by broadcast intent from Termux. Built to work autonomously on an\nHonor NEM-L21 (Android 7.0, EMUI) deployed for periodic timelapse photography –\n**no USB cable, no screen wake, no human interaction required**.\n\n**All research, experimentation, scripting, Java/APK development, on-device\ntesting, and documentation in this project was performed autonomously by coding\nagents.** In early February, [openclaw-termux][openclaw-termux] and its agent\npersona Tyko, running on gogo and backed by Claude Opus 4.5, researched\nAndroid camera options, wrote the initial timelapse scripts, and documented\nADB/Termux tricks. Then on March 21, a Pi agent on atom, using mostly Claude\nOpus 4.6 and partly Claude Sonnet 4.6, tackled the flash problem: *\"Your goal\nis to have the phone take a photo with the flash. Don't stop until you've\nsucceeded.\"* The human's role throughout was providing the goal, answering\nclarifying questions, physically connecting/disconnecting the USB cable, and\nstarting SSH when asked. The failed attempts described here were not\nhand-written inputs from the human – they came from the agents' own\nexperiments, logs, errors, and image measurements. Every line of code, every\nfailed experiment, every workaround discovery, and this README were produced by\nagents across **16+ sessions totalling 1,822+ API turns and ~174M+ tokens\n($165+ at API pricing)**. See [Agent session cost](#agent-session-cost) for the\nfull breakdown.\n\n[claude-code]: https://claude.ai/code\n[pi-agent]: https://github.com/mariozechner/pi\n[openclaw-termux]: https://github.com/explysm/openclaw-termux\n\n---\n\n## The problem\n\nAn old Honor 5C phone (NEM-L21) is repurposed as a timelapse camera pointed at\nthe house mechanical room. The phone sits on mains power, connected to Wi-Fi,\nrunning [Termux][termux] with SSH access. A cron job takes a photo every three\nhours.\n\nThe immediate goal is to capture flash-illuminated images of the utility gauges\nin this otherwise dark space – cold water meter, hot water meter, and hydronic\nradiator heating pressure/temperature gauges. The longer-term vision is to\ndevelop machine reading of these gauge faces to automatically track water\nconsumption and heating system status over time.\n\nThe catch: the room is dark. Without flash, photos are black (0.01% mean\nbrightness). Getting the flash to fire from a headless SSH session turned out to\nbe a six-week odyssey through Android's camera stack, Huawei's proprietary HAL,\nand the limits of what an unprivileged app can do on a locked-down device.\n\n[termux]: https://termux.dev/\n\n---\n\n## Full project timeline\n\n### Week 1: Initial setup (February 5–6, 2026) — openclaw-termux / Tyko on gogo\n\nThe Honor NEM-L21 was set up by [openclaw-termux][openclaw-termux] and its\nagent persona Tyko, running in Termux on gogo and backed by Claude Opus 4.5.\nThe agent researched 5 approaches for periodic Android photography (see\n`termux/docs/android-periodic-photos.md`), wrote the ADB/Termux knowledge base\n(`android-tricks.md`), created the initial `honor-timelapse.sh` trigger\nscript, and documented everything. Archived session logs are under\n`/home/akaihola/my-knowledge/archives/openclaw-sessions/` (9 sessions, 788 API\nturns, $59.14):\n\n- Installed Termux, Termux:Boot, Termux:API, F-Droid\n- Installed Dropbear SSH server, configured key-based auth\n- Created `~/.termux/boot/start-services` to auto-start wake lock, dropbear,\n  and crond on boot\n- Wrote `timelapse.sh` – a loop-based timelapse script that ran continuously:\n  ```bash\n  while true; do\n      termux-torch on \u0026\n      termux-camera-photo -c 0 \"$DIR/$TIMESTAMP.jpg\"\n      termux-torch off\n      sleep 10800  # 3 hours\n  done\n  ```\n  Photos were black. The torch LED turned off the instant Camera2 opened a new\n  session – they share the same hardware.\n\n### Week 2: Cron-based automation (February 11, 2026) — manual by human\n\nReplaced the loop with cronie for reliability (manual human work on the phone,\nno agent sessions found for this period):\n\n- Installed `cronie`, `imagemagick` in Termux\n- Wrote `flash-photo.sh` – brought Termux to foreground before torch+capture\n  (EMUI required foreground for torch). Brightness: 0.4% – faint outlines.\n- Wrote `photo_shoot.py` and `photo_shoot_v2.py` – Python wrappers with\n  delays between torch and capture, wake lock management, cleanup. Same result:\n  Camera2 kills the torch when opening a session.\n- Wrote `service-watchdog.sh` – checks every 15 minutes that dropbear and crond\n  are alive, restarts them if not\n- Set up crontab: photo every 3 hours, watchdog every 15 minutes\n- Wrote `timelapse-cron.sh` as the cron wrapper\n\nThe cron job ran faithfully for six weeks, producing consistently dark photos.\nThe phone ran on mains power, reporting via SSH. On March 8, internal storage\nfilled up – photos started failing with ENOSPC until old files were cleaned.\n\n### Week 7: The flash breakthrough (March 21–22, 2026) — all agent work\n\nSix weeks of dark photos later, the human handed the problem to a Pi coding\nagent on atom. Three intensive Pi sessions in a single day, using Claude Opus\n4.6, cracked the problem, followed by a fourth session the next morning for the\nAPK, cron integration, and this repo.\n\n#### Session 1: Exploration (Pi session `9c4eed4e`, 17:36–20:20)\n\nThe initial prompt: *\"Your goal is to have the phone take a photo with the\nflash. Brainstorm, experiment, test, iterate, make detailed notes. There are\nsome unsuccessful scripts in the Termux home directory. Don't stop until you've\nsucceeded.\"*\n\nSix approaches were tried:\n\n| # | Approach | Result |\n|---|----------|--------|\n| 1 | `termux-torch on` + `termux-camera-photo` | Torch killed by Camera2 session. 0.01% brightness. |\n| 2 | Custom `FlashPhoto.java` via `app_process` | `systemMain()` → SIGKILL. `attach(false)` → SIGKILL. |\n| 3 | `dalvikvm` with framework bootclasspath | `UnsatisfiedLinkError` – native libs unavailable. |\n| 4 | Patch Termux:API smali (AE_MODE_ON → ALWAYS_FLASH) | Shared user ID requires matching APK signature. |\n| 5 | Build standalone APK on device (ecj + dx + aapt) | aapt SDK 33 can't parse device framework-res SDK 24. |\n| 6 | ADB `input swipe` to tap Huawei camera shutter | ✅ **Worked!** 30–33% brightness. But needs USB. |\n\n**Key discovery:** The Huawei camera app's own flash works perfectly via UI\nautomation. The `input` command simulates a shutter tap, and the camera fires\nthe flash as configured (set to \"Päälle\" / On).\n\nMid-session, the user clarified: *\"The phone will be mounted to a location where\nit's running standalone connected to mains power but no other cables or devices\nattached.\"* This ruled out ADB-dependent solutions.\n\nThe session ended with comprehensive documentation of all findings and a plan\nfor the self-loopback ADB approach.\n\n#### Session 2: Self-loopback ADB (Pi session `12acdef4`, 20:20–21:29)\n\nThe user reconnected USB and enabled ADB TCP mode. The session implemented\nself-loopback ADB:\n\n1. Termux installs its own `adb` client (`pkg install android-tools`)\n2. With USB connected, `adb tcpip 5555` enables TCP mode on adbd\n3. Termux's adb connects to `127.0.0.1:5555` (device to itself)\n4. `adb -s emulator-5554 shell input swipe ...` works from Termux scripts\n\nProduction script `flash-photo-adb-local.sh` was written and tested: wakes\nscreen, swipes to unlock, launches Huawei camera, taps shutter, copies photo.\nPhotos had 27–44% brightness.\n\n**Critical discovery:** On this EMUI device, adbd unconditionally stops when USB\nis physically disconnected, even with TCP mode active. Every persistence\nworkaround failed (setprop, settings, active TCP client). The USB cable had to\nstay connected.\n\n#### Session 3: The FlashPhoto APK (Pi session `d98b9bef`, 21:29–ongoing)\n\nThe goal: *\"Find a work-around to enable autonomous periodic photo with flash\nwithout a connected USB cable.\"*\n\n**Insight:** Termux's `am` command (termux-am) works without ADB – it uses\n`app_process` with its own JAR. If a standalone APK could take flash photos\nwhen triggered by `am broadcast`, no ADB or USB would be needed.\n\nThe APK was built on the host computer (NixOS) since the phone's aapt was\nincompatible:\n\n- `aapt2` from nixpkgs for manifest compilation\n- `javac` from JDK 17 for Java compilation\n- `d8` from Android SDK build-tools 33 for dex conversion\n- `android.jar` from Android SDK platform 33 for compilation classpath\n- `jarsigner` from JDK 17 for APK signing\n\n**Discovery: EMUI Camera2 HAL lies about flash.** The first APK used\n`CONTROL_AE_MODE_ON_ALWAYS_FLASH` (mode 3). The capture result reported\n`FLASH_STATE=FIRED` (3). Photo brightness: 0.08%. The HAL log told the truth:\n`setFlashStatus() flash status:0` – the hardware flash never fired.\n\n**Discovery: TORCH mode works.** Using `FLASH_MODE_TORCH` (mode 2) keeps the\nLED on continuously via the flashlight code path – one that the Huawei HAL\n*does* honor. With screen on: 28–29% brightness.\n\n**Discovery: SurfaceTexture fails with screen off.** The standard Camera2\npattern uses a dummy `SurfaceTexture` for preview. This requires an OpenGL ES\ncontext, which is unavailable when the screen is off. Error:\n`createStream: Failed to query Surface consumer usage: No such device (-19)`.\nFix: use only `ImageReader` (no SurfaceTexture), discard preview frames.\n\nInitial result: 7–14% brightness with screen off, but intermittently dark\n(0.3%) due to timing race between torch and `TEMPLATE_STILL_CAPTURE`.\n\n#### Session 4: Preview-frame strategy (March 22, 2026)\n\nA follow-up session diagnosed the intermittent failures: on Huawei EMUI, the\nCamera2 HAL kills the torch LED when `TEMPLATE_STILL_CAPTURE` fires. The fix:\n**save a TORCH-lit preview frame directly** from the JPEG ImageReader, never\nissuing a still capture at all. This yielded **consistent 12–16% brightness**\non both battery and AC power. See [EXPERIMENTS.md] for detailed timing results.\n\nFinal result: **12–16% brightness with screen off, fully autonomous**, triggered\nby `am broadcast` from a Termux cron job.\n\n#### Session 5: Production tuning and documentation (March 22–23, 2026)\n\nAfter the preview-frame breakthrough, several shorter Pi sessions refined the\nsystem for real use in the mechanical room:\n\n- Replaced the painfully slow ImageMagick brightness measurement with Pillow on\n  the phone\n- Tested the phone on AC power in the actual installation room\n- Added manual exposure controls (`iso`, `exposure_ms`) to compensate for the\n  auto-exposure algorithm underexposing a mostly dark scene\n- Settled on **ISO 800 + 100 ms** as the production default, raising measured\n  brightness to **24%** and making the gauges clearly readable\n- Updated the wrapper script, experiments log, README, and the Finnish\n  narrative document to reflect the production setup\n- Disabled Huawei's background killers (`com.huawei.powergenie` and\n  `com.huawei.android.hwaps`) over ADB so they would stop killing cron and\n  Dropbear\n\nAt this point the project had crossed the line from \"flash works\" to \"usable\nproduction photos arrive automatically\".\n\n#### Session 6: Focus fix and deployment (March 25–26, 2026)\n\nOnce the camera was producing bright images reliably, a new problem showed up:\nmany production photos were soft and slightly out of focus. A Pi session on\nMarch 25 analyzed the Camera2 flow and identified the root cause: the APK was\nsaving whatever TORCH-lit preview frame happened to arrive after warmup, with\nno explicit autofocus confirmation.\n\nThe fix added three pieces:\n\n- **AF-lock-before-save** – trigger `AF_TRIGGER_START` after warmup and wait\n  for `FOCUSED_LOCKED` / `NOT_FOCUSED_LOCKED`\n- **Manual focus mode** – allow a calibrated `focus_diopters` override for a\n  fixed-mount installation\n- **Diagnostic focus logging** – record AF state, lens movement, and focus\n  distance for each frame\n\nOn March 26 the new APK was deployed and tested on the phone. ADB logcat showed\nAF locking around **1.13 diopters** (~88 cm), while a manually tuned focus\nsetting around **1.3 diopters** produced the sharpest result in the actual\nmechanical room. The production Termux wrapper was updated to use the calibrated\nmanual focus setting, and an end-to-end test produced a **24.3% brightness**\nimage with visibly sharper gauge faces and labels. The comparison shots were\nalso copied into the follow-on `gauge-reader` project for machine-vision\nexperiments.\n\n[EXPERIMENTS.md]: EXPERIMENTS.md\n\n---\n\n## Device details\n\n| Property | Value |\n|----------|-------|\n| Model | Honor NEM-L21 (Honor 5C / Honor 7 Lite) |\n| Android | 7.0 (API 24), EMUI |\n| Camera | Back camera ID 0, 4160×3120 (13MP) |\n| AE modes | 0 (OFF), 1 (ON), 2 (AUTO_FLASH), 3 (ALWAYS_FLASH) |\n| Flash | Available, but ALWAYS_FLASH mode broken in HAL |\n| Storage | Internal 11GB + SD card 59GB |\n| Termux user | u0_a133 (UID 10133) |\n| FlashPhoto APK | com.flashphoto (UID 10100) |\n| SSH | Dropbear on port 8022, key-based auth |\n| Boot | Termux:Boot → wake lock + dropbear + crond |\n\n---\n\n## Brightness comparison\n\n| Method | Brightness | Status |\n|--------|-----------|--------|\n| No flash (dark room) | 0.01% | Essentially black |\n| Torch + termux-camera-photo | 0.01–0.4% | Torch killed by Camera2 session |\n| Camera2 `ALWAYS_FLASH` | 0.08% | HAL ignores, flash doesn't fire |\n| **FlashPhoto TORCH + manual exposure (production)** | **24%** | ✅ Production method (ISO 800, 100ms) |\n| FlashPhoto TORCH + auto-exposure (small room) | 12–16% | Works in small reflective spaces |\n| FlashPhoto TORCH + auto-exposure (large dark room) | 0.8–1.6% | AE underexposes |\n| FlashPhoto TORCH STILL_CAPTURE (screen off) | 0.3–14% | Unreliable – HAL kills torch |\n| FlashPhoto TORCH (screen on) | 28–29% | Better but screen must be on |\n| ADB + Huawei camera app | 27–44% | Requires USB cable |\n\n---\n\n## Building\n\n### Prerequisites\n\n- NixOS or `nix-shell` available\n- Internet access (downloads Android SDK components on first build)\n\n### Build\n\n```bash\n./build.sh\n```\n\nThis produces `/tmp/flashphoto-build/flashphoto.apk`. A pre-built APK is\nincluded in the repo as `flashphoto.apk`.\n\n### Install (one-time, requires USB)\n\n```bash\nadb install flashphoto.apk\nadb shell pm grant com.flashphoto android.permission.CAMERA\nadb shell pm grant com.flashphoto android.permission.WRITE_EXTERNAL_STORAGE\nadb shell pm grant com.flashphoto android.permission.READ_EXTERNAL_STORAGE\n```\n\nAfter installation, USB can be disconnected permanently.\n\n---\n\n## Usage\n\n### Take a single photo (from SSH)\n\n```bash\nssh -p 8022 user@phone \\\n  'am broadcast -n com.flashphoto/.FlashReceiver \\\n   -a com.flashphoto.TAKE -e file /sdcard/DCIM/photo.jpg'\n```\n\nWait ~5 seconds for the capture to complete.\n\n### Using the wrapper script\n\n```bash\nssh -p 8022 user@phone '~/bin/flash-photo-broadcast.sh'\nssh -p 8022 user@phone '~/bin/flash-photo-broadcast.sh ~/photos/custom.jpg'\n```\n\nThe wrapper handles directory creation, copies from shared storage to Termux\nprivate storage, measures brightness via Pillow, and cleans up.\n\n---\n\n## Termux setup\n\nSee the `termux/` directory for all scripts deployed on the phone.\n\n### Boot script (`termux/boot/start-services`)\n\nRuns automatically on device boot via Termux:Boot:\n\n```bash\ntermux-wake-lock   # prevent CPU sleep\ndropbear -s        # SSH server (key-only auth)\ncrond              # cron daemon for timelapse\n```\n\n### Crontab (`termux/crontab`)\n\n```cron\n# Service watchdog – restart dropbear/crond if dead\n*/15 * * * * ~/bin/service-watchdog.sh\n\n# Timelapse photo every 3 hours\n0 */3 * * * ~/bin/timelapse-cron.sh \u003e\u003e ~/cron.log 2\u003e\u00261\n```\n\n### Installed Termux packages\n\n```\nandroid-tools  apksigner  cronie  dropbear  ecj  python-pillow\n```\n\n### Scripts on the phone (`termux/bin/`)\n\n| Script | Purpose | Date |\n|--------|---------|------|\n| `flash-photo-broadcast.sh` | **Production** – FlashPhoto APK via broadcast | Mar 22 |\n| `timelapse-cron.sh` | Cron wrapper calling flash-photo-broadcast.sh | Mar 22 |\n| `service-watchdog.sh` | Restarts dropbear/crond if dead | Feb 11 |\n| `flash-photo-adb-local.sh` | Legacy – self-loopback ADB (needs USB) | Mar 21 |\n| `flash-photo-v2.sh` | Early ADB input method | Mar 21 |\n| `set-flash-on.sh` | One-time: set Huawei camera flash to \"On\" | Mar 21 |\n| `flash-photo.sh` | Original torch+camera attempt | Feb 11 |\n| `photo_shoot.py` | Python torch+camera wrapper | Feb 11 |\n| `photo_shoot_v2.py` | Improved Python with wake lock | Feb 11 |\n| `timelapse.sh` | Original loop-based timelapse (replaced by cron) | Feb 6 |\n\n---\n\n## How it works (technical)\n\nThe APK contains a single exported `BroadcastReceiver` (`FlashReceiver`) that:\n\n1. Receives `com.flashphoto.TAKE` broadcast with `file` extra (output path)\n2. Calls `goAsync()` to extend receiver lifetime beyond 10 seconds\n3. Opens Camera2 on a background `HandlerThread`\n4. Creates a capture session with **only an `ImageReader`** surface (no\n   `SurfaceTexture` – avoids OpenGL dependency that fails with screen off)\n5. Runs repeating preview requests with `FLASH_MODE_TORCH` on the JPEG\n   ImageReader for 2 seconds (LED on, AE converges; preview frames discarded)\n6. **Waits for autofocus lock** before saving (or uses manual focus distance) –\n   sends `AF_TRIGGER_START`, monitors `CONTROL_AF_STATE` for `FOCUSED_LOCKED`,\n   falls back to saving anyway after a configurable timeout\n7. Saves a TORCH-lit preview frame directly – **no `STILL_CAPTURE`**\n   (the Huawei HAL kills the torch when a still capture request fires)\n8. `ImageReader.OnImageAvailableListener` saves the JPEG and cleans up\n\nOptional broadcast extras for tuning:\n- `--ei warmup \u003cms\u003e` – torch warmup before AF lock (default: 2000)\n- `--ei skip \u003cN\u003e` – preview frames to skip after warmup (manual focus only, default: 5)\n- `--ei iso \u003cvalue\u003e` – manual ISO sensitivity (0 = auto, try 400–1600)\n- `--ei exposure_ms \u003cvalue\u003e` – manual exposure time in ms (0 = auto, try 33–100)\n- `--ef focus_diopters \u003cfloat\u003e` – manual focus distance in diopters (default: -1 = autofocus).\n  Examples: `2.0` for ~50 cm, `3.3` for ~30 cm, `1.0` for ~1 m, `0.0` for infinity.\n  Best for fixed-mount setups where the distance to the subject doesn't change.\n- `--ei af_timeout \u003cms\u003e` – AF lock timeout before saving anyway (default: 3000).\n  If autofocus can't converge within this time, the best available frame is saved\n  rather than losing the shot.\n\n**Production defaults:** ISO 800 + 100 ms exposure. Auto-exposure fails in\nlarge dark rooms because the AE algorithm sees mostly darkness and\nunderexposes despite the torch being on. See [EXPERIMENTS.md] for the\nfull tuning data.\n\n#### Focus modes\n\n**Autofocus (default):** After the torch warmup period, the receiver triggers\nan explicit AF lock (`AF_TRIGGER_START`) and waits for `FOCUSED_LOCKED` or\n`NOT_FOCUSED_LOCKED` before saving. Every frame logs AF state, focus distance,\nand lens state via `logcat -s FlashPhoto` for diagnostics. If the AF lock\ndoesn't converge within `af_timeout` ms, the frame is saved anyway.\n\n**Manual focus (`--ef focus_diopters \u003cvalue\u003e`):** Sets `CONTROL_AF_MODE_OFF`\nand a fixed `LENS_FOCUS_DISTANCE`. Ideal for a phone mounted at a known\ndistance from gauges – eliminates AF hunting entirely. After warmup, skips\n`skip` frames to let the lens settle, then saves. To calibrate: take a test\nshot with autofocus enabled, check the logged focus distance, then use that\nvalue for subsequent shots.\n\n### Key design decisions\n\n- **TORCH instead of ALWAYS_FLASH** – the EMUI HAL ignores standard flash\n  modes but honors torch mode (shared code path with the flashlight feature)\n- **Preview-frame save instead of STILL_CAPTURE** – the HAL kills the torch\n  when `TEMPLATE_STILL_CAPTURE` fires; saving a TORCH-lit preview frame from\n  the JPEG ImageReader avoids this entirely (see [EXPERIMENTS.md])\n- **AF lock before save** – `CONTINUOUS_PICTURE` AF can be mid-hunt when a\n  frame is captured; triggering `AF_TRIGGER_START` and waiting for\n  `FOCUSED_LOCKED` ensures the lens has converged before saving\n- **Manual focus option** – for fixed-mount setups, `CONTROL_AF_MODE_OFF` with\n  a calibrated `LENS_FOCUS_DISTANCE` eliminates AF hunting entirely\n- **Save on AF timeout** – if the HAL doesn't report AF convergence (broken\n  state reporting or genuinely unfocusable scene), the frame is saved anyway\n  after a timeout rather than losing the shot\n- **No SurfaceTexture** – fails when screen is off due to missing OpenGL\n  context; `ImageReader`-only sessions work headlessly\n- **`goAsync()`** – BroadcastReceivers normally have a 10-second limit; this\n  extends it for the full capture sequence (~5–8 seconds with AF lock)\n\n[EXPERIMENTS.md]: EXPERIMENTS.md\n\n### What failed and why\n\n| Approach | Failure mode |\n|----------|-------------|\n| `termux-torch` + `termux-camera-photo` | Camera2 session takes exclusive hardware control, kills torch |\n| Custom Java via `app_process` | Android kills unregistered processes (SIGKILL) |\n| `dalvikvm` with framework classpath | Native libs unavailable outside app_process |\n| Patching Termux:API smali | Shared user ID requires matching APK signature |\n| On-device APK build (ecj/dx/aapt) | aapt SDK version mismatch with device framework |\n| Camera2 `CONTROL_AE_MODE_ON_ALWAYS_FLASH` | EMUI HAL ignores; reports FIRED but doesn't fire |\n| Camera2 `FLASH_MODE_SINGLE` | Same – HAL ignores |\n| `/system/bin/input` from Termux | Needs shell UID; SIGKILL under app UID |\n| Write to `/dev/input/eventN` | Permission denied – not in input group |\n| SurfaceTexture for preview | Needs OpenGL context; fails with screen off |\n\n---\n\n## Future work\n\n### Camera pipeline status\n\n- [x] Flash-illuminated autonomous capture from Termux via broadcast-triggered APK\n- [x] Production exposure tuning for the dark mechanical room (`ISO 800`, `100 ms`)\n- [x] Focus reliability improvements via AF lock, manual focus override, and diagnostics\n- [x] Disable Huawei background killers (`com.huawei.powergenie`, `com.huawei.android.hwaps`)\n- [ ] Set up photo retrieval pipeline (SCP/rsync from phone to server)\n- [ ] Verify over a longer unattended run that cron, Dropbear, and the camera app stay stable\n\n### Gauge reading (next phase)\n\nThe phone will be pointed at the house mechanical room gauges:\n\n- **Cold water meter** – cumulative m³ consumption\n- **Hot water meter** – cumulative m³ consumption\n- **Hydronic heating pressure gauge** – bar, analog dial\n- **Hydronic heating temperature gauge** – °C, analog dial\n\nPlanned work:\n\n- [ ] Mount phone in a fixed position with gauges in frame\n- [ ] Develop image segmentation to locate each gauge dial in the photo\n- [ ] Train or use OCR/CV model to read analog gauge needle positions\n- [ ] Log gauge readings over time (database or CSV)\n- [ ] Alert on anomalies (sudden consumption, pressure drop, temperature spike)\n- [ ] Dashboard for historical trends\n\n---\n\n## Agent session cost\n\nToken counts and costs are from the Anthropic API usage logs embedded in each\nsession file. Three agent platforms were used across the project's lifetime.\n\n### Phase 1: OpenClaw agent on gogo (February 5–7, 2026)\n\nThe OpenClaw agent (\"Clawd\", later Tyko) ran on gogo, accessed via Telegram.\nSessions stored in `/home/agent/.openclaw/agents/clawd/sessions/`.\n\n| Session | Focus | Turns | Output tokens | Cache (read+write) | Cost |\n|---------|-------|------:|-------------:|-----------:|-----:|\n| `d16c0a1c` | Research: 5 Android camera approaches | 161 | 33,777 | 9.9M + 1.1M | $10.09 |\n| `46214d5a` | Phone setup, Termux, SSH, ADB | 38 | 13,035 | 2.8M + 1.5M | $5.71 |\n| `58f429ac` | android-periodic-photos.md docs | 26 | 7,000 | 674K + 186K | $1.38 |\n| `07ccd2b0` | Timelapse scripts, boot config | 474 | 98,037 | 41.5M + 3.2M | $39.05 |\n| 5 smaller | ADB tricks, cleanup, migration | 89 | 17,986 | 1.6M + 676K | $2.90 |\n| **Subtotal** | | **788** | **169,835** | **56.5M + 6.7M** | **$59.14** |\n\n### Phase 2: Pi agent on atom (March 21–26, 2026)\n\nPi sessions on atom. Early sessions used mostly Claude Opus 4.6 and partly\nClaude Sonnet 4.6; the later tuning/focus sessions are recorded as Claude Opus\n4.6 in the Pi logs. Session logs in `~/.pi/agent/sessions/`.\n\n| Session | Focus | Turns | Output tokens | Cache (read+write) | Cost |\n|---------|-------|------:|-------------:|-----------:|-----:|\n| `9c4eed4e` | Exploration, 6 failed approaches, first ADB flash | 213 | 79,753 | 25.1M + 799K | $19.54 |\n| `7138154e` | Brief intermediate | 6 | 1,087 | 120K + 39K | $0.33 |\n| `12acdef4` | Self-loopback ADB, production script | 210 | 60,025 | 20.0M + 254K | $13.11 |\n| `d98b9bef` | FlashPhoto APK, TORCH discovery, cron setup | 178 | 95,561 | 20.6M + 812K | $17.74 |\n| `1df67ec4` | Production tuning, manual exposure docs, PowerGenie removal, gauge-reader seed repo | 290 | 76,678 | 33.7M + 1.6M | $30.37 |\n| `37bfb929` | Clarify and reword Finnish/English project narrative | 40 | 9,030 | ~0 + ~0* | $19.97 |\n| `e132d2bf` | Focus diagnosis, AF lock/manual focus implementation, build, deploy, test | 97 | 32,033 | 6.8M + 169K | $5.27 |\n| **Subtotal** | | **1,034** | **354,167** | **106.2M + 3.7M** | **$106.33** |\n\n\\* Pi logged zero cache tokens for `37bfb929`, but the billed total is still\npresent in the session usage metadata.\n\n### Grand total\n\n| | Turns | Output tokens | Cache (read+write) | Cost |\n|---|------:|-------------:|-----------:|-----:|\n| **All 16 sessions** | **1,822** | **524,002** | **162.7M + 10.4M** | **$165.47** |\n\nThe high cache-read volume reflects the iterative nature of the work: each API\nturn re-sends the full conversation context (including tool results from SSH\ncommands, logcat output, Java source, and build logs) which is served from\nAnthropic's prompt cache.\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakaihola%2Fflashphoto-android7","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakaihola%2Fflashphoto-android7","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakaihola%2Fflashphoto-android7/lists"}