Files
onvif-go/examples/test-real-camera-all/main.go
T
0x524a 0551d28f61 feat: add comprehensive tests for Bosch FLEXIDOME indoor 5100i IR camera
- Introduced new test files for device and media service operations using real camera responses.
- Implemented tests for GetDeviceInformation, GetMediaServiceCapabilities, and user management functions.
- Enhanced documentation with a detailed testing flow and coverage reports.
- Added JSON test reports for tracking operation success and response times.
- Updated the README and other documentation to reflect new testing capabilities and structure.
2025-12-02 00:43:17 -05:00

604 lines
20 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/0x524a/onvif-go"
)
const (
cameraEndpoint = "192.168.1.201"
username = "service"
password = "Service.1234"
)
type TestResult struct {
Operation string `json:"operation"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
Response interface{} `json:"response,omitempty"`
ResponseTime string `json:"response_time"`
}
type CameraTestReport struct {
DeviceInfo struct {
Manufacturer string `json:"manufacturer"`
Model string `json:"model"`
FirmwareVersion string `json:"firmware_version"`
SerialNumber string `json:"serial_number"`
HardwareID string `json:"hardware_id"`
} `json:"device_info"`
TestResults []TestResult `json:"test_results"`
Timestamp string `json:"timestamp"`
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
report := CameraTestReport{
Timestamp: time.Now().Format(time.RFC3339),
}
// Try different endpoint formats and common ONVIF ports
endpoints := []string{
cameraEndpoint, // http://192.168.1.230/onvif/device_service
"http://" + cameraEndpoint, // http://192.168.1.230/onvif/device_service
"https://" + cameraEndpoint, // https://192.168.1.230/onvif/device_service
cameraEndpoint + ":80", // http://192.168.1.230:80/onvif/device_service
cameraEndpoint + ":443", // http://192.168.1.230:443/onvif/device_service
cameraEndpoint + ":8080", // http://192.168.1.230:8080/onvif/device_service
cameraEndpoint + ":554", // http://192.168.1.230:554/onvif/device_service
cameraEndpoint + ":8000", // http://192.168.1.230:8000/onvif/device_service
"http://" + cameraEndpoint + ":80",
"https://" + cameraEndpoint + ":443",
"http://" + cameraEndpoint + ":8080",
"https://" + cameraEndpoint + ":8443",
"http://" + cameraEndpoint + "/onvif/device_service",
"https://" + cameraEndpoint + "/onvif/device_service",
"http://" + cameraEndpoint + ":8080/onvif/device_service",
}
var client *onvif.Client
var deviceInfo *onvif.DeviceInformation
var err error
fmt.Println("📡 Trying to connect to camera...")
for i, endpoint := range endpoints {
fmt.Printf(" Attempt %d: %s\n", i+1, endpoint)
opts := []onvif.ClientOption{
onvif.WithCredentials(username, password),
onvif.WithTimeout(10 * time.Second),
}
// Add insecure skip verify for HTTPS endpoints
if strings.HasPrefix(endpoint, "https://") {
opts = append(opts, onvif.WithInsecureSkipVerify())
}
client, err = onvif.NewClient(endpoint, opts...)
if err != nil {
fmt.Printf(" ❌ Failed to create client: %v\n", err)
continue
}
// Try to get device information
deviceInfo, err = client.GetDeviceInformation(ctx)
if err != nil {
fmt.Printf(" ❌ Failed to connect: %v\n", err)
continue
}
fmt.Printf(" ✅ Connected successfully!\n")
break
}
if err != nil || deviceInfo == nil {
log.Fatalf("Failed to connect to camera with any endpoint format. Last error: %v", err)
}
report.DeviceInfo.Manufacturer = deviceInfo.Manufacturer
report.DeviceInfo.Model = deviceInfo.Model
report.DeviceInfo.FirmwareVersion = deviceInfo.FirmwareVersion
report.DeviceInfo.SerialNumber = deviceInfo.SerialNumber
report.DeviceInfo.HardwareID = deviceInfo.HardwareID
fmt.Printf("✅ Camera: %s %s (FW: %s)\n", deviceInfo.Manufacturer, deviceInfo.Model, deviceInfo.FirmwareVersion)
// Initialize to discover service endpoints
fmt.Println("🔍 Initializing service endpoints...")
if err := client.Initialize(ctx); err != nil {
log.Fatalf("Failed to initialize: %v", err)
}
// Test all device operations
fmt.Println("\n🔧 Testing Device Operations...")
testDeviceOperations(ctx, client, &report)
// Test all media operations
fmt.Println("\n🎬 Testing Media Operations...")
testMediaOperations(ctx, client, &report)
// Save report
reportJSON, err := json.MarshalIndent(report, "", " ")
if err != nil {
log.Fatalf("Failed to marshal report: %v", err)
}
// Create test-reports directory if it doesn't exist
reportDir := "../../test-reports"
if err := os.MkdirAll(reportDir, 0755); err != nil {
log.Fatalf("Failed to create test-reports directory: %v", err)
}
filename := fmt.Sprintf("camera_test_report_%s_%s_%s.json",
sanitizeFilename(deviceInfo.Manufacturer),
sanitizeFilename(deviceInfo.Model),
time.Now().Format("20060102_150405"))
filepath := fmt.Sprintf("%s/%s", reportDir, filename)
if err := os.WriteFile(filepath, reportJSON, 0644); err != nil {
log.Fatalf("Failed to write report: %v", err)
}
fmt.Printf("\n✅ Test report saved to: %s\n", filepath)
}
func sanitizeFilename(s string) string {
result := ""
for _, r := range s {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
result += string(r)
} else {
result += "_"
}
}
return result
}
func testDeviceOperations(ctx context.Context, client *onvif.Client, report *CameraTestReport) {
// Test all operations
testOperation := func(name string, testFn func() (interface{}, error)) {
fmt.Printf(" Testing %s...", name)
start := time.Now()
result, err := testFn()
duration := time.Since(start)
testResult := TestResult{
Operation: name,
ResponseTime: duration.String(),
}
if err != nil {
testResult.Success = false
testResult.Error = err.Error()
fmt.Printf(" ❌ Error: %v\n", err)
} else {
testResult.Success = true
testResult.Response = result
fmt.Printf(" ✅\n")
}
report.TestResults = append(report.TestResults, testResult)
time.Sleep(200 * time.Millisecond)
}
// Basic device operations
testOperation("GetDeviceInformation", func() (interface{}, error) {
return client.GetDeviceInformation(ctx)
})
testOperation("GetCapabilities", func() (interface{}, error) {
return client.GetCapabilities(ctx)
})
testOperation("GetServiceCapabilities", func() (interface{}, error) {
return client.GetServiceCapabilities(ctx)
})
testOperation("GetServices", func() (interface{}, error) {
return client.GetServices(ctx, false)
})
testOperation("GetServicesWithCapabilities", func() (interface{}, error) {
return client.GetServices(ctx, true)
})
// System operations
testOperation("GetSystemDateAndTime", func() (interface{}, error) {
return client.GetSystemDateAndTime(ctx)
})
testOperation("GetHostname", func() (interface{}, error) {
return client.GetHostname(ctx)
})
testOperation("GetDNS", func() (interface{}, error) {
return client.GetDNS(ctx)
})
testOperation("GetNTP", func() (interface{}, error) {
return client.GetNTP(ctx)
})
// Network operations
testOperation("GetNetworkInterfaces", func() (interface{}, error) {
return client.GetNetworkInterfaces(ctx)
})
testOperation("GetNetworkProtocols", func() (interface{}, error) {
return client.GetNetworkProtocols(ctx)
})
testOperation("GetNetworkDefaultGateway", func() (interface{}, error) {
return client.GetNetworkDefaultGateway(ctx)
})
// Discovery operations
testOperation("GetDiscoveryMode", func() (interface{}, error) {
return client.GetDiscoveryMode(ctx)
})
testOperation("GetRemoteDiscoveryMode", func() (interface{}, error) {
return client.GetRemoteDiscoveryMode(ctx)
})
testOperation("GetEndpointReference", func() (interface{}, error) {
return client.GetEndpointReference(ctx)
})
// Scope operations
testOperation("GetScopes", func() (interface{}, error) {
return client.GetScopes(ctx)
})
// User operations (read-only to avoid modifying camera)
testOperation("GetUsers", func() (interface{}, error) {
return client.GetUsers(ctx)
})
// Set operations - test with caution (may modify camera state)
// Note: These are commented out to avoid modifying camera during testing
// Uncomment if you want to test write operations
// testOperation("SetDiscoveryMode", func() (interface{}, error) {
// currentMode, _ := client.GetDiscoveryMode(ctx)
// err := client.SetDiscoveryMode(ctx, currentMode) // Set to current value
// return nil, err
// })
// testOperation("SetRemoteDiscoveryMode", func() (interface{}, error) {
// currentMode, _ := client.GetRemoteDiscoveryMode(ctx)
// err := client.SetRemoteDiscoveryMode(ctx, currentMode) // Set to current value
// return nil, err
// })
// System reboot - skip to avoid rebooting camera during testing
// testOperation("SystemReboot", func() (interface{}, error) {
// return client.SystemReboot(ctx)
// })
}
func testMediaOperations(ctx context.Context, client *onvif.Client, report *CameraTestReport) {
// Get profiles and other resources first
profiles, _ := client.GetProfiles(ctx)
videoSources, _ := client.GetVideoSources(ctx)
audioOutputs, _ := client.GetAudioOutputs(ctx)
var profileToken, videoEncoderToken, audioEncoderToken, videoSourceToken, audioOutputToken string
if len(profiles) > 0 {
profileToken = profiles[0].Token
if profiles[0].VideoEncoderConfiguration != nil {
videoEncoderToken = profiles[0].VideoEncoderConfiguration.Token
}
if profiles[0].AudioEncoderConfiguration != nil {
audioEncoderToken = profiles[0].AudioEncoderConfiguration.Token
}
}
if len(videoSources) > 0 {
videoSourceToken = videoSources[0].Token
}
if len(audioOutputs) > 0 {
audioOutputToken = audioOutputs[0].Token
}
// Test all operations
testOperation := func(name string, testFn func() (interface{}, error)) {
fmt.Printf(" Testing %s...", name)
start := time.Now()
result, err := testFn()
duration := time.Since(start)
testResult := TestResult{
Operation: name,
ResponseTime: duration.String(),
}
if err != nil {
testResult.Success = false
testResult.Error = err.Error()
fmt.Printf(" ❌ Error: %v\n", err)
} else {
testResult.Success = true
testResult.Response = result
fmt.Printf(" ✅\n")
}
report.TestResults = append(report.TestResults, testResult)
time.Sleep(200 * time.Millisecond)
}
// Basic operations
testOperation("GetMediaServiceCapabilities", func() (interface{}, error) {
return client.GetMediaServiceCapabilities(ctx)
})
testOperation("GetProfiles", func() (interface{}, error) {
return client.GetProfiles(ctx)
})
testOperation("GetVideoSources", func() (interface{}, error) {
return client.GetVideoSources(ctx)
})
testOperation("GetAudioSources", func() (interface{}, error) {
return client.GetAudioSources(ctx)
})
testOperation("GetAudioOutputs", func() (interface{}, error) {
return client.GetAudioOutputs(ctx)
})
// Profile operations
if profileToken != "" {
testOperation("GetStreamURI", func() (interface{}, error) {
return client.GetStreamURI(ctx, profileToken)
})
testOperation("GetSnapshotURI", func() (interface{}, error) {
return client.GetSnapshotURI(ctx, profileToken)
})
testOperation("GetProfile", func() (interface{}, error) {
return client.GetProfile(ctx, profileToken)
})
testOperation("SetSynchronizationPoint", func() (interface{}, error) {
err := client.SetSynchronizationPoint(ctx, profileToken)
return nil, err
})
}
// Video encoder operations
if videoEncoderToken != "" {
testOperation("GetVideoEncoderConfiguration", func() (interface{}, error) {
return client.GetVideoEncoderConfiguration(ctx, videoEncoderToken)
})
testOperation("GetVideoEncoderConfigurationOptions", func() (interface{}, error) {
return client.GetVideoEncoderConfigurationOptions(ctx, videoEncoderToken)
})
testOperation("GetGuaranteedNumberOfVideoEncoderInstances", func() (interface{}, error) {
return client.GetGuaranteedNumberOfVideoEncoderInstances(ctx, videoEncoderToken)
})
}
// Audio encoder operations
if audioEncoderToken != "" {
testOperation("GetAudioEncoderConfiguration", func() (interface{}, error) {
return client.GetAudioEncoderConfiguration(ctx, audioEncoderToken)
})
}
testOperation("GetAudioEncoderConfigurationOptions", func() (interface{}, error) {
return client.GetAudioEncoderConfigurationOptions(ctx, audioEncoderToken, profileToken)
})
// Video source operations
if videoSourceToken != "" {
testOperation("GetVideoSourceModes", func() (interface{}, error) {
return client.GetVideoSourceModes(ctx, videoSourceToken)
})
}
// Audio output operations
testOperation("GetAudioOutputConfiguration", func() (interface{}, error) {
// Try to get audio output config - need to find config token
// For now, try with empty token or skip if not available
if audioOutputToken != "" {
// Try to get configuration - this may require a different approach
return nil, fmt.Errorf("audio output configuration token lookup not implemented")
}
return nil, fmt.Errorf("no audio output available")
})
testOperation("GetAudioOutputConfigurationOptions", func() (interface{}, error) {
return client.GetAudioOutputConfigurationOptions(ctx, "")
})
// Metadata operations
testOperation("GetMetadataConfigurationOptions", func() (interface{}, error) {
configToken := ""
if len(profiles) > 0 && profiles[0].MetadataConfiguration != nil {
configToken = profiles[0].MetadataConfiguration.Token
}
return client.GetMetadataConfigurationOptions(ctx, configToken, profileToken)
})
// Audio decoder operations
testOperation("GetAudioDecoderConfigurationOptions", func() (interface{}, error) {
return client.GetAudioDecoderConfigurationOptions(ctx, "")
})
// OSD operations
testOperation("GetOSDs", func() (interface{}, error) {
return client.GetOSDs(ctx, "")
})
testOperation("GetOSDOptions", func() (interface{}, error) {
return client.GetOSDOptions(ctx, "")
})
// Additional Media operations - test all implemented operations
if profileToken != "" {
// Profile management operations
testOperation("SetProfile", func() (interface{}, error) {
profile, err := client.GetProfile(ctx, profileToken)
if err != nil {
return nil, err
}
err = client.SetProfile(ctx, profile)
return nil, err
})
// Profile configuration add/remove operations
if videoEncoderToken != "" {
testOperation("AddVideoEncoderConfiguration", func() (interface{}, error) {
// Try adding to a different profile if available
if len(profiles) > 1 {
err := client.AddVideoEncoderConfiguration(ctx, profiles[1].Token, videoEncoderToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemoveVideoEncoderConfiguration", func() (interface{}, error) {
// Only test if we have multiple profiles to avoid breaking the main profile
if len(profiles) > 1 && profiles[1].VideoEncoderConfiguration != nil {
err := client.RemoveVideoEncoderConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
if audioEncoderToken != "" {
testOperation("AddAudioEncoderConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.AddAudioEncoderConfiguration(ctx, profiles[1].Token, audioEncoderToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemoveAudioEncoderConfiguration", func() (interface{}, error) {
if len(profiles) > 1 && profiles[1].AudioEncoderConfiguration != nil {
err := client.RemoveAudioEncoderConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
// Video source configuration operations
if len(profiles) > 0 && profiles[0].VideoSourceConfiguration != nil {
videoSourceConfigToken := profiles[0].VideoSourceConfiguration.Token
testOperation("AddVideoSourceConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.AddVideoSourceConfiguration(ctx, profiles[1].Token, videoSourceConfigToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemoveVideoSourceConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.RemoveVideoSourceConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
// Audio source configuration operations
if len(profiles) > 0 && profiles[0].AudioSourceConfiguration != nil {
audioSourceConfigToken := profiles[0].AudioSourceConfiguration.Token
testOperation("AddAudioSourceConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.AddAudioSourceConfiguration(ctx, profiles[1].Token, audioSourceConfigToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemoveAudioSourceConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.RemoveAudioSourceConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
// Metadata configuration operations
if len(profiles) > 0 && profiles[0].MetadataConfiguration != nil {
metadataConfigToken := profiles[0].MetadataConfiguration.Token
testOperation("GetMetadataConfiguration", func() (interface{}, error) {
return client.GetMetadataConfiguration(ctx, metadataConfigToken)
})
testOperation("AddMetadataConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.AddMetadataConfiguration(ctx, profiles[1].Token, metadataConfigToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemoveMetadataConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.RemoveMetadataConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
// PTZ configuration operations (if available)
if len(profiles) > 0 && profiles[0].PTZConfiguration != nil {
ptzConfigToken := profiles[0].PTZConfiguration.Token
testOperation("AddPTZConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.AddPTZConfiguration(ctx, profiles[1].Token, ptzConfigToken)
return nil, err
}
return nil, fmt.Errorf("only one profile available")
})
testOperation("RemovePTZConfiguration", func() (interface{}, error) {
if len(profiles) > 1 {
err := client.RemovePTZConfiguration(ctx, profiles[1].Token)
return nil, err
}
return nil, fmt.Errorf("cannot test - would break profile")
})
}
// Multicast streaming operations
testOperation("StartMulticastStreaming", func() (interface{}, error) {
err := client.StartMulticastStreaming(ctx, profileToken)
return nil, err
})
testOperation("StopMulticastStreaming", func() (interface{}, error) {
err := client.StopMulticastStreaming(ctx, profileToken)
return nil, err
})
// OSD operations (if OSD token available)
osds, _ := client.GetOSDs(ctx, "")
if len(osds) > 0 {
osdToken := osds[0].Token
testOperation("GetOSD", func() (interface{}, error) {
return client.GetOSD(ctx, osdToken)
})
}
// Video source mode operations
if videoSourceToken != "" {
testOperation("SetVideoSourceMode", func() (interface{}, error) {
modes, err := client.GetVideoSourceModes(ctx, videoSourceToken)
if err != nil || len(modes) == 0 {
return nil, fmt.Errorf("no modes available or error getting modes")
}
// Try to set to first available mode
err = client.SetVideoSourceMode(ctx, videoSourceToken, modes[0].Token)
return nil, err
})
}
}
// Create/Delete profile operations - test with caution
// Note: These are commented out to avoid creating test profiles
// Uncomment if you want to test profile creation/deletion
// testOperation("CreateProfile", func() (interface{}, error) {
// profile, err := client.CreateProfile(ctx, "TestProfile", "TestToken")
// if err != nil {
// return nil, err
// }
// // Clean up - delete the test profile
// defer func() {
// _ = client.DeleteProfile(ctx, profile.Token)
// }()
// return profile, nil
// })
}