Add ONVIF device service endpoint discovery for PTZ control
This commit is contained in:
@@ -29,86 +29,186 @@ func NewONVIFDiscovery(logger interface{ Debug(string, ...any); Error(string, er
|
||||
|
||||
// DiscoverStreamsForIP discovers all possible streams for a given IP
|
||||
func (o *ONVIFDiscovery) DiscoverStreamsForIP(ctx context.Context, ip, username, password string) ([]models.DiscoveredStream, error) {
|
||||
o.logger.Debug("=== ONVIF DiscoverStreamsForIP STARTED ===",
|
||||
"ip", ip,
|
||||
"username", username,
|
||||
"password_len", len(password))
|
||||
|
||||
// Clean IP (remove port if present)
|
||||
if idx := strings.IndexByte(ip, ':'); idx > 0 {
|
||||
o.logger.Debug("cleaning IP address", "original", ip, "cleaned", ip[:idx])
|
||||
ip = ip[:idx]
|
||||
}
|
||||
|
||||
var allStreams []models.DiscoveredStream
|
||||
|
||||
// Try real ONVIF discovery first
|
||||
o.logger.Debug(">>> Starting ONVIF device discovery", "ip", ip)
|
||||
onvifStreams := o.discoverViaONVIF(ctx, ip, username, password)
|
||||
o.logger.Debug("<<< ONVIF device discovery completed", "streams_found", len(onvifStreams))
|
||||
|
||||
if len(onvifStreams) > 0 {
|
||||
o.logger.Debug("ONVIF streams details:")
|
||||
for i, stream := range onvifStreams {
|
||||
o.logger.Debug(" ONVIF stream found",
|
||||
"index", i,
|
||||
"url", stream.URL,
|
||||
"protocol", stream.Protocol,
|
||||
"port", stream.Port,
|
||||
"type", stream.Type)
|
||||
}
|
||||
}
|
||||
allStreams = append(allStreams, onvifStreams...)
|
||||
|
||||
// Add common RTSP streams
|
||||
o.logger.Debug(">>> Adding common RTSP streams", "ip", ip)
|
||||
commonStreams := o.getCommonRTSPStreams(ip, username, password)
|
||||
o.logger.Debug("<<< Common RTSP streams added", "count", len(commonStreams))
|
||||
allStreams = append(allStreams, commonStreams...)
|
||||
|
||||
o.logger.Debug("collected streams", "onvif", len(onvifStreams), "common", len(commonStreams), "total", len(allStreams))
|
||||
o.logger.Debug("=== ONVIF DiscoverStreamsForIP COMPLETED ===",
|
||||
"onvif_streams", len(onvifStreams),
|
||||
"common_streams", len(commonStreams),
|
||||
"total_streams", len(allStreams))
|
||||
|
||||
return allStreams, nil
|
||||
}
|
||||
|
||||
// discoverViaONVIF performs real ONVIF discovery
|
||||
func (o *ONVIFDiscovery) discoverViaONVIF(ctx context.Context, ip, username, password string) []models.DiscoveredStream {
|
||||
o.logger.Debug(">>> discoverViaONVIF STARTED", "ip", ip)
|
||||
var streams []models.DiscoveredStream
|
||||
|
||||
// Try standard ONVIF ports
|
||||
ports := []int{80, 8080, 8000}
|
||||
o.logger.Debug("Will try ONVIF ports", "ports", ports)
|
||||
|
||||
for portIdx, port := range ports {
|
||||
o.logger.Debug("--- Trying ONVIF port ---",
|
||||
"port_index", portIdx+1,
|
||||
"total_ports", len(ports),
|
||||
"port", port)
|
||||
|
||||
for _, port := range ports {
|
||||
// Create timeout context for ONVIF connection
|
||||
onvifCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
xaddr := fmt.Sprintf("%s:%d", ip, port)
|
||||
|
||||
o.logger.Debug("trying ONVIF connection", "xaddr", xaddr)
|
||||
o.logger.Debug("Creating ONVIF device",
|
||||
"xaddr", xaddr,
|
||||
"username", username,
|
||||
"has_password", password != "")
|
||||
|
||||
// Create ONVIF device
|
||||
startTime := time.Now()
|
||||
dev, err := onvif.NewDevice(onvif.DeviceParams{
|
||||
Xaddr: xaddr,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
if err != nil {
|
||||
o.logger.Debug("ONVIF device creation failed", "xaddr", xaddr, "error", err.Error())
|
||||
o.logger.Debug("❌ ONVIF device creation FAILED",
|
||||
"xaddr", xaddr,
|
||||
"error", err.Error(),
|
||||
"elapsed", elapsed.String())
|
||||
continue
|
||||
}
|
||||
|
||||
o.logger.Debug("✅ ONVIF device created successfully",
|
||||
"xaddr", xaddr,
|
||||
"elapsed", elapsed.String())
|
||||
|
||||
// Try to get profiles with context
|
||||
o.logger.Debug("Getting media profiles...", "xaddr", xaddr)
|
||||
profileStreams := o.getProfileStreams(onvifCtx, dev, ip)
|
||||
|
||||
if len(profileStreams) > 0 {
|
||||
// Add ONVIF device service endpoint
|
||||
deviceServiceURL := fmt.Sprintf("http://%s/onvif/device_service", xaddr)
|
||||
streams = append(streams, models.DiscoveredStream{
|
||||
URL: deviceServiceURL,
|
||||
Type: "ONVIF",
|
||||
Protocol: "http",
|
||||
Port: port,
|
||||
Working: true, // Mark as working since ONVIF connection succeeded
|
||||
Metadata: map[string]interface{}{
|
||||
"source": "onvif",
|
||||
"description": "ONVIF Device Service - used for PTZ control and device management",
|
||||
},
|
||||
})
|
||||
|
||||
// Add profile streams
|
||||
streams = append(streams, profileStreams...)
|
||||
o.logger.Debug("ONVIF discovery successful", "xaddr", xaddr, "profiles", len(profileStreams))
|
||||
|
||||
o.logger.Debug("🎉 ONVIF discovery SUCCESSFUL!",
|
||||
"xaddr", xaddr,
|
||||
"device_service", deviceServiceURL,
|
||||
"profiles_found", len(profileStreams))
|
||||
|
||||
// Log device service
|
||||
o.logger.Debug(" Device Service",
|
||||
"url", deviceServiceURL)
|
||||
|
||||
// Log each profile
|
||||
for i, stream := range profileStreams {
|
||||
o.logger.Debug(" Profile stream",
|
||||
"index", i+1,
|
||||
"url", stream.URL,
|
||||
"metadata", stream.Metadata)
|
||||
}
|
||||
break // Found working port, stop trying
|
||||
} else {
|
||||
o.logger.Debug("⚠️ No profiles returned from port", "xaddr", xaddr)
|
||||
}
|
||||
}
|
||||
|
||||
o.logger.Debug("<<< discoverViaONVIF COMPLETED",
|
||||
"total_streams_found", len(streams))
|
||||
|
||||
return streams
|
||||
}
|
||||
|
||||
// getProfileStreams gets stream URIs from media profiles
|
||||
func (o *ONVIFDiscovery) getProfileStreams(ctx context.Context, dev *onvif.Device, ip string) []models.DiscoveredStream {
|
||||
o.logger.Debug(">>> getProfileStreams STARTED", "ip", ip)
|
||||
var streams []models.DiscoveredStream
|
||||
|
||||
// Get media profiles
|
||||
o.logger.Debug("Calling GetProfiles ONVIF method...")
|
||||
getProfilesReq := media.GetProfiles{}
|
||||
startTime := time.Now()
|
||||
profilesResp, err := dev.CallMethod(getProfilesReq)
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
if err != nil {
|
||||
o.logger.Debug("failed to get ONVIF profiles", "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to call GetProfiles",
|
||||
"error", err.Error(),
|
||||
"elapsed", elapsed.String())
|
||||
return streams
|
||||
}
|
||||
defer profilesResp.Body.Close()
|
||||
|
||||
o.logger.Debug("✅ GetProfiles call successful",
|
||||
"elapsed", elapsed.String(),
|
||||
"status_code", profilesResp.StatusCode)
|
||||
|
||||
// Read and parse XML response
|
||||
o.logger.Debug("Reading response body...")
|
||||
body, err := io.ReadAll(profilesResp.Body)
|
||||
if err != nil {
|
||||
o.logger.Debug("failed to read profiles response", "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to read profiles response",
|
||||
"error", err.Error())
|
||||
return streams
|
||||
}
|
||||
|
||||
o.logger.Debug("Response body read",
|
||||
"body_length", len(body),
|
||||
"body_preview", string(body[:min(200, len(body))]))
|
||||
|
||||
// Parse SOAP envelope
|
||||
o.logger.Debug("Parsing SOAP envelope...")
|
||||
var envelope struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body struct {
|
||||
@@ -117,14 +217,29 @@ func (o *ONVIFDiscovery) getProfileStreams(ctx context.Context, dev *onvif.Devic
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(body, &envelope); err != nil {
|
||||
o.logger.Debug("failed to parse profiles response", "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to parse profiles response",
|
||||
"error", err.Error())
|
||||
return streams
|
||||
}
|
||||
|
||||
profileCount := len(envelope.Body.GetProfilesResponse.Profiles)
|
||||
o.logger.Debug("✅ SOAP envelope parsed successfully",
|
||||
"profiles_count", profileCount)
|
||||
|
||||
// Get stream URI for each profile
|
||||
for _, profile := range envelope.Body.GetProfilesResponse.Profiles {
|
||||
for i, profile := range envelope.Body.GetProfilesResponse.Profiles {
|
||||
o.logger.Debug("Processing profile",
|
||||
"index", i+1,
|
||||
"total", profileCount,
|
||||
"token", string(profile.Token),
|
||||
"name", string(profile.Name))
|
||||
|
||||
streamURI := o.getStreamURI(dev, string(profile.Token))
|
||||
if streamURI != "" {
|
||||
o.logger.Debug("✅ Got stream URI for profile",
|
||||
"profile_token", string(profile.Token),
|
||||
"stream_uri", streamURI)
|
||||
|
||||
streams = append(streams, models.DiscoveredStream{
|
||||
URL: streamURI,
|
||||
Type: "FFMPEG",
|
||||
@@ -137,14 +252,29 @@ func (o *ONVIFDiscovery) getProfileStreams(ctx context.Context, dev *onvif.Devic
|
||||
"profile_name": string(profile.Name),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
o.logger.Debug("⚠️ Failed to get stream URI for profile",
|
||||
"profile_token", string(profile.Token))
|
||||
}
|
||||
}
|
||||
|
||||
o.logger.Debug("<<< getProfileStreams COMPLETED",
|
||||
"streams_collected", len(streams))
|
||||
|
||||
return streams
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// getStreamURI retrieves stream URI for a profile
|
||||
func (o *ONVIFDiscovery) getStreamURI(dev *onvif.Device, profileToken string) string {
|
||||
o.logger.Debug(">>> getStreamURI STARTED", "profile_token", profileToken)
|
||||
|
||||
stream := xsdonvif.StreamType("RTP-Unicast")
|
||||
protocol := xsdonvif.TransportProtocol("RTSP")
|
||||
token := xsdonvif.ReferenceToken(profileToken)
|
||||
@@ -159,20 +289,37 @@ func (o *ONVIFDiscovery) getStreamURI(dev *onvif.Device, profileToken string) st
|
||||
},
|
||||
}
|
||||
|
||||
o.logger.Debug("Calling GetStreamUri ONVIF method...", "profile_token", profileToken)
|
||||
startTime := time.Now()
|
||||
resp, err := dev.CallMethod(getStreamURIReq)
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
if err != nil {
|
||||
o.logger.Debug("failed to get stream URI", "profile", profileToken, "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to get stream URI",
|
||||
"profile", profileToken,
|
||||
"error", err.Error(),
|
||||
"elapsed", elapsed.String())
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
o.logger.Debug("✅ GetStreamUri call successful",
|
||||
"profile", profileToken,
|
||||
"elapsed", elapsed.String(),
|
||||
"status_code", resp.StatusCode)
|
||||
|
||||
// Read and parse XML response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
o.logger.Debug("failed to read stream URI response", "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to read stream URI response",
|
||||
"error", err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
o.logger.Debug("Response body read",
|
||||
"body_length", len(body),
|
||||
"body_preview", string(body[:min(200, len(body))]))
|
||||
|
||||
// Parse SOAP envelope
|
||||
var envelope struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
@@ -182,11 +329,16 @@ func (o *ONVIFDiscovery) getStreamURI(dev *onvif.Device, profileToken string) st
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(body, &envelope); err != nil {
|
||||
o.logger.Debug("failed to parse stream URI response", "error", err.Error())
|
||||
o.logger.Debug("❌ Failed to parse stream URI response",
|
||||
"error", err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(envelope.Body.GetStreamUriResponse.MediaUri.Uri)
|
||||
streamURI := string(envelope.Body.GetStreamUriResponse.MediaUri.Uri)
|
||||
o.logger.Debug("<<< getStreamURI COMPLETED",
|
||||
"stream_uri", streamURI)
|
||||
|
||||
return streamURI
|
||||
}
|
||||
|
||||
// getCommonRTSPStreams returns common RTSP stream URLs
|
||||
|
||||
Reference in New Issue
Block a user