Decoding integer fields in Alerts\OnTrigger registry — does a community reference exist?

whoami ™

Pulling my weight
Aug 4, 2019
242
233
South Florida
I've been building an MCP server (bi-mcp) that lets an AI agent read Blue Iris config from the camera .reg exports,
including the per-camera action sets under Alerts\OnTrigger\<N> and Alerts\OnReset\<N>. Most of the schema is
self-explanatory once you've seen a few exports — web_path, soundPath, camname, descript, etc. — but a few fields are
integer-coded and I haven't found public documentation:

- type — the action kind. In my install I only see type=3 (Web/MQTT) and type=12 (Do Command). The other 12 BI action kinds
(Sound, Push, Email, SMS, Phone, Run Program, DIO Bits, Popup, FTP Upload, Change Shield, Change Schedule/Profile, Wait)
must each have their own integer, but I haven't toggled them in the UI yet to see what they serialize to.
- command — for type=12 Do Command actions. I see 2200..2299 for PTZ-preset recall (command=2202 = "call PTZ preset 2"),
and the manual lists ~30 do-commands in the dropdown (snapshot, profile change, /admin?, etc.) but I don't have their
integer codes.
- web_proto1 — protocol enum for type=3. I see 2 = MQTT; the UI offers HTTP-GET, HTTP-POST, MQTT, and TCP, so there are
presumably 3 more values.
- trig_source — looks like a wider bitmask (I see 132, 16514, 16516, 16518, 16522; bit 14 = 16384 seems to correlate with
AI confirmation). I haven't worked out the full bit layout.

I've already searched the forum (trig_source returns zero hits forum-wide; OnTrigger returns one on-topic post about a V6
upgrade) and the obvious GitHub projects (bp2008/BlueIrisRegistryReader, VorlonCD/bi-aidetection, magapp/blueiriscmd) —
none of them touch the action-set blobs. I half-remember someone (maybe @jaydeel ?) doing systematic .reg diffs across BI
versions to catch undocumented additions. If that's you, or you've seen the work, I'd love a pointer.

Worst case I just toggle each option in the UI on a throwaway camera and diff the exports — but if someone's already done
this, I'd rather build on top of it than redo it. Happy to publish whatever decoder tables I end up with back to the
community.

Thanks!
 
I'm kind of excited about this MCP server, I would have it finished, but I have to build it in stages because I'm not paying for extra usage credits for this. I'm obviously running this thing in Claude Code. I believe this thing is going to be POWERFUL, its hard at this point to imagine all the use cases but for example, I recently got a PTZ5A4M-25X. I was having an issue with a race condition from my spotter cams pulling my PTZ away to preset while tracking. For a week I went back and forth with having a clone for AI confirmation, and "skip alerts when this preset is active" on the master vs. just using the PTZ for everything with "skip alerts when this preset is active" disabled. Watching days worth of clips trying to analyze the results myself. Yesterday I ran a test without the clone and had Claude do an analysis using the MCP server... Here is how it went down all from one prompt.

Question: does the SecCam_11AI clone actually do work, or was periodic-scan-only on the master enough to suppress the
spotter-pull race?

Tools used for the clone A/B analysis

1. bi_list_log (admin) — the keystone tool
- Pulled the BI system log to find Alert skipped (PTZ preset N) events across the 4-day window
- This is what produced the 28-event count and the zero-on-2026-05-16 finding
- Requires the claude-admin BI user (admin-gated cmd)

2. bi_list_alerts — per-camera alert correlation
- Listed alerts on the spotter cameras (SecCam_1/3/4) and the PTZ clone in the test window
- Used to find the ±5s context around each Alert skipped log line — i.e., which spotter was triggering at the moment the
master was locked to a different preset

3. bi_get_alert_tracks + bi_get_clip_info — to verify individual race events
- Pulled tracks/clip metadata on the suspect alerts to confirm the person was actually in-frame on the spotter while the
PTZ held its lock elsewhere

