Summary

Currently there appears to be a lack of options for home cameras that meet my current needs/expectations. As a result I set out to build my own cameras to fill this need. The intent is to build basic home security cameras that meet the following needs:

  • Ethernet
  • POE/POE+
  • A “moderate” or better camera quality (think 1080 or higher resolution)
  • No microphone
  • No PTZ functions
  • No “cloud” functions
  • Frigate compatible

Justifications

The majority of these needs are driven by a need for privacy/security.

No PTZ/Mic

Lacking a PTZ function and microphone ensures should someone access the device they would not be able to hear conversations or rotate the camera to view areas they should not. With a custom solution, in an IOT network lacking NAT, this is not a likely scenario however I do not wish to rely on a camera that needs access to the internet for updates, in my case the RPi image can either be rebuilt or pointed to an internal mirror.

POE/Ethernet

As a general principle I keep things off WiFi if at all possible, especially in this case as this will be streaming video and too many cameras over WiFi I predict would become troublesome quickly. The WiFi in my current location is also relatively congested, with everything from the school nearby to Buicks and Toyotas (apparently, this is new to me) all screaming into the ether.

congestion example

The image above may not accurately reflect the number of devices as many of them overlap and can not be seen.

No “cloud” functions

While I feel like this really should not need a justification I will post two below:

Louis Rossman

China

china

Collecting the materials

As you can already tell from the title I ended up choosing to build my own camera using a raspberry pi. This will fit all of my requirements and I ended up with the following hardware:

Be warned, I have tested two POE+ hats from two different resellers and it appears if the RPi does not draw enough power it will make a horrible high pitched coil like whine. Thankfully its very easy to consume just enough power to stop this, but if the system idles the noise will return.

Assembling the materials

In general the assembly is very obvious but there are a few things to take note of. When connecting the camera to the raspberry pi take note of the direction the ribbon cable is facing. Ensure the side of the cable with blue tape is facing towards the USB ports. See the picture below or checkout the RPi site here:
RPi-cable-connector
Notice how the exposed metal is facing away from the USB ports, and on the opposite side of the “moving” part of the connector.

Building the RPi image

Overall I attempted to build an image with both Armbian and Rocky and in both cases I was unable to get the camera working. I can not find any details on what drivers I could use, or if there was a package, etc there is not a lot of info on the camera drivers. Either way nothing is set in stone so the OS could always be revisited at a later date, the preferred option would be Rocky but after a few nights of testing, it really was time to just push forward and create an “MVP” of sorts. With that out of the way, at this time (2023-12-02) raspbian lite running debian bookworm (12) does not work, so I needed to roll with bullseye instead.

Build the image

As stated above I had issues with pretty much every OS except for rapsbian based on debian 11. So I collected the image from their site, burned the image to my microsd, and got to setting up my image. After doing so I setup my user and the OS like so:

  • Setup OS
    apt update -y
    apt upgrade -y
    apt install ssh zsh libcamera-apps qv4l2 htop git -y
    
  • Setup User
    sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"
    git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
    sed -i 's/robbyrussell/powerlevel10k\/powerlevel10k/g' ~/.zshrc
    

The snippet above is not needed but I do like having zsh with the powerlevel10k theme. I would also copy your ssh key to this image so it will be available on any clones created later.

Setting up the RTSP service

