{"id":95,"date":"2026-05-06T16:13:31","date_gmt":"2026-05-06T20:13:31","guid":{"rendered":"https:\/\/oerinet.net\/blog\/?p=95"},"modified":"2026-05-06T16:55:54","modified_gmt":"2026-05-06T20:55:54","slug":"95-2","status":"publish","type":"post","link":"https:\/\/oerinet.net\/blog\/95-2\/","title":{"rendered":"Flashing OpenIPC on the Anpiz IPC-D3B53W-S"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The Anpiz IPC-D3B53W-S is a cheap Amazon dome camera that turns out to be a surprisingly capable piece of hardware once you replace the stock firmware with <a href=\"https:\/\/openipc.org\/\">OpenIPC<\/a>. Under the hood it runs a <strong>SigmaStar SSC377 (infinity6c)<\/strong> SoC with a <strong>SC4336P<\/strong> image sensor, capable of 2560&#215;1440 at 30fps. Here&#8217;s everything you need to get it running.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What You&#8217;ll Need<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Anpiz IPC-D3B53W-S camera<\/li>\n\n\n\n<li><strong>CH341A programmer<\/strong> and a flash chip clamp (SOIC-8)<\/li>\n\n\n\n<li>A PC with <a href=\"https:\/\/www.flashrom.org\/\">flashrom<\/a> installed<\/li>\n\n\n\n<li>OpenIPC 16MB Lite image for SSC377 \u2014 download from <a href=\"https:\/\/openipc.org\/cameras\/vendors\/sigmastar\/socs\/ssc377\">openipc.org<\/a><\/li>\n\n\n\n<li>SSH client and a network cable<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1 \u2014 Flash the Firmware<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The camera uses a <strong>GD25Q128C<\/strong> 16MB SPI NOR flash chip on the PCB. You do not need to desolder it \u2014 a SOIC-8 chip clamp works fine.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Disassemble the camera and locate the flash chip on the main board<\/li>\n\n\n\n<li>Clip the CH341A clamp onto the chip (<strong>power the camera off first<\/strong>)<\/li>\n\n\n\n<li>Connect the CH341A to your PC and flash the OpenIPC image:<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>flashrom -p ch341a_spi -w openipc-ssc377-lite-16mb.bin\n<\/code><\/pre>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li>Verify the write completes without errors<\/li>\n\n\n\n<li>Remove the clamp, reassemble, and power the camera on<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The camera should come up on your network via DHCP within about 30 seconds. Set a root password in the Web UI, then ssh in as root.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2 \u2014 Copy the IQ Tuning File<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The stock firmware ships with a sensor-specific <strong>image quality binary<\/strong> (<code>sc4336p_day.bin<\/code>) that contains the color correction matrix, white balance tables, and gamma curves tuned for this exact sensor. Without it, colors look washed out and flat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Extract the file from the original firmware (or <a href=\"\/sc4336p_day.bin\" data-type=\"link\" data-id=\"\/sc4336p_day.bin\">download my copy here<\/a>) and copy it to the camera. The easiest method if you only have SSH access (no SCP) is base64:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>On your PC:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>base64 sc4336p_day.bin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>On the camera via SSH:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>base64 -d &gt; \/etc\/sensors\/sc4336p.bin &lt;&lt; 'EOF'\n&lt;paste base64 output here&gt;\nEOF\n\n# Verify the file size (should be 111620 bytes)\nwc -c \/etc\/sensors\/sc4336p.bin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then tell majestic to use it in <code>\/etc\/majestic.yaml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>isp:\n  iqFile: \/etc\/sensors\/sc4336p.bin\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3 \u2014 Fix the CMA Memory Allocation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Out of the box, OpenIPC only reserves <strong>2MB of CMA memory<\/strong> for the video encoder. The SC4336P at 1920&#215;1080 needs at least 6.2MB, which causes the encoder to fail silently and produce a solid pink image.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Increase the CMA reservation in the boot environment:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fw_setenv bootargs \"console=ttyS0,115200 panic=20 root=\/dev\/mtdblock3 init=\/init mtdparts=NOR_FLASH:256k(boot),64k(env),2048k(kernel),5120k(rootfs),-(rootfs_data) LX_MEM=0x4000000 mma_heap=mma_heap_name0,miu=0,sz=0x2000000 cma=8M\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reboot and verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/proc\/meminfo | grep -i cma\n# CmaTotal: 8192 kB\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4 \u2014 Configure the GPIO Pins (IR Filter and Light)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This was the most involved part of the setup. The stock firmware doesn&#8217;t document its GPIO assignments anywhere obvious, so finding the right pins required extracting and analysing the firmware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key was finding <code>ko.sh<\/code> \u2014 the kernel module startup script embedded in the squashfs of the original firmware \u2014 which explicitly exports and tests the ICR motor pins at boot.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The complete pin map for this camera:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Function<\/th><th>Interface<\/th><th>Pin\/Channel<\/th><\/tr><\/thead><tbody><tr><td>ICR filter \u2014 day coil<\/td><td>GPIO<\/td><td>11<\/td><\/tr><tr><td>ICR filter \u2014 night coil<\/td><td>GPIO<\/td><td>80<\/td><\/tr><tr><td>LED light<\/td><td>GPIO<\/td><td>13<\/td><\/tr><tr><td>IR LED (infrared light)<\/td><td>PWM<\/td><td>pwmchip0\/pwm4<\/td><\/tr><tr><td>White light<\/td><td>PWM<\/td><td>pwmchip0\/pwm0<\/td><\/tr><tr><td>Light sensor input<\/td><td>GPIO<\/td><td>23 (input)<\/td><\/tr><tr><td>Alarm input<\/td><td>GPIO<\/td><td>44 (input)<\/td><\/tr><tr><td>Alarm output<\/td><td>GPIO<\/td><td>61 (output)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The IR cut filter is a <strong>mechanical two-coil motor<\/strong> \u2014 it needs a brief pulse (~250ms) on one pin to switch to night position and a pulse on the other to return to day. Both pins sit at 0 between switches. The IR and white lights are PWM-controlled at a 50kHz period, not simple GPIO on\/off.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Add the ICR pins to <code>\/etc\/majestic.yaml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nightMode:\n  irCutPin1: 11\n  irCutPin2: 80\n  irCutSingleInvert: false\n  lightMonitor: false # doesn't seem to work\n  lightSensorInvert: false\n  backlightPin: 13\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To manually test the filter from the shell:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Switch to day mode\necho 1 &gt; \/sys\/class\/gpio\/gpio80\/value\necho 0 &gt; \/sys\/class\/gpio\/gpio11\/value\nusleep 250000\necho 0 &gt; \/sys\/class\/gpio\/gpio11\/value\necho 0 &gt; \/sys\/class\/gpio\/gpio80\/value\n\n# Switch to night mode\necho 1 &gt; \/sys\/class\/gpio\/gpio11\/value\necho 0 &gt; \/sys\/class\/gpio\/gpio80\/value\nusleep 250000\necho 0 &gt; \/sys\/class\/gpio\/gpio11\/value\necho 0 &gt; \/sys\/class\/gpio\/gpio80\/value\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should hear a faint click each time as the filter moves.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Pink Screen Mystery<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">During setup we ran into a solid pink image with no visible picture at all. It turned out to be two separate issues that happened to occur at the same time:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>CMA too small<\/strong> \u2014 the encoder was failing silently (fixed by the <code>cma=8M<\/code> bootarg above)<\/li>\n\n\n\n<li><strong>Lens not fully seated<\/strong> \u2014 the lens barrel wasn&#8217;t screwed down flush to the sensor PCB, letting ambient light bleed in through the gap from behind and saturating the sensor pink<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">If you see a solid pink image, check both. The CMA fix is in the bootargs. The lens issue is mechanical \u2014 just unscrew the dome cover and tighten the lens down until it&#8217;s flush.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5 \u2014 Automatic Day\/Night Switching<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">OpenIPC&#8217;s built-in night mode appears to have a significant limitation on this camera: enabling <code>lightMonitor: true<\/code> (which uses image brightness to decide when to switch) disables all the night mode buttons in the web UI. And setting <code>lightMonitor: false<\/code> to get the buttons back means automatic switching stops working for some reason. You can&#8217;t have both at the same time. I hope I am wrong &#8211; if so, correct me!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But my workaround is to disable lightMonitor and handle automatic switching yourself with a cron script that uses sunrise\/sunset times for your location. Save the following to <code>\/root\/nightwatch.sh<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/sh\n\nLAT=40.0000\nLON=-85.0000\nSTATE_FILE=\/tmp\/nightmode_state\nCACHE=\/tmp\/sun_times\n\nset_day() {\n    if &#91; \"$(cat $STATE_FILE 2>\/dev\/null)\" != \"day\" ]; then\n        echo \"Switching to DAY mode\"\n        curl -s \"http:\/\/localhost\/night?enabled=0\" > \/dev\/null\n        echo 11 > \/sys\/class\/gpio\/export 2>\/dev\/null\n        echo 80 > \/sys\/class\/gpio\/export 2>\/dev\/null\n        echo out > \/sys\/class\/gpio\/gpio11\/direction 2>\/dev\/null\n        echo out > \/sys\/class\/gpio\/gpio80\/direction 2>\/dev\/null\n        echo 1 > \/sys\/class\/gpio\/gpio80\/value\n        echo 0 > \/sys\/class\/gpio\/gpio11\/value\n        usleep 250000\n        echo 0 > \/sys\/class\/gpio\/gpio11\/value\n        echo 0 > \/sys\/class\/gpio\/gpio80\/value\n        echo day > $STATE_FILE\n    else\n        echo \"Already in DAY mode\"\n    fi\n}\n\nset_night() {\n    if &#91; \"$(cat $STATE_FILE 2>\/dev\/null)\" != \"night\" ]; then\n        echo \"Switching to NIGHT mode\"\n        curl -s \"http:\/\/localhost\/night?enabled=1\" > \/dev\/null\n        echo 11 > \/sys\/class\/gpio\/export 2>\/dev\/null\n        echo 80 > \/sys\/class\/gpio\/export 2>\/dev\/null\n        echo out > \/sys\/class\/gpio\/gpio11\/direction 2>\/dev\/null\n        echo out > \/sys\/class\/gpio\/gpio80\/direction 2>\/dev\/null\n        echo 1 > \/sys\/class\/gpio\/gpio11\/value\n        echo 0 > \/sys\/class\/gpio\/gpio80\/value\n        usleep 250000\n        echo 0 > \/sys\/class\/gpio\/gpio11\/value\n        echo 0 > \/sys\/class\/gpio\/gpio80\/value\n        echo night > $STATE_FILE\n    else\n        echo \"Already in NIGHT mode\"\n    fi\n}\n\niso_to_ts() {\n    DT=$(echo $1 | sed 's\/T\/ \/' | sed 's\/+&#91;0-9:]*$\/\/' | sed 's\/-&#91;0-9]&#91;0-9]:&#91;0-9]&#91;0-9]$\/\/')\n    date -u -d \"$DT\" +%s\n}\n\nis_valid_ts() {\n    &#91; -n \"$1\" ] &amp;&amp; echo \"$1\" | grep -q '^&#91;0-9]*$'\n}\n\nCACHE_AGE=0\nif &#91; -f $CACHE ]; then\n    CACHE_AGE=$(( $(date +%s) - $(date -r $CACHE +%s 2>\/dev\/null || echo 0) ))\nfi\n\nFORCE_FETCH=0\nif &#91; ! -f $CACHE ] || &#91; $CACHE_AGE -gt 86400 ]; then\n    FORCE_FETCH=1\nelse\n    read SUNRISE_TS SUNSET_TS &lt; $CACHE\n    if ! is_valid_ts \"$SUNRISE_TS\" || ! is_valid_ts \"$SUNSET_TS\"; then\n        echo \"Bad cache detected, clearing\"\n        rm -f $CACHE\n        FORCE_FETCH=1\n    else\n        echo \"Cached: sunrise=$SUNRISE_TS sunset=$SUNSET_TS\"\n    fi\nfi\n\nif &#91; \"$FORCE_FETCH\" = \"1\" ]; then\n    echo \"Fetching sun times from API\"\n    RESPONSE=$(curl -s \"https:\/\/api.sunrise-sunset.org\/json?lat=$LAT&amp;lng=$LON&amp;formatted=0\")\n    SUNRISE=$(echo $RESPONSE | grep -o '\"sunrise\":\"&#91;^\"]*\"' | cut -d'\"' -f4)\n    SUNSET=$(echo $RESPONSE | grep -o '\"sunset\":\"&#91;^\"]*\"' | cut -d'\"' -f4)\n    SUNRISE_TS=$(iso_to_ts \"$SUNRISE\")\n    SUNSET_TS=$(iso_to_ts \"$SUNSET\")\n    if is_valid_ts \"$SUNRISE_TS\" &amp;&amp; is_valid_ts \"$SUNSET_TS\"; then\n        echo \"$SUNRISE_TS $SUNSET_TS\" > $CACHE\n        echo \"Fetched: sunrise=$SUNRISE_TS sunset=$SUNSET_TS\"\n    else\n        echo \"Failed to fetch valid sun times, defaulting to night\"\n        set_night\n        exit 1\n    fi\nfi\n\nNOW=$(date +%s)\necho \"Now=$NOW sunrise=$SUNRISE_TS sunset=$SUNSET_TS\"\n\nif &#91; \"$NOW\" -ge \"$SUNRISE_TS\" ] &amp;&amp; &#91; \"$NOW\" -lt \"$SUNSET_TS\" ]; then\n    set_day\nelse\n    set_night\nfi\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Update <code>LAT<\/code> and <code>LON<\/code> for your location. Make it executable and add it to cron:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x \/root\/nightwatch.sh\necho \"*\/5 * * * * \/root\/nightwatch.sh &gt;&gt; \/tmp\/nightwatch.log 2&gt;&amp;1\" &gt;&gt; \/etc\/crontabs\/root\nkillall crond 2&gt;\/dev\/null\ncrond\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The script caches sunrise\/sunset times for 24 hours (using the free <a href=\"https:\/\/sunrise-sunset.org\/api\">sunrise-sunset.org<\/a> API), only pulses the ICR motor when the state actually needs to change, and fails safely to night mode if the API is unreachable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With this approach, set <code>lightMonitor: false<\/code> in majestic.yaml so the web UI buttons remain functional for manual overrides.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Final majestic.yaml<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s a working configuration for reference:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>system:\n  webPort: 80\nisp:\n  iqFile: \/etc\/sensors\/sc4336p.bin\n  antiFlicker: disabled\nimage:\n  mirror: false\n  flip: false\n  rotate: 0\n  contrast: 50\n  hue: 50\n  saturation: 50\n  luminance: 50\nvideo0:\n  enabled: true\n  codec: h264\n  size: 1920x1080\n  fps: 20\n  bitrate: 4096\n  rcMode: vbr\n  gopSize: 1\nvideo1:\n  enabled: false\nnightMode:\n  colorToGray: true\n  irCutPin1: 11\n  irCutPin2: 80\n  irCutSingleInvert: false\n  lightMonitor: false\n  lightSensorInvert: false\n  backlightPin: 0\nrtsp:\n  enabled: true\n  port: 554\nwatchdog:\n  enabled: true\n  timeout: 300\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Anpiz IPC-D3B53W-S is a solid candidate for OpenIPC. The hardware is capable, the sensor driver is already supported upstream, and once the IQ file, CMA, and GPIO pins are sorted it runs well. The main gotchas are the CMA allocation (easy fix), finding the correct ICR GPIO pins (GPIO 11 and 80, not obvious without digging into the original firmware), and making sure the lens is properly seated after reassembly.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Anpiz IPC-D3B53W-S is a cheap Amazon dome camera that turns out to be a surprisingly capable piece of hardware once you replace the stock firmware with OpenIPC. Under the hood it runs a SigmaStar SSC377 (infinity6c) SoC with a SC4336P image sensor, capable of 2560&#215;1440 at 30fps. Here&#8217;s everything you need to get it &hellip; <a href=\"https:\/\/oerinet.net\/blog\/95-2\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Flashing OpenIPC on the Anpiz IPC-D3B53W-S&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-95","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/posts\/95","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/comments?post=95"}],"version-history":[{"count":3,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/posts\/95\/revisions"}],"predecessor-version":[{"id":98,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/posts\/95\/revisions\/98"}],"wp:attachment":[{"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/media?parent=95"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/categories?post=95"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oerinet.net\/blog\/wp-json\/wp\/v2\/tags?post=95"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}