4. bi_list_cameras — to confirm clone visibility state
- Verified SecCam_11 was hidden: true and SecCam_11AI was hidden: false after the rebuild

5. bi_get_reg + bi_get_actionset — to audit the actual on-disk config
- Inspected Alerts\OnTrigger on the spotters to confirm the type=12 PTZ-call actions were the source of the pull
- Inspected the PTZ master's skip-alerts flags per preset

6. The human side
- You were the one who deleted the clone on 2026-05-16 and rebuilt it on 2026-05-17 night; I didn't drive that. The MCP
read-tools just measured the result.

Method: A/B test over ~36h.
- 2026-05-16: clone deleted, ran periodic-scan-only on the PTZ master for ~1 day
- 2026-05-17 night: clone rebuilt
- 2026-05-18 17:34: audited the BI log

Evidence in the log (4-day window):
- 28 Alert skipped (PTZ preset N) events across presets 1, 2, 3, 4, 6
- Zero on 2026-05-16 — the day the clone was absent
- 13 race-condition saves in the first ~19h post-rebuild, several with ±5s context showing a spotter (SecCam_1/3/4)
triggering on a person while the PTZ held a different preset, and being suppressed

Conclusion: the clone's priority lock is what's doing the work, not periodic-scan timing. Do NOT propose deleting the clone
again.

The architecture it locked in

- SecCam_11 — hidden master, all tracking presets have skip-alerts ON, holds the priority lock so spotters can't yank it
mid-track. Produces no Person-alert clips.
- SecCam_11AI — visible clone of the same physical camera, no skip-alerts, runs the AI scan and produces the Person-alert
clips.

This is the documented community workaround for the "want priority lock AND want alert images" conflict — you can't get
both from a single camera definition because skip-alerts kills the alert pipeline on the same camera that owns the lock.

Stored in:
- project_ptz_alert_pipeline.md — full A/B test result and configuration
- project_camera_roster.md — both camera entries flagged with the test outcome
- Do-not-delete-the-clone warning lives in both

The reason I started playing around with this is because I'm running a MCP server for HomeAssistant, along side Googles MCP server for chrome, and also giving Claude ssh access to my HomeAssistant box running HAOS. There is nothing I cant do with this thing. It has a full feedback loop. Its so powerful I cant even begin to put it into words... I just tell it what i want like its a magic genie and it builds it. I'm now only limited by imagination on building dashboards. I don't know HTML or CSS but if i can explain it or give Claude an example it will build - test - and verify by inspecting the webpage in chrome dev tools and looking at screenshots until it gets it right. I hope I don't sound crazy. But I don't think most people can even imagine. Vibe coding is cool... but when you have a complete feedback loop this thing is crazy powerful. The people that think they are using AI by using it like a google search in web are going to be so far behind. This space is changing daily, it'll be impossible to catch up. I haven't been this excited about something since 2016 going into 17 with crypto. I was impressed last year with copy and pasting code into web chats but it was no where near "there" yet. Now its there, and it will only get more powerful at insane pace.
 
Here are various Powershell decoding examples for Blue Iris Action Condition settings from the Windows Registry.

1779382494456.png

Trigger Profiles (regkey 'profiles')

Code:
function Convert-ProfileMaskToString {
    <# EXAMPLES:
        Convert-ProfileMaskToString 255  # 0123456
        Convert-ProfileMaskToString 254  # 123456
        Convert-ProfileMaskToString 46   # ''
    #>
 
        param(
            $Value
        )

        if ($null -eq $Value -or $Value -eq '') {
            return ''
        }

        # Blue Iris legacy/special case:
        # 0x2E / decimal 46 appears to mean no profiles.
        if ([int]$Value -eq 46) {
            return ''
        }

        $profile_val = [int]$Value
        $rtn_str = ''

        for ($i = 0; $i -lt 7; $i++) {
            if (($profile_val -band [int][math]::Pow(2, $i)) -ne 0) {
                $rtn_str += [string]$i
            }
        }

        return $rtn_str
    }

