Flashing OpenIPC on the Anpiz IPC-D3B53W-S

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×1440 at 30fps. Here’s everything you need to get it running.


What You’ll Need

  • Anpiz IPC-D3B53W-S camera
  • CH341A programmer and a flash chip clamp (SOIC-8)
  • A PC with flashrom installed
  • OpenIPC 16MB Lite image for SSC377 — download from openipc.org
  • SSH client and a network cable

Step 1 — Flash the Firmware

The camera uses a GD25Q128C 16MB SPI NOR flash chip on the PCB. You do not need to desolder it — a SOIC-8 chip clamp works fine.

  1. Disassemble the camera and locate the flash chip on the main board
  2. Clip the CH341A clamp onto the chip (power the camera off first)
  3. Connect the CH341A to your PC and flash the OpenIPC image:
flashrom -p ch341a_spi -w openipc-ssc377-lite-16mb.bin
  1. Verify the write completes without errors
  2. Remove the clamp, reassemble, and power the camera on

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.


Step 2 — Copy the IQ Tuning File

The stock firmware ships with a sensor-specific image quality binary (sc4336p_day.bin) 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.

Extract the file from the original firmware (or download my copy here) and copy it to the camera. The easiest method if you only have SSH access (no SCP) is base64:

On your PC:

base64 sc4336p_day.bin

On the camera via SSH:

base64 -d > /etc/sensors/sc4336p.bin << 'EOF'
<paste base64 output here>
EOF

# Verify the file size (should be 111620 bytes)
wc -c /etc/sensors/sc4336p.bin

Then tell majestic to use it in /etc/majestic.yaml:

isp:
  iqFile: /etc/sensors/sc4336p.bin

Step 3 — Fix the CMA Memory Allocation

Out of the box, OpenIPC only reserves 2MB of CMA memory for the video encoder. The SC4336P at 1920×1080 needs at least 6.2MB, which causes the encoder to fail silently and produce a solid pink image.

Increase the CMA reservation in the boot environment:

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"

Reboot and verify:

cat /proc/meminfo | grep -i cma
# CmaTotal: 8192 kB

Step 4 — Configure the GPIO Pins (IR Filter and Light)

This was the most involved part of the setup. The stock firmware doesn’t document its GPIO assignments anywhere obvious, so finding the right pins required extracting and analysing the firmware.

The key was finding ko.sh — the kernel module startup script embedded in the squashfs of the original firmware — which explicitly exports and tests the ICR motor pins at boot.

The complete pin map for this camera:

FunctionInterfacePin/Channel
ICR filter — day coilGPIO11
ICR filter — night coilGPIO80
LED lightGPIO13
IR LED (infrared light)PWMpwmchip0/pwm4
White lightPWMpwmchip0/pwm0
Light sensor inputGPIO23 (input)
Alarm inputGPIO44 (input)
Alarm outputGPIO61 (output)

The IR cut filter is a mechanical two-coil motor — 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.

Add the ICR pins to /etc/majestic.yaml:

nightMode:
  irCutPin1: 11
  irCutPin2: 80
  irCutSingleInvert: false
  lightMonitor: false # doesn't seem to work
  lightSensorInvert: false
  backlightPin: 13

To manually test the filter from the shell:

# Switch to day mode
echo 1 > /sys/class/gpio/gpio80/value
echo 0 > /sys/class/gpio/gpio11/value
usleep 250000
echo 0 > /sys/class/gpio/gpio11/value
echo 0 > /sys/class/gpio/gpio80/value

# Switch to night mode
echo 1 > /sys/class/gpio/gpio11/value
echo 0 > /sys/class/gpio/gpio80/value
usleep 250000
echo 0 > /sys/class/gpio/gpio11/value
echo 0 > /sys/class/gpio/gpio80/value

You should hear a faint click each time as the filter moves.


The Pink Screen Mystery

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:

  1. CMA too small — the encoder was failing silently (fixed by the cma=8M bootarg above)
  2. Lens not fully seated — the lens barrel wasn’t screwed down flush to the sensor PCB, letting ambient light bleed in through the gap from behind and saturating the sensor pink

If you see a solid pink image, check both. The CMA fix is in the bootargs. The lens issue is mechanical — just unscrew the dome cover and tighten the lens down until it’s flush.


Step 5 — Automatic Day/Night Switching

OpenIPC’s built-in night mode appears to have a significant limitation on this camera: enabling lightMonitor: true (which uses image brightness to decide when to switch) disables all the night mode buttons in the web UI. And setting lightMonitor: false to get the buttons back means automatic switching stops working for some reason. You can’t have both at the same time. I hope I am wrong – if so, correct me!

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 /root/nightwatch.sh:

#!/bin/sh

LAT=40.0000
LON=-85.0000
STATE_FILE=/tmp/nightmode_state
CACHE=/tmp/sun_times

