Turn on studio lights / fill / ring lights when camera comes on to light your face when on video calls

I have a Neewer ring light that I use to light my face properly when I’m on work video calls, but the physical switch is annoying to flip on and off whenever I start a call, and it’s kind of blinding to have it on all the time.

Let’s solve this with Home Assistant! Note: These instructions will only work if your video call machine is running OSX. If someone wants to remix this solution for Windows please do!

  1. Plug studio light into a smart switch
    I use a pretty basic Innr switch which connects to HA over Zigbee and offers only on/off, no power measurement or anything like that. But that’s OK.

  2. Add switch to HA
    I use zigbee2mqtt to do this. I actually recently moved from ZHA and find Z2MQTT running on separate dedicated hardware (an old RPi 1B) to be really great.

  3. Set up mosquitto and MQTT on HA
    Add the Mosquitto app in Supervisor, and enable the MQTT integration in HA config. Obviously I’ve already done this because I’m using zigbee2mqtt, but if you’re using some other way to talk to your switch, you might need to add MQTT to your mix. When setting up mosquitto, it’s a good idea to make a dedicated user account for pushing camera events. I called mine on-air-monitor.

  4. Make a shell script to detect your camera events
    OK, so this is the key bit. Paste this into a file called on-air-monitor.sh in your computer’s home directory. Change the IP address, username and password in the mosquitto_pub commands to match the IP address of your HA server and the username and password you set up in the previous step. This script depends on mosquitto_pub which can be installed using Homebrew with brew install mosquitto.

    #!/bin/sh
    
    args=("$@") # capture them here so we can use them if --sync's not passed
    async=true
    
    source ./config.sh
    
    while [ $# -gt 0 ]
    do
        case "$1" in
            --sync)
                async=false
                ;;
            # other options
        esac
        shift
    done
    
    for pid in $(pgrep -f on-air-monitor.sh); do
        if [ $pid != $$ ]; then
            echo "[$(date)] : on-air-monitor.sh : Process is already running with PID $pid"
            exit 1
        else
          echo "Running with PID $pid"
        fi
    done
    
    # if --sync isn't passed, rerun the script as a background task
    if [ "$async" = true ]; then
      echo "Starting on air monitor in the background"
      nohup "${BASH_SOURCE[0]}" --sync "${args[@]}" 0<&- &> /dev/null &
      exit 1
    fi
    
    echo "Monitoring camera events for $mqtt_topic"
    log stream | grep --line-buffered "Post event kCameraStream" | while read -r line; do
      if [[ $line == *"StreamStart"* ]]; then
        echo "Video start"
        SECONDS=0
        mosquitto_pub -h 192.168.X.X -u on-air-monitor -P your_password -t on-air-monitor/state -m 'on'
      else
        echo "Video off after $SECONDS sec"
        mosquitto_pub -h 192.168.X.X -u on-air-monitor -P your_password -t on-air-monitor/state -m 'off'
      fi
    done
    
  5. Launch the script

    $ chmod +x on-air-monitor.sh
    $ ./on-air-monitor.sh
    
  6. Set up an automation to turn your switch on and off
    When the MQTT server publishes a change to the on-air-monitor/state topic. I use Node-RED to do this:

Works great and the response time is easily fast enough that by the time my colleague sees my video in their VC client, my light is on. One other thing to note is that my light can be turned on and off just by giving it power. Many fill lights don’t remember their state when power is removed and require a physical button press to turn them on after power is restored. Don’t buy one of those :laughing:

1 Like

I have something similar, using windows.
This generates a ‘webcam in use’ binary sensor that I use in an automation to turn on my light

This doesn’t work in macOS Monterey. You’ll have to use a slightly different predicate:

$ log stream --predicate 'subsystem == "com.apple.UVCExtension" and composedMessage contains "Post PowerLog"'
Filtering the log data using "subsystem == "com.apple.UVCExtension" AND composedMessage CONTAINS "Post PowerLog""
Timestamp                       Thread     Type        Activity             PID    TTL  
2021-10-27 12:21:13.366628+0200 0x147c5    Default     0x0                  353    0    UVCAssistant: (UVCExtension) [com.apple.UVCExtension:device] UVCExtensionDevice:0x1234005d7 [0x7fe3ce008ca0] Post PowerLog {
    "VDCAssistant_Device_GUID" = "00000000-1432-0000-1234-000022470000";
    "VDCAssistant_Power_State" = On;
}
2021-10-27 12:21:16.946379+0200 0x13dac    Default     0x0                  353    0    UVCAssistant: (UVCExtension) [com.apple.UVCExtension:device] UVCExtensionDevice:0x1234005d7 [0x7fe3ce008ca0] Post PowerLog {
    "VDCAssistant_Device_GUID" = "00000000-1432-0000-1234-000022470000";
    "VDCAssistant_Power_State" = Off;
}
1 Like

Brilliant! Thanks for posting that update.

NP :slight_smile:

You can also use --style ndjson to avoid the multi-line log messages.

Update for MacOS Sonoma:

log stream --predicate 'sender contains "appleh13camerad" and (composedMessage contains "PowerOnCamera" or composedMessage contains "PowerOffCamera")' | grep --line-buffered "ISP_Power" | while read -r line; do