Add a new device type detector to the Strix probe system. Covers adding new probers, result types, and detector functions.
You are adding a new device type detector to the Strix probe system. The probe system runs when a user enters an IP address -- it discovers what's at that IP and determines the device type. The device type drives the frontend flow.
The detector name is provided as argument (e.g. /add_probe_detector_strix onvif). If no argument, use AskUserQuestion to ask which detector to add.
/home/user/Strix/home/user/go2rtcBefore writing anything, read these files COMPLETELY:
/home/user/Strix/internal/probe/probe.go -- glue: Init(), runProbe(), detectors, API handler
/home/user/Strix/pkg/probe/models.go -- all data structures (Response, Probes, result types)
/home/user/Strix/pkg/probe/ping.go -- prober example: ICMP ping
/home/user/Strix/pkg/probe/ports.go -- prober example: TCP port scan
/home/user/Strix/pkg/probe/arp.go -- prober example: ARP lookup
/home/user/Strix/pkg/probe/dns.go -- prober example: reverse DNS
/home/user/Strix/pkg/probe/http.go -- prober example: HTTP HEAD request
/home/user/Strix/pkg/probe/mdns.go -- prober example: HomeKit mDNS query
/home/user/Strix/pkg/probe/oui.go -- prober example: OUI vendor lookup
Read ALL of them. Every prober is different. Understand the full picture before proceeding.
The probe has three layers:
Layer 1: Probers (pkg/probe/)
Pure functions that gather raw data about an IP address. Each runs in parallel with a shared 100ms timeout context. They do NOT interpret results -- just collect facts.
Current probers:
Ping() -- ICMP echo, returns latencyScanPorts() -- TCP connect to all known camera ports, returns open portsReverseDNS() -- reverse DNS lookup, returns hostnameLookupARP() -- reads /proc/net/arp, returns MAC addressLookupOUI() -- looks up MAC prefix in SQLite, returns vendor nameProbeHTTP() -- HTTP HEAD to ports 80/8080, returns status + server headerQueryHAP() -- mDNS query for HomeKit Accessory Protocol, returns device infoEvery prober writes its result into resp.Probes.{Name} via mutex.
Layer 2: Detectors (internal/probe/probe.go)
Functions registered in the detectors slice. They run AFTER all probers complete. Each detector receives the full *probe.Response with all probe results and returns a device type string (or empty string to pass).
var detectors []func(*probe.Response) string
Detectors are checked in order. First non-empty result wins and sets resp.Type.
Default type is "standard". If device is unreachable, type is "unreachable".
Layer 3: API (internal/probe/probe.go)
GET /api/probe?ip=192.168.1.100 returns the full Response JSON. The frontend uses type field to decide which UI flow to show.
IP address
|
v
[All probers run in parallel, 100ms timeout]
|
v
probe.Response filled with results
|
v
[Detectors run in order on the Response]
|
v
resp.Type = "homekit" | "standard" | "unreachable" | ...
|
v
JSON response to frontend
{
"ip": "192.168.1.100",
"reachable": true,
"latency_ms": 2.5,
"type": "homekit",
"probes": {
"ping": {"latency_ms": 2.5},
"ports": {"open": [80, 554, 5353]},
"dns": {"hostname": "camera.local"},
"arp": {"mac": "C0:56:E3:AA:BB:CC", "vendor": "Hikvision"},
"mdns": {
"name": "My Camera",
"device_id": "AA:BB:CC:DD:EE:FF",
"model": "Camera 1080p",
"category": "camera",
"paired": false,
"port": 80
},
"http": {"port": 80, "status_code": 200, "server": "nginx"}
}
}
Use AskUserQuestion to discuss with the user. There are two scenarios:
The detector can determine device type from data already collected by existing probers. No new prober needed.
Examples:
In this case: skip to STEP 3.
Need to collect new data that existing probers don't provide. Requires adding a new prober to pkg/probe/ and a new result type to models.go.
Examples:
In this case: proceed to STEP 2.
Edit /home/user/Strix/pkg/probe/models.go:
type {Name}Result struct {
// fields specific to this probe
}
Probes struct:type Probes struct {
Ping *PingResult `json:"ping"`
Ports *PortsResult `json:"ports"`
DNS *DNSResult `json:"dns"`
ARP *ARPResult `json:"arp"`
MDNS *MDNSResult `json:"mdns"`
HTTP *HTTPResult `json:"http"`
{Name} *{Name}Result `json:"{name}"` // add here
}
Create /home/user/Strix/pkg/probe/{name}.go.
Rules:
context.Context and ip string as first params(*{Name}Result, error)nil, nil when device doesn't support this (NOT an error)Pattern:
package probe
import "context"
func Probe{Name}(ctx context.Context, ip string) (*{Name}Result, error) {
// respect context deadline
deadline, ok := ctx.Deadline()
if !ok {
// set sensible default
}
// do the probe work...
// not supported = nil, nil (not an error)
// found = &{Name}Result{...}, nil
// actual error = nil, err
}
Edit /home/user/Strix/internal/probe/probe.go, add to runProbe() alongside other probers:
run(func() {
r, _ := probe.Probe{Name}(ctx, ip)
mu.Lock()
resp.Probes.{Name} = r
mu.Unlock()
})
All probers run in parallel inside the same run() pattern. The mutex protects writes to resp.Probes.
Edit /home/user/Strix/internal/probe/probe.go, add detector in Init():
// {Name} detector
detectors = append(detectors, func(r *probe.Response) string {
// check probe results to determine device type
// return type string or "" to pass
if r.Probes.{Something} != nil && {condition} {
return "{type_name}"
}
return ""
})
"homekit", "onvif", "tapo", etc."" (empty) to pass to the next detectorThe type string is used by the frontend to select UI flow:
"unreachable" -- device not found (set automatically, don't return this)"standard" -- default, normal camera (set automatically if no detector matches)"homekit" -- Apple HomeKit devicecd /home/user/Strix
go build ./...
If it compiles, rebuild Docker and test:
docker build -t strix:test .
docker rm -f strix
docker run -d --name strix --network host --restart unless-stopped strix:test
sleep 2
# test probe on a known device
curl -s "http://localhost:4567/api/probe?ip={DEVICE_IP}" | python3 -m json.tool
Verify:
probes object (if new prober added)type field correctly identifies the devicedocker logs strixcd /home/user/Strix
git add -A
git commit -m "Add {name} probe detector"
git push origin develop
context.Context as first param for anything with I/Onil, nil for "not applicable" (not an error)conn, resp, bufomitempty使用 Arthas 的 watch/trace 获取 EagleEye traceId / 获取请求的 traceId