set_day() {
    if [ "$(cat $STATE_FILE 2>/dev/null)" != "day" ]; then
        echo "Switching to DAY mode"
        curl -s "http://localhost/night?enabled=0" > /dev/null
        echo 11 > /sys/class/gpio/export 2>/dev/null
        echo 80 > /sys/class/gpio/export 2>/dev/null
        echo out > /sys/class/gpio/gpio11/direction 2>/dev/null
        echo out > /sys/class/gpio/gpio80/direction 2>/dev/null
        echo 1 > /sys/class/gpio/gpio80/value
        echo 0 > /sys/class/gpio/gpio11/value
        usleep 250000
        echo 0 > /sys/class/gpio/gpio11/value
        echo 0 > /sys/class/gpio/gpio80/value
        echo day > $STATE_FILE
    else
        echo "Already in DAY mode"
    fi
}

set_night() {
    if [ "$(cat $STATE_FILE 2>/dev/null)" != "night" ]; then
        echo "Switching to NIGHT mode"
        curl -s "http://localhost/night?enabled=1" > /dev/null
        echo 11 > /sys/class/gpio/export 2>/dev/null
        echo 80 > /sys/class/gpio/export 2>/dev/null
        echo out > /sys/class/gpio/gpio11/direction 2>/dev/null
        echo out > /sys/class/gpio/gpio80/direction 2>/dev/null
        echo 1 > /sys/class/gpio/gpio11/value
        echo 0 > /sys/class/gpio/gpio80/value
        usleep 250000
        echo 0 > /sys/class/gpio/gpio11/value
        echo 0 > /sys/class/gpio/gpio80/value
        echo night > $STATE_FILE
    else
        echo "Already in NIGHT mode"
    fi
}

iso_to_ts() {
    DT=$(echo $1 | sed 's/T/ /' | sed 's/+[0-9:]*$//' | sed 's/-[0-9][0-9]:[0-9][0-9]$//')
    date -u -d "$DT" +%s
}

is_valid_ts() {
    [ -n "$1" ] && echo "$1" | grep -q '^[0-9]*$'
}

CACHE_AGE=0
if [ -f $CACHE ]; then
    CACHE_AGE=$(( $(date +%s) - $(date -r $CACHE +%s 2>/dev/null || echo 0) ))
fi

FORCE_FETCH=0
if [ ! -f $CACHE ] || [ $CACHE_AGE -gt 86400 ]; then
    FORCE_FETCH=1
else
    read SUNRISE_TS SUNSET_TS < $CACHE
    if ! is_valid_ts "$SUNRISE_TS" || ! is_valid_ts "$SUNSET_TS"; then
        echo "Bad cache detected, clearing"
        rm -f $CACHE
        FORCE_FETCH=1
    else
        echo "Cached: sunrise=$SUNRISE_TS sunset=$SUNSET_TS"
    fi
fi

if [ "$FORCE_FETCH" = "1" ]; then
    echo "Fetching sun times from API"
    RESPONSE=$(curl -s "https://api.sunrise-sunset.org/json?lat=$LAT&lng=$LON&formatted=0")
    SUNRISE=$(echo $RESPONSE | grep -o '"sunrise":"[^"]*"' | cut -d'"' -f4)
    SUNSET=$(echo $RESPONSE | grep -o '"sunset":"[^"]*"' | cut -d'"' -f4)
    SUNRISE_TS=$(iso_to_ts "$SUNRISE")
    SUNSET_TS=$(iso_to_ts "$SUNSET")
    if is_valid_ts "$SUNRISE_TS" && is_valid_ts "$SUNSET_TS"; then
        echo "$SUNRISE_TS $SUNSET_TS" > $CACHE
        echo "Fetched: sunrise=$SUNRISE_TS sunset=$SUNSET_TS"
    else
        echo "Failed to fetch valid sun times, defaulting to night"
        set_night
        exit 1
    fi
fi

NOW=$(date +%s)
echo "Now=$NOW sunrise=$SUNRISE_TS sunset=$SUNSET_TS"

if [ "$NOW" -ge "$SUNRISE_TS" ] && [ "$NOW" -lt "$SUNSET_TS" ]; then
    set_day
else
    set_night
fi

Update LAT and LON for your location. Make it executable and add it to cron:

chmod +x /root/nightwatch.sh
echo "*/5 * * * * /root/nightwatch.sh >> /tmp/nightwatch.log 2>&1" >> /etc/crontabs/root
killall crond 2>/dev/null
crond

The script caches sunrise/sunset times for 24 hours (using the free sunrise-sunset.org API), only pulses the ICR motor when the state actually needs to change, and fails safely to night mode if the API is unreachable.

With this approach, set lightMonitor: false in majestic.yaml so the web UI buttons remain functional for manual overrides.


Final majestic.yaml

Here’s a working configuration for reference:

system:
  webPort: 80
isp:
  iqFile: /etc/sensors/sc4336p.bin
  antiFlicker: disabled
image:
  mirror: false
  flip: false
  rotate: 0
  contrast: 50
  hue: 50
  saturation: 50
  luminance: 50
video0:
  enabled: true
  codec: h264
  size: 1920x1080
  fps: 20
  bitrate: 4096
  rcMode: vbr
  gopSize: 1
video1:
  enabled: false
nightMode:
  colorToGray: true
  irCutPin1: 11
  irCutPin2: 80
  irCutSingleInvert: false
  lightMonitor: false
  lightSensorInvert: false
  backlightPin: 0
rtsp:
  enabled: true
  port: 554
watchdog:
  enabled: true
  timeout: 300

Summary

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.