con un clic
add-protocol-strix
// Add a new protocol support to Strix -- full flow from research to implementation. Covers stream handler registration, URL builder updates, database issues, and go2rtc integration.
// Add a new protocol support to Strix -- full flow from research to implementation. Covers stream handler registration, URL builder updates, database issues, and go2rtc integration.
Create or redesign frontend pages for Strix. Use when building new HTML pages, redesigning existing ones, or working on any UI task in the www/ directory. Covers design principles, layout patterns, and component usage.
Full release of Strix -- merge develop to main, tag, build multiarch Docker image, build static binaries, push to Docker Hub, update hassio-strix, create GitHub Release with binaries attached.
Add a new device type detector to the Strix probe system. Covers adding new probers, result types, and detector functions.
Build and push dev Docker image for Strix, update hassio-strix dev add-on version.
| name | add_protocol_strix |
| description | Add a new protocol support to Strix -- full flow from research to implementation. Covers stream handler registration, URL builder updates, database issues, and go2rtc integration. |
| disable-model-invocation | true |
| argument-hint | ["protocol-name"] |
You are adding support for a new protocol to Strix. Follow every step in order. Be thorough -- read all referenced files completely before writing any code.
The protocol name is provided as argument (e.g. /add_protocol_strix bubble). If no argument, use AskUserQuestion to ask which protocol to add.
/home/user/Strix/home/user/go2rtc (reference implementation, read-only)Before doing anything, read these files completely to understand the patterns:
/home/user/Strix/pkg/tester/source.go -- handler registry + RTSP reference implementation
/home/user/Strix/pkg/tester/worker.go -- how handlers are called, screenshot logic
/home/user/Strix/pkg/tester/session.go -- session data structures
/home/user/Strix/pkg/camdb/streams.go -- URL builder, placeholder replacement
/home/user/Strix/internal/test/test.go -- API layer for tester
/home/user/Strix/internal/search/search.go -- search API (rarely needs changes)
Registration in pkg/tester/source.go:
var handlers = map[string]SourceHandler{}
func RegisterSource(scheme string, handler SourceHandler) {
handlers[scheme] = handler
}
func init() {
RegisterSource("rtsp", rtspHandler)
RegisterSource("rtsps", rtspHandler)
RegisterSource("rtspx", rtspHandler)
}
Handler -- receives a URL string, returns go2rtc core.Producer:
func rtspHandler(rawURL string) (core.Producer, error) {
rawURL, _, _ = strings.Cut(rawURL, "#")
conn := rtsp.NewClient(rawURL)
conn.Backchannel = false
if err := conn.Dial(); err != nil {
return nil, fmt.Errorf("rtsp: dial: %w", err)
}
if err := conn.Describe(); err != nil {
_ = conn.Stop()
return nil, fmt.Errorf("rtsp: describe: %w", err)
}
return conn, nil
}
Data flow: URL -> GetHandler(url) -> handler(url) -> core.Producer -> GetMedias() -> codecs, latency -> getScreenshot() -> jpegSize() -> Result (with width, height)
Key: The handler ONLY needs to return a core.Producer. Everything else (codecs extraction, screenshot capture, session management) is handled automatically by worker.go.
pkg/camdb/streams.go:/cam/realmonitor?channel=[CHANNEL]&subtype=0replacePlaceholders() substitutes [CHANNEL], [USERNAME], [PASSWORD], etc.buildURL() prepends protocol://user:pass@host:port to the pathurl.PathEscape / url.QueryEscapeDefault ports are defined in defaultPorts map:
var defaultPorts = map[string]int{
"rtsp": 554, "rtsps": 322, "http": 80, "https": 443,
"rtmp": 1935, "mms": 554, "rtp": 5004,
}
go2rtc already implements most camera protocols. Study the implementation:
| What | Where |
|---|---|
| Protocol client logic | /home/user/go2rtc/pkg/{protocol}/ |
| Module registration | /home/user/go2rtc/internal/{protocol}/ |
| Core interfaces | /home/user/go2rtc/pkg/core/core.go |
| Stream handler registry | /home/user/go2rtc/internal/streams/handlers.go |
| Keyframe capture | /home/user/go2rtc/pkg/magic/keyframe.go |
| Protocol | pkg/ (Dial function) | internal/ (Init glue) |
|---|---|---|
| rtsp/rtsps | pkg/rtsp/client.go | internal/rtsp/rtsp.go |
| http/https | pkg/magic/producer.go, pkg/tcp/request.go | internal/http/http.go |
| rtmp | pkg/rtmp/ | internal/rtmp/rtmp.go |
| bubble | pkg/bubble/ | internal/bubble/bubble.go |
| dvrip | pkg/dvrip/ | internal/dvrip/dvrip.go |
| onvif | pkg/onvif/ | internal/onvif/onvif.go |
| homekit | pkg/homekit/, pkg/hap/ | internal/homekit/homekit.go |
| tapo | pkg/tapo/ | internal/tapo/tapo.go |
| kasa | pkg/kasa/ | internal/kasa/kasa.go |
| eseecloud | pkg/eseecloud/ | internal/eseecloud/eseecloud.go |
| nest | pkg/nest/ | internal/nest/init.go |
| ring | pkg/ring/ | internal/ring/ring.go |
| wyze | pkg/wyze/ | internal/wyze/wyze.go |
| xiaomi | pkg/xiaomi/ | internal/xiaomi/xiaomi.go |
| tuya | pkg/tuya/ | internal/tuya/tuya.go |
| doorbird | pkg/doorbird/ | internal/doorbird/doorbird.go |
| isapi | pkg/isapi/ | internal/isapi/init.go |
| flussonic | pkg/flussonic/ | internal/flussonic/flussonic.go |
| gopro | pkg/gopro/ | internal/gopro/gopro.go |
| roborock | pkg/roborock/ | internal/roborock/roborock.go |
/home/user/go2rtc/internal/{protocol}/{protocol}.go -- find streams.HandleFunc call, understand what function is called and how/home/user/go2rtc/pkg/{protocol}/ -- find the Dial() or NewClient() function, understand its signature and what it returnscore.Producer? Does it need special setup before Dial? Does it need credentials differently?package kasa
import (
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/kasa"
)
func Init() {
streams.HandleFunc("kasa", func(source string) (core.Producer, error) {
return kasa.Dial(source)
})
}
Most protocols follow this exact pattern: pkg/{protocol}.Dial(url) returns core.Producer.
Use AskUserQuestion to discuss with the user. Determine the protocol type:
bubble://host:port/path)ONLY for Type A protocols that have URL patterns stored in the database.
Create a GitHub issue using gh CLI for the new protocol:
cd /home/user/Strix
gh issue create --repo eduard256/StrixCamDB \
--title "[New Protocol] {PROTOCOL_NAME}" \
--label "new-protocol" \
--body "$(cat <<'ISSUE_EOF'
```yaml
protocol: {PROTOCOL_NAME}
default_port: {PORT}
url_format: {EXAMPLE_URL_PATTERN}
{DESCRIPTION -- what cameras use this, what firmware, how it works}
{ANY_NOTES} ISSUE_EOF )"
If the protocol introduces new placeholders (e.g. `[STREAM]`), create a separate issue:
```bash
gh issue create --repo eduard256/StrixCamDB \
--title "[New Placeholder] {PLACEHOLDER}" \
--label "new-placeholder" \
--body "$(cat <<'ISSUE_EOF'
placeholder: "{PLACEHOLDER}"
alternatives: ["{alt1}", "{alt2}"]
description: "{WHAT_IT_DOES}"
example_values: ["{VAL1}", "{VAL2}"]
## URL examples
- {URL_EXAMPLE_1}
- {URL_EXAMPLE_2}
## Known brands using this
- {BRAND1}
- {BRAND2}
ISSUE_EOF
)"
DO NOT wait for issue approval. Continue immediately to the next step.
If the protocol needs a new default port, edit /home/user/Strix/pkg/camdb/streams.go:
Add the port to defaultPorts map:
var defaultPorts = map[string]int{
"rtsp": 554, "rtsps": 322, "http": 80, "https": 443,
"rtmp": 1935, "mms": 554, "rtp": 5004,
// add new protocol here:
"bubble": 80,
}
If the protocol needs new placeholders in replacePlaceholders(), add them to the pairs slice. Follow the existing pattern -- both [UPPER] and [lower] variants, plus {curly} variants.
/home/user/Strix/pkg/camdb/streams.go -- defaultPorts map and replacePlaceholders() function/home/user/Strix/pkg/tester/source.go completelyDial() function needs and returnsMost protocols follow the same pattern as RTSP. The handler:
pkg/{protocol}.Dial(url) or equivalentcore.ProducerAdd the handler to /home/user/Strix/pkg/tester/source.go.
Pattern for simple protocols (bubble, dvrip, rtmp, kasa, etc.):
import "github.com/AlexxIT/go2rtc/pkg/{protocol}"
// in init():
RegisterSource("{scheme}", {scheme}Handler)
// handler:
func {scheme}Handler(rawURL string) (core.Producer, error) {
return {protocol}.Dial(rawURL)
}
If the protocol needs extra setup before Dial (like RTSP needs Backchannel = false), add it. Study the go2rtc internal module to see what setup is done.
Pattern for protocols that need connection setup (like RTSP):
func {scheme}Handler(rawURL string) (core.Producer, error) {
rawURL, _, _ = strings.Cut(rawURL, "#")
conn := {protocol}.NewClient(rawURL)
// any setup specific to this protocol
if err := conn.Dial(); err != nil {
return nil, fmt.Errorf("{scheme}: dial: %w", err)
}
// protocol-specific validation (like RTSP Describe)
return conn, nil
}
These protocols do NOT go through the standard URL -> handler flow. They need a source handler that receives custom parameters and produces results directly.
The current architecture uses SourceHandler func(rawURL string) (core.Producer, error) for standard protocols. For custom protocols, you need to:
streams arrayCurrent request format:
{
"sources": {
"streams": ["rtsp://...", "http://..."]
}
}
Extended format for custom protocols:
{
"sources": {
"streams": ["rtsp://...", "http://..."],
"homekit": {"device_id": "AA:BB:CC", "pin": "123-45-678"},
"onvif": {"host": "192.168.1.100", "username": "admin", "password": "pass"}
}
}
To implement this:
/home/user/Strix/pkg/tester/source.go:// SourceBlockHandler processes a custom source block, writes results directly to session
type SourceBlockHandler func(data json.RawMessage, s *Session)
var sourceHandlers = map[string]SourceBlockHandler{}
func RegisterSourceBlock(name string, handler SourceBlockHandler) {
sourceHandlers[name] = handler
}
Update /home/user/Strix/internal/test/test.go apiTestCreate() to parse and dispatch custom source blocks.
Write the handler for your protocol. It receives raw JSON and a Session, and is responsible for:
s.AddTested() for progress trackingIMPORTANT: Before implementing a custom protocol, discuss the approach with the user. Custom protocols are rare and need careful design.
cd /home/user/Strix
go build ./...
If it compiles, test with the running container:
# rebuild image
docker build -t strix:test .
# restart container
docker rm -f strix
docker run -d --name strix --network host --restart unless-stopped strix:test
# check logs
docker logs strix
# test the new protocol (example for bubble)
curl -s -X POST http://localhost:4567/api/test \
-H 'Content-Type: application/json' \
-d '{"sources":{"streams":["bubble://admin:password@192.168.1.100:80/"]}}'
/api/streams returns URLs with correct scheme and portcd /home/user/Strix
git add -A
git commit -m "Add {protocol} protocol support
- Register {protocol} stream handler using go2rtc pkg/{protocol}
- Add default port {port} for {protocol} scheme
- {any other changes}"
git push origin develop
All code MUST follow AlexxIT go2rtc style:
source.go if it's a one-liner (return pkg.Dial(url))source_{protocol}.gosource.go as the registry + simple handlers{scheme}Handler (e.g. bubbleHandler, rtmpHandler)"{scheme}: dial: ..." or "{scheme}: ..."conn for connection, prod for producerfmt.Errorf("bubble: dial: %w", err)_ = conn.Stop()// ex. "bubble://admin:pass@192.168.1.100:80/""github.com/AlexxIT/go2rtc/pkg/{protocol}""github.com/AlexxIT/go2rtc/pkg/core" for Producer interfaceEvery protocol handler must return something that implements core.Producer:
type Producer interface {
GetMedias() []*Media // what tracks are available (video/audio codecs)
GetTrack(media *Media, codec *Codec) (*Receiver, error) // get specific track
Start() error // start receiving packets (blocking)
Stop() error // close connection
}
The tester uses:
GetMedias() -- to list codecs (H264, AAC, etc.)GetTrack() + Start() -- to capture screenshot (keyframe)Stop() -- to clean upgetScreenshot(prod) is called after successful Dialmagic.NewKeyframe() consumerprod.GetTrack()prod.Start() in goroutine (blocking -- reads packets)cons.WriteTo() with 10s timeoutjpegSize(jpeg) extracts width and height from JPEG SOF0/SOF2 markerResult.Width and Result.HeightThis works automatically for ANY protocol that returns a valid core.Producer. You do NOT need to implement screenshot or resolution logic per protocol.
type Result struct {
Source string `json:"source"`
Screenshot string `json:"screenshot,omitempty"`
Codecs []string `json:"codecs,omitempty"`
Width int `json:"width,omitempty"` // from JPEG screenshot
Height int `json:"height,omitempty"` // from JPEG screenshot
LatencyMs int64 `json:"latency_ms,omitempty"`
Skipped bool `json:"skipped,omitempty"`
}
Resolution is extracted from the JPEG screenshot, not from SDP or protocol-specific data. This means width/height are only available when a screenshot was successfully captured. The frontend uses these values to classify streams as Main (HD) or Sub (SD).
Captures first video keyframe from any Producer. Supports H264, H265, JPEG, MJPEG. The tester uses this -- you never call it directly from a protocol handler.
Simple Dial (most protocols):
// pkg/bubble/client.go
func Dial(rawURL string) (core.Producer, error) {
// parse URL, connect, return producer
}
Client with setup (rtsp):
// pkg/rtsp/client.go
conn := rtsp.NewClient(rawURL)
conn.Backchannel = false // optional setup
conn.Dial() // TCP connect
conn.Describe() // RTSP DESCRIBE (gets SDP)
// conn is now a Producer
HTTP-based (complex -- content type detection):
// pkg/magic/producer.go
// Opens HTTP connection, detects Content-Type:
// - multipart/x-mixed-replace -> MJPEG
// - image/jpeg -> single JPEG frame
// - application/vnd.apple.mpegurl -> HLS
// - video/mp2t -> MPEG-TS
// - etc.
Many protocols use pkg/tcp for low-level connection:
tcp.Dial(rawURL) -- TCP connect with timeouttcp.Client -- HTTP client with digest/basic authsource.godefaultPorts in streams.go (if not already there)source.go init() or new filego build ./...