Trigger Zones (regkey 'trig_zones')

Code:
function Convert-ZoneMaskToString {
    <# EXAMPLES
        Convert-ZoneMaskToString 255   # ABCDEFGH
        Convert-ZoneMaskToString 7     # ABC
        Convert-ZoneMaskToString 16    # E

        Convert-ZoneStringToMask 'ACG' # 69
        Convert-ZoneStringToMask 'AB'  # 3
    #>

        param(
            $Value
        )

        if ($null -eq $Value -or $Value -eq '') {
            return ''
        }

        $zone_letters = @('A','B','C','D','E','F','G','H')
        $zone_val = [int]$Value
        $rtn_str = ''

        for ($i = 0; $i -lt 8; $i++) {
            if (($zone_val -band [math]::Pow(2, $i)) -ne 0) {
                $rtn_str += $zone_letters[$i]
            }
        }

        return $rtn_str
    }

Trigger Zones Any (regkey 'trig_allzones')

Code:
function Convert-TrigAllZonesToString {

    param(
        $Value
    )

    if ($null -eq $Value -or $Value -eq '') {
        return ''
    }

    switch ([int]$Value) {
        2 { return 'Any' }
        1 { return 'All' }
        0 { return '=' }
        default { return ("Unknown({0})" -f $Value) }
    }
}

Trigger Source (regkey 'trig_source')

Code:
function Convert-TrigSourceMaskToString {

        param(
            $Value
        )

        if ($null -eq $Value -or $Value -eq '') {
            return ''
        }

        $source_map = @{
            1  = 'Motion'
            2  = 'ONVIF'
            3  = 'Audio'
            4  = 'External'
            5  = 'DIO'
            6  = 'Group'
            14 = 'AI'
        }

        $source_val = [int]$Value
        $rtn_ary = @()

        foreach ($bit_no in ($source_map.Keys | Sort-Object)) {
            if (($source_val -band [int][math]::Pow(2, $bit_no)) -ne 0) {
                $rtn_ary += $source_map[$bit_no]
            }
        }

        return ($rtn_ary -join ',')
    }

Trigger DIO (regkey 'diobits')

Code:
function Convert-DioBitsToString {

        param(
            $Value
        )

        if ($null -eq $Value -or $Value -eq '') {
            return ''
        }

        $dio_val = [int]$Value
        $rtn_ary = @()

        for ($i = 0; $i -lt 32; $i++) {
            if (($dio_val -band [int][math]::Pow(2, $i)) -ne 0) {
                $rtn_ary += [string]($i + 1)
            }
        }

        return ($rtn_ary -join ',')
    }
 
Last edited:
Here is a Powershell decoding example for Blue Iris Do command Action from the Windows Registry.

1779382637162.png

DoCommand (regkey 'docmd')

