{"id":603,"date":"2026-10-20T10:23:40","date_gmt":"2026-10-20T09:23:40","guid":{"rendered":"https:\/\/www.fabidouille.com\/?p=603"},"modified":"2026-10-20T10:23:40","modified_gmt":"2026-10-20T09:23:40","slug":"au-doigt-et-a-l-oeil","status":"publish","type":"post","link":"https:\/\/www.fabidouille.com\/?p=603","title":{"rendered":"Au Doigt et \u00e0 l&rsquo;\u0152il: teaching a Chinese action cam to press its own OK button"},"content":{"rendered":"<p>02:09, 02:25, 02:35, 02:46. Four emails from my print monitor: \u00ab\u00a0failed print\u00a0\u00bb. Then nothing. The printer did not stop. It made spaghetti until the morning, and I cancelled at 78 %.<\/p>\n<p>The monitor scores a camera frame every 8 seconds with an open source failure model (the one from Obico). It can send an email, but it cannot pause the printer: I never turned that on. And when its camera has a bad day, it still says \u00ab\u00a0healthy\u00a0\u00bb. I wanted a second camera, with its own opinion.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/failed-print.jpg\" alt=\"A 3D printer bed with a failed print: a thin outline of a part and many tangled white strings, with two cameras at the top\"\/><figcaption>A failed print, the day before: a three-way switch for a toy train track, made mostly of strings.<\/figcaption><\/figure>\n<p>My only camera is a Yi. On a good day it sees the bed in grey. After a power cut, in purple.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/yi-views.jpg\" alt=\"Two frames of the printer bed from the same camera: one in grey, one fully purple\"\/><figcaption>My current camera. Left: a normal day. Right: after a power cut.<\/figcaption><\/figure>\n<p>In a drawer I had two forgotten cameras: a JooVuu X, and a Chinese action cam, the AT-03, with \u00ab\u00a04K\u00a0\u00bb and \u00ab\u00a0Wi-Fi\u00a0\u00bb printed on the case.<\/p>\n<h2 class=\"wp-block-heading\">One cable<\/h2>\n<p>Both cameras have Wi-Fi and a micro-USB port. I own many micro-USB cables and exactly one that carries data. I tested all the others, one by one, with hope. The ratio did not change.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/at03-front.jpg\" alt=\"The front of the AT-03 action cam: a lens, a round button, and the text 4K and Wi-Fi printed on the black case\"\/><figcaption>The AT-03. Its sensor is an IMX179 (8 megapixels) and the videos on its card are 1080p. &lsquo;4K&rsquo; is a matter of opinion.<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Android, in 16 MB<\/h2>\n<p>Plugged into my PC, the AT-03 shows a menu: USB mode, camera mode, charging mode. In USB mode it is a USB stick, plus <code>adb<\/code>. And <code>adb<\/code> gives a root shell: Android 4.2.2, an \u00ab\u00a0eng\u00a0\u00bb build, kernel 3.4.39, an Allwinner V3 chip.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/screens.jpg\" alt=\"Left: the AT-03 screen with the menu Mode USB, Mode cam\u00e9ra, Mode chargement. Right: the firmware information screen: model AT-03, version 20160927V4.0\"\/><figcaption>Left: the menu, at every USB plug. Right: the firmware info. Model AT-03, version 20160927V4.0.<\/figcaption><\/figure>\n<p>Before touching anything, a backup: the 8 partitions of the flash (12 MB), with SHA-256 hashes, on two machines.<\/p>\n<p>The whole camera is one program, <code>sdv<\/code>: screen, video, buttons, USB. In its strings I found the USB modes:<\/p>\n<ul class=\"wp-block-list\">\n<li><code>mass_storage,adb<\/code>: a USB stick (1f3a:1002), the default<\/li>\n<li><code>webcam,adb<\/code>: a webcam (1f3a:100e)<\/li>\n<\/ul>\n<p>A webcam mode! The first test was easy: <code>setprop sys.usb.config webcam,adb<\/code>. The camera appears as a UVC webcam, with one format: MJPEG, 1280&#215;720, 30 fps. And then, no frame. Every control request times out (error -110).<\/p>\n<h2 class=\"wp-block-heading\">Why it stays silent<\/h2>\n<p>I asked my AI agent to look inside <code>sdv<\/code> with Ghidra. The string <code>webcam,adb<\/code> is used in one small function that sets the property and returns. The function that really starts the video is <code>startPCCam<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code>void FUN_00036c28(int param_1,undefined4 param_2)   \/\/ startPCCam\n{\n  __android_log_print(2,\"MainWindow.cpp\",\n                      \"&lt;line[%04d] %s()&gt; +++++++++[debug_jaosn]:this is start pc cam+++++++++\\n\",\n                      0xc7f,\"startPCCam\",param_2);\n  *(undefined1 *)(param_1 + 0x6c) = 1;                  \/\/ the \"PC camera\" flag\n  ...\n  FUN_0003b914(*(undefined4 *)(param_1 + 0x20));        \/\/ property_set(\"sys.usb.config\",\"webcam,adb\")\n  return;\n}<\/code><\/pre>\n<p>(The log message says \u00ab\u00a0debug_jaosn\u00a0\u00bb. Thanks, Jaosn.)<\/p>\n<p>Only one thing calls it: <code>connectToPCCallback<\/code>, the callback of the on-screen menu. No hidden switch. To get a webcam, a human must press DOWN, then OK, every time the camera starts. And if nobody reads the video, the camera gives up after a few seconds and goes back to being a USB stick.<\/p>\n<h2 class=\"wp-block-heading\">The JooVuu detour<\/h2>\n<p>The JooVuu has no USB data at all, only Wi-Fi, and its Wi-Fi did not start for a day. A config file on the SD card, a QR code on my screen, a test access point on my PC: nothing. What worked in the end: normal mode (green blinking LED), then hold the M button for 4 seconds. A blue LED, a network, and a video stream: 432&#215;240, UDP only. Then it switched itself off, for a reason I never found.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/joovuu-frame.jpg\" alt=\"A very low resolution frame from the JooVuu: a wooden staircase and coats on a wall\"\/><figcaption>The best the JooVuu can do: 432&#215;240.<\/figcaption><\/figure>\n<p>The AT-03 has Wi-Fi too. Its own network is called \u00ab\u00a0Sport-cameras\u00a0\u00bb, and the password is on the screen.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/wifi-screen.jpg\" alt=\"The AT-03 screen showing Informations WIFI: network name Sport-cameras, password 123456789\"\/><figcaption>The AT-03&rsquo;s own Wi-Fi. The password is 123456789.<\/figcaption><\/figure>\n<p>Nothing streams on it: the RTSP library is in the firmware, but nothing loads it. So the cable stays.<\/p>\n<h2 class=\"wp-block-heading\">The brick<\/h2>\n<p>I wanted the AT-03 to enter webcam mode alone. The plan: change <code>sdv<\/code> so the \u00ab\u00a0PC camera\u00a0\u00bb flag starts at 1. A first test, with a patched copy mounted over <code>\/system\/bin\/sdv<\/code> (RAM only), proved nothing: when <code>sdv<\/code> stops, the camera reboots, and the reboot erases the mount.<\/p>\n<p>My agent warned me twice: if a write is interrupted, the camera may not boot again, and the recovery method was untested. I said yes to the risk. Then I said: \u00ab\u00a0continue the firmware route, with more tenacity\u00a0\u00bb. It did.<\/p>\n<p>It wrote the <code>system<\/code> partition block by block, over <code>adb<\/code>, while Android was running. The write stopped around block 9. Half old, half new. The agent \u00ab\u00a0restored\u00a0\u00bb it with an <code>adb push<\/code> of the full original image, and the readback hash matched. Next boot: the screen stays on WELCOME. No USB, no <code>adb<\/code>, nothing. (Its analysis was wrong too: it had read a routine as Thumb code.)<\/p>\n<p>Its usage limit ran out a few minutes later. Pas de bol.<\/p>\n<p>The agent&rsquo;s first recovery idea: a copy of the camera&rsquo;s normal bootloader on the microSD card. A very elaborate way to start the same broken system. Still WELCOME.<\/p>\n<h2 class=\"wp-block-heading\">FEL, the back door<\/h2>\n<p>Allwinner chips have a boot ROM that cannot be erased. It has a USB mode called FEL: the chip shows up as <code>1f3a:efe8<\/code>, and a PC can read and write its memory and its flash. The trick is to enter it without opening the camera. With a card inside, the chip starts from the card and not from the flash. So a card with a tiny loader at 8 KB sends the chip straight to FEL. The loader is <code>fel-sdboot<\/code> from the sunxi-tools project: 40 bytes of ARM code that jump to the ROM&rsquo;s FEL entry point.<\/p>\n<p>A second AI agent, in a new session with no memory of the first, wrote this loader on the card. Camera off, card in, USB cable: black screen, and the PC shows <code>1f3a:efe8<\/code>.<\/p>\n<p><code>sunxi-fel<\/code> said: SoC V3s, SPI flash Winbond, 16 MiB. The agent read the whole chip and compared it with my backup. Only six blocks of 64 KiB were different, all in <code>system<\/code>. And <code>adb<\/code> had told me seven, and had told me everything was restored. The most likely reason: <code>adb push<\/code> to a <code>\/dev\/block\/...<\/code> path replaced the device file with a normal file in RAM. My \u00ab\u00a0restore\u00a0\u00bb went to RAM, and the readback read RAM.<\/p>\n<p>Six blocks written through FEL, the whole chip read back, the SHA-256 of <code>system<\/code> identical to the original. The camera booted and showed its menu. If you try something like this: never write a mounted partition, and never <code>adb push<\/code> to a block device path.<\/p>\n<h2 class=\"wp-block-heading\">A finger made of software<\/h2>\n<p>Now the real goal: nobody at boot. The camera starts, the menu appears, DOWN, OK.<\/p>\n<p>My first agent had an idea: a servo (or a solenoid) glued in front of the two buttons, driven by the Raspberry Pi. A robot finger, to press the buttons of a camera that watches a robot. I said no. Software only.<\/p>\n<p>The buttons are plain Linux input devices. I pressed them while the agent listened on <code>\/dev\/input\/event0<\/code>: the side button sends KEY_VOLUMEUP (115), the round button sends KEY_MENU (139). DOWN and OK.<\/p>\n<p>The second agent left the restored system alone and added a small static ARM program, <code>at03-autocam<\/code>, started at boot by a one-shot service in the boot partition (<code>init.sun8i.rc<\/code>). Only five blocks of the boot partition changed.<\/p>\n<pre class=\"wp-block-code\"><code>service at03_autocam \/data\/at03-autocam --autostart\n    class main\n    user root\n    group root\n    oneshot<\/code><\/pre>\n<p>What the program does:<\/p>\n<ol class=\"wp-block-list\">\n<li>It waits until the stock app says that its USB selector is open (it reads the Android log).<\/li>\n<li>It waits 500 ms.<\/li>\n<li>It writes DOWN, then OK, to <code>\/dev\/input\/event0<\/code>: key 115, then key 139.<\/li>\n<li>It checks <code>\/sys\/class\/android_usb\/android0\/functions<\/code>. Webcam? Done. It never retries.<\/li>\n<\/ol>\n<p>One detail that took a test to learn: if the screen is asleep, the first key only wakes it. So when the display turns off, the program goes back to waiting.<\/p>\n<p>The camera&rsquo;s own log of a cold boot, without a microSD card and without a finger:<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/logcat.png\" alt=\"Terminal output filtered from the camera log: the stock app opens the connect2Pc dialog, the AT03AutoCam helper sends one DOWN\/OK sequence, the app opens PCCamMode and starts the PC camera, then the video device appears\"\/><figcaption>The AT03AutoCam lines are the helper. The other lines are the stock app, obeying.<\/figcaption><\/figure>\n<p>Two cold boots in a row: the camera announced itself as a webcam, and a fresh 1280&#215;720 frame arrived on my server 8 seconds later. The timestamps moved and the image changed (a frozen frame would also look like success).<\/p>\n<h2 class=\"wp-block-heading\">Where it stands<\/h2>\n<p>The first frame this camera ever gave my computer: the underside of my desk, and a page of a book.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/first-frame.jpg\" alt=\"A tilted frame from the AT-03 showing the underside of a wooden desk with cables, and a book page in the foreground\"\/><figcaption>The first frame: the underside of my desk.<\/figcaption><\/figure>\n<p>Today the AT-03 is plugged into the Raspberry Pi of the printer, the only place my data cable can reach. The Pi relays the video, and my home server saves one snapshot per second for the detector, which uses it only when it is fresh.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.fabidouille.com\/wp-content\/uploads\/2026\/10\/on-the-pi.jpg\" alt=\"A dark frame from the AT-03 near the printer: the print head with a very bright lamp and its reflection on the bed\"\/><figcaption>A frame from the AT-03 on the Pi: the print head and a lamp. (I cropped myself out.)<\/figcaption><\/figure>\n<p>There is no lithium battery inside: the camera sits next to a 60 \u00b0C bed and a 210 \u00b0C nozzle, so it runs on USB power only.<\/p>\n<p>Still to do: framing, light, and a real test with the model. The AT-03 stays disabled in the detector until it passes. And the \u00ab\u00a0auto pause\u00a0\u00bb is still off.<\/p>\n<h2 class=\"wp-block-heading\">How AI was used<\/h2>\n<p>Two AI agents did most of the hands-on work.<\/p>\n<p>The first one (Claude Code) probed the camera with <code>adb<\/code>, backed up the flash, drove Ghidra, found <code>startPCCam<\/code> and built the capture service. It also wrote the partition that killed the camera, after a wrong analysis.<\/p>\n<p>The second one, a new session with no memory of the first, did the recovery through FEL, then wrote the helper, the boot integration, the tests and a review of the boot image.<\/p>\n<p>My part: owning exactly one data cable, pressing buttons and reading screens out loud (green, red, blue, WELCOME), refusing the robot finger, saying \u00ab\u00a0continue\u00a0\u00bb when I should have said \u00ab\u00a0stop\u00a0\u00bb, and deciding that the camera goes on the Pi, with no battery.<\/p>\n<p>Note: I don&rsquo;t have much time for the blog these days, so this post was written with the help of an AI, from my own notes, then read and corrected by me. Better to share the content this way than not at all.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A cheap Chinese action cam with a hidden Android, a bad flash that bricked it, a 40-byte loader that brought it back, and a software finger that presses OK at boot.<\/p>\n","protected":false},"author":0,"featured_media":596,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-603","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-non-classe"],"_links":{"self":[{"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/posts\/603","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=603"}],"version-history":[{"count":1,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/posts\/603\/revisions"}],"predecessor-version":[{"id":604,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/posts\/603\/revisions\/604"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=\/wp\/v2\/media\/596"}],"wp:attachment":[{"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=603"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=603"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fabidouille.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=603"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}