I spent some time looking into possible RTSP applications, mainly VLC, and this ended up becoming cumbersome. I ended up finding MediaMTX (formerly rtsp-simple-server) which appears to fit the bill VERY well. The RTSP server will run as a separate user that we will create and a systemd service.

  • Setup MediaMTX: Below we will download mediamtx and create a user
    cd /tmp/
    wget https://github.com/bluenviron/mediamtx/releases/download/v1.3.1/mediamtx_v1.3.1_linux_arm64v8.tar.gz
    tar -xvf ./mediamtx_v1.3.1_linux_arm64v8.tar.gz
    mv ./mediamtx /bin/
    useradd mediamtx -r -d /srv/mediamtx -s /usr/sbin/nologin -c "Mediamtx service user" -G video
    mkdir /srv/mediamtx
    touch /srv/mediamtx/mediamtx.yml
    chown -R mediamtx:mediamtx /srv/mediamtx
    chmod 400 /srv/mediamtx/mediamtx.yml
    
  • Create service file: Write file to /lib/systemd/system/mediamtx.service
    [Unit]
    Description=Mediamtx RTSP Server
    After=network.target
    
    [Service]
    ExecStart=/bin/mediamtx /srv/mediamtx/mediamtx.yml
    Type=simple
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    
  • Enable the new service: Don’t start the service yet, the config file has not been written yet.
    systemctl daemon-reload
    systemctl enable mediamtx
    
  • Write the config: The config will need to be tweaked to your needs, however the following is what I will be using. The key take away is that I have stripped the majority of the file and its comments below I only need RTSP, and wanted a time overlay. Technically this file can be placed anywhere the new user has access to but in the systemd service file created above I set it to /srv/mediamtx/mediamtx.yml. Make sure to adjust the location in the systemd file if you change this yourself.
    logLevel: info
    logDestinations: [stdout]
    readTimeout: 10s
    writeTimeout: 10s
    writeQueueSize: 512
    udpMaxPayloadSize: 1472
    api: no
    rtsp: yes
    protocols: [udp, multicast, tcp]
    encryption: "no"
    rtspAddress: :8554
    
    rtmp: false
    hls: false
    webrtc: false
    srt: false
    
    pathDefaults:
      source: publisher
      sourceOnDemand: no
      sourceOnDemandStartTimeout: 10s
      sourceOnDemandCloseAfter: 10s
      maxReaders: 0
      record: no
      rtspTransport: automatic
      rpiCameraCamID: 0
      rpiCameraWidth: 1920
      rpiCameraHeight: 1080
      rpiCameraHFlip: false
      rpiCameraVFlip: false
      rpiCameraBrightness: 0
      rpiCameraContrast: 1
      rpiCameraSaturation: 1
      rpiCameraSharpness: 1
      rpiCameraExposure: normal
      rpiCameraAWB: auto
      rpiCameraDenoise: "off"
      rpiCameraShutter: 0
      rpiCameraMetering: centre
      rpiCameraGain: 0
      rpiCameraEV: 0
      rpiCameraHDR: false
      rpiCameraFPS: 30
      rpiCameraIDRPeriod: 60
      rpiCameraBitrate: 1000000
      rpiCameraProfile: main
      rpiCameraLevel: '4.1'
      rpiCameraAfMode: continuous
      rpiCameraAfRange: normal
      rpiCameraAfSpeed: normal
      rpiCameraLensPosition: 0.0
      rpiCameraTextOverlayEnable: true
      rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - Office South'
    
    paths:
      cam:
        source: rpiCamera
    

Finishing the image

If you intend on re-using your image now is the time to delete the ssh keys, these can be found in /etc/ssh/. If you choose to delete the host keys, they will need to be rebuilt the next time the RPi boots, this can be done with sudo dpkg-reconfigure openssh-server do note though SSH will not be available so the RPi will need to be accessed locally. At this point your uSD can be imaged and re-used later.

Adjusting the camera

At this point your RPi is assembled with the POE+ hat somewhat squashing your cameras ribbon cable. Take the time now to adjust your camera, adjustments will most likely be needed later when the system and its camera are transferred into a case however at this point it would be best to make sure everything works. Make sure you can adjust the camera focus and aperture. You will need to unscrew the focus ring a good bit, you should begin to see a silver ring pictured below:
silver-ring
Also don’t forget to use the back focus adjustment ring to dial it in. Take note of the diagram below:
camera-diagram
There are two separate ring/adapters in this diagram that can be adjusted. If, like me, you purchased the 6mm lens the largest one (C-CS adapter) must be discarded, that leaves the smallest ring, the “Back focus adjustment ring”, make sure yours can move. In my case this required a significant amount of force (think pliers + rage) even with the lock screw fully removed. This adjustment ring can move and most likely will need to in order to get the image focused correctly.

At the time of writing this post the lens appears to have been a bad choice. I am unable to focus the camera at a distant object. The picture quality is great but only within a short distance, i.e. ~10 feet or so, any further and the image becomes blurry again. Other lenses may be a better option.
When the image is still blurry but mostly there that final ring can be adjusted to get the final bit of clarity from the image. Keep in mind this may take a good while.

Closing Notes

At this point the main meat and potatoes has all been shown to work, and a very simple RTSP server is available. There are still many improvements that need to be made, for example:

  • The system needs a case
  • The image can not be focused on distant objects
    • Think street lights at 50 meters
  • The host OS is fine however I have a preference for RHEL based distros
    • While the OS does not need to be RHEL based SELinux would be a nice addition
    • Currently the cameras will exist in an IOT network, this network does not have NAT available so some sort of package proxy would be nice for updates.

There is more work to here for sure. Next step is to print a case for the system, which will be its own ordeal. I anticipate future steps being of note and as such there most likely will be a “part 2” to this.

Sources