Code:
function Convert-DoCmdToString {

    param(
        $Value
    )

    if ($null -eq $Value -or $Value -eq '') {
        return ''
    }

    $cmd_no = [int]$Value

    # Action sets
    if ($cmd_no -ge 33203 -and $cmd_no -le 33210) {
        return ('Action set {0}' -f ($cmd_no - 33202))
    }

    # Brightness
    if ($cmd_no -ge 58697 -and $cmd_no -le 58712) {
        return ('Brightness {0}' -f ($cmd_no - 58697))
    }

    # Contrast
    if ($cmd_no -ge 58713 -and $cmd_no -le 58719) {
        return ('Contrast {0}' -f ($cmd_no - 58713))
    }

    # Gain
    if ($cmd_no -ge 58985 -and $cmd_no -le 58995) {
        return ('Gain {0}%' -f (($cmd_no - 58985) * 10))
    }

    # PTZ presets
    if ($cmd_no -ge 2201 -and $cmd_no -le 2456) {
        return ('PTZ Preset {0}' -f ($cmd_no - 2200))
    }

    # PTZ speed
    if ($cmd_no -ge 59050 -and $cmd_no -le 59059) {
        return ('PTZ Speed {0}' -f ($cmd_no - 59049))
    }

    # Set/Reset Outputs
    if ($cmd_no -ge 59027 -and $cmd_no -le 59034) {
        return ('Set output {0}' -f ($cmd_no - 59026))
    }
    if ($cmd_no -ge 59035 -and $cmd_no -le 59042) {
        return ('Reset output {0}' -f ($cmd_no - 59034))
    }

    switch ($cmd_no) {

        # /admin? command

        1 { return '/admin?' }

        # Camera

        58491 { return 'Restart' }

        58508 { return 'Trigger' }

        58473 { return 'Snapshot' }

        32796 { return 'Camera enable' }
        32797 { return 'Camera disable' }
        32798 { return 'Camera enable/disable' }

        58662 { return 'Show camera' }
        58663 { return 'Hide camera' }

        # DIO

        58664 { return 'DIO 1 On' }
        58665 { return 'DIO 2 On' }
        58666 { return 'DIO 3 On' }
        58674 { return 'DIO 1 Off' }
        58675 { return 'DIO 2 Off' }

        # Focus / iris

        58684 { return 'Focus far' }
        58685 { return 'Focus near' }

        58754 { return 'Iris open' }
        58755 { return 'Iris close' }

        # IR LEDs

        58722 { return 'IR LEDs Auto' }
        58721 { return 'IR LEDs Off' }
        58720 { return 'IR LEDs On' }

        # PTZ movement

        58686 { return 'Pan left' }
        58687 { return 'Pan right' }

        58688 { return 'Tilt up' }
        58689 { return 'Tilt down' }

        58690 { return 'Home' }

        58691 { return 'Zoom in' }
        58692 { return 'Zoom out' }

        59060 { return 'PTZ Speed +' }
        59061 { return 'PTZ Speed -' }

        32945 { return 'Auto-cycle patrol ON' }
        32946 { return 'Auto-cycle patrol OFF' }

        # PTZ Presets
 
        58725 { return 'Previous preset' }

        # Modes

        58694 { return 'Mode 50Hz' }
        58695 { return 'Mode 60Hz' }
        58696 { return 'Mode Outdoor' }

        # Overlays

        32930 { return 'Overlay toggle' }

        # Pause

        32890 { return 'Pause indefinitely' }
        32891 { return 'Pause reset' }

        32892 { return 'Pause add 30 seconds' }

        32893 { return 'Pause add 5 minutes' }
        32901 { return 'Pause add 15 minutes' }
        32894 { return 'Pause add 30 minutes' }

        32895 { return 'Pause add 1 hour' }
        32896 { return 'Pause add 2 hours' }
        32897 { return 'Pause add 3 hours' }
        32898 { return 'Pause add 5 hours' }
        32899 { return 'Pause add 10 hours' }

        # Recording

        58500 { return 'Record Start/Stop' }

        # Reboot

        58723 { return 'Reboot' }
        58782 { return 'Reboot PC' }

        # Shutter

        58970 { return 'Shutter 1/6' }
        58971 { return 'Shutter 1/12' }
        58972 { return 'Shutter 1/30' }
        58973 { return 'Shutter 1/60' }
        58974 { return 'Shutter 1/90' }

        58976 { return 'Shutter 1/200' }
        58977 { return 'Shutter 1/250' }
        58978 { return 'Shutter 1/500' }
        58979 { return 'Shutter 1/1000' }
        58980 { return 'Shutter 1/2000' }
        58981 { return 'Shutter 1/4000' }

        # Wiper

        58752 { return 'Wiper Off' }
        58753 { return 'Wiper On' }

        default {
            return ("Command({0})" -f $cmd_no)
        }
    }
}
 
Last edited:
Here is a Powershell decoding example for Blue Iris Web/MQTT Request Action from the Windows Registry.

1779382751359.png

Web/MQTT Request (regkey 'web_proto1')

Code:
function Convert-WebProtoToString {

    param(
        $Value
    )

    if ($null -eq $Value -or $Value -eq '') {
        return ''
    }

    switch ([int]$Value) {
        0 { return 'http://' }
        1 { return 'https://' }
        2 { return 'MQTT topic' }
        default { return ("Unknown({0})" -f $Value) }
    }
}
 
Last edited:
Here is a Powershell decoding example for Blue Iris Run a Program Action from the Windows Registry.

1779383564611.png

Run a Progam (regkey 'run_action')

Code:
function Convert-RunActionToString {

        param(
            $Value
        )

        if ($null -eq $Value -or $Value -eq '') {
            return ''
        }

        switch ([int]$Value) {
            0 { return 'Run program/script' }
            1 { return 'Write to file--append' }
            2 { return 'Write to file--replace' }
            3 { return 'Delete file' }
            default { return ("Unknown({0})" -f $Value) }
        }
    }
 
@jaydeel — this is gold, thank you. That's a huge chunk of the BI .reg surface decoded in one post, and it lined up with the empirical values I'd been seeing in my own exports almost exactly.
I just landed all of it in the MCP server. Now decoded:
  • web_proto10=http, 1=https, 2=mqtt (good to know there's no TCP variant; that was my wrong guess)
  • run_action — full 0-3 enum for type=2
  • trig_allzones — exact / all / any (I'd been silently dropping value=0 and misreading value=2)
  • trig_source — the bit map cleared up our most-observed values: 16518 = motion+onvif+ai, 16514 = motion+ai, 132 = onvif+(bit 7). I still see bit 7 (=128) in every onvif-triggered action set on my install — any idea what that one is? It tracks perfectly with ONVIF presence, so I half-suspect it's an internal "ONVIF source verified" flag, but I'm guessing.
  • profiles — fixed my off-by-one (I was treating bit 0 as 'unused/inactive' instead of profile 0). The 46 sentinel got handled too.
  • diobits — DIO 1-32, 1-indexed
  • docmd — this is the one that unlocked the most for me. The PTZ-preset range (2201-2456) means I was capping at preset 99 before; the action-set range (33203-33210), brightness/contrast/gain ranges, and the ~60 individual codes (snapshot, restart, pause-add-N, shutter speeds, etc.) are all in the decoder now. Previously a do-command action just came back as command_raw: 32891 and the agent had to guess what that meant.
Concretely, this is what changed in my install: when an AI agent (Claude, via the MCP) asks "what does SecCam_3 do on a person alert?", it gets back semantic JSON like
Code:
{
  "type": "do_command",
  "command": "ptz_preset",
  "preset": 2,
  "target_camera": "SecCam_11",
  "filters": {
    "profiles": ["1","2","3","4","5","6"],
    "zones": ["A","B","C","D","E","F","G","H"],
    "zones_match": "exact",
    "objects": ["person"],
    "trig_source": ["motion","onvif","ai"]
  }
}
instead of an opaque blob of integers. That makes the difference between "the agent can reason about my action sets" and "the agent has to read the manual every time."
I'll publish the decoder source (Python) once a few more loose ends are tied off. Happy to credit your post as the source — let me know if you'd prefer a different attribution.
Bit 7 question aside, the only documented loose ends left on my side are payload-field names for the action kinds I haven't toggled in the UI (email recipient, sms body, push title/body, ftp destination, etc.). I'll work through those with UI toggles + diffs when I get to it.
Thanks again — this saved me hours of UI-toggling and gave me confidence the tables are right, which I wouldn't have gotten from my own empirical inferences alone. The profile bit-0 and the 46 sentinel especially — those weren't going to fall out of UI experiments.