Add or update .codecov copy.yml
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
"github.com/0x524a/onvif-go/discovery"
|
||||
)
|
||||
|
||||
// This is a comprehensive demonstration of all onvif-go features
|
||||
func main() {
|
||||
// Step 1: Discover cameras on the network
|
||||
fmt.Println("=== Step 1: Discovering ONVIF Cameras ===")
|
||||
discoverCameras()
|
||||
|
||||
// Step 2: Connect to a specific camera
|
||||
fmt.Println("\n=== Step 2: Connecting to Camera ===")
|
||||
client := connectToCamera()
|
||||
|
||||
// Step 3: Get device information
|
||||
fmt.Println("\n=== Step 3: Getting Device Information ===")
|
||||
getDeviceInfo(client)
|
||||
|
||||
// Step 4: Get media profiles and streams
|
||||
fmt.Println("\n=== Step 4: Getting Media Profiles ===")
|
||||
profiles := getMediaProfiles(client)
|
||||
|
||||
// Step 5: Control PTZ
|
||||
if len(profiles) > 0 {
|
||||
fmt.Println("\n=== Step 5: PTZ Control ===")
|
||||
controlPTZ(client, profiles[0].Token)
|
||||
}
|
||||
|
||||
// Step 6: Adjust imaging settings
|
||||
if len(profiles) > 0 && profiles[0].VideoSourceConfiguration != nil {
|
||||
fmt.Println("\n=== Step 6: Adjusting Imaging Settings ===")
|
||||
adjustImaging(client, profiles[0].VideoSourceConfiguration.SourceToken)
|
||||
}
|
||||
|
||||
fmt.Println("\n=== All operations completed successfully! ===")
|
||||
}
|
||||
|
||||
// discoverCameras demonstrates network discovery
|
||||
func discoverCameras() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
devices, err := discovery.Discover(ctx, 5*time.Second)
|
||||
if err != nil {
|
||||
log.Printf("Discovery error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d device(s):\n", len(devices))
|
||||
for i, device := range devices {
|
||||
fmt.Printf(" [%d] %s at %s\n", i+1, device.GetName(), device.GetDeviceEndpoint())
|
||||
}
|
||||
}
|
||||
|
||||
// connectToCamera creates and initializes a client
|
||||
func connectToCamera() *onvif.Client {
|
||||
// Replace with your camera's details
|
||||
endpoint := "http://192.168.1.100/onvif/device_service"
|
||||
username := "admin"
|
||||
password := "password"
|
||||
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
// Initialize to discover service endpoints
|
||||
ctx := context.Background()
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("Failed to initialize: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Connected to: %s\n", endpoint)
|
||||
return client
|
||||
}
|
||||
|
||||
// getDeviceInfo retrieves and displays device information
|
||||
func getDeviceInfo(client *onvif.Client) {
|
||||
ctx := context.Background()
|
||||
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get device info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Manufacturer: %s\n", info.Manufacturer)
|
||||
fmt.Printf("Model: %s\n", info.Model)
|
||||
fmt.Printf("Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf("Serial: %s\n", info.SerialNumber)
|
||||
|
||||
// Get capabilities
|
||||
caps, err := client.GetCapabilities(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get capabilities: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nSupported Services:")
|
||||
if caps.Media != nil {
|
||||
fmt.Printf(" ✓ Media (Streaming)\n")
|
||||
}
|
||||
if caps.PTZ != nil {
|
||||
fmt.Printf(" ✓ PTZ (Pan/Tilt/Zoom)\n")
|
||||
}
|
||||
if caps.Imaging != nil {
|
||||
fmt.Printf(" ✓ Imaging (Image Settings)\n")
|
||||
}
|
||||
if caps.Events != nil {
|
||||
fmt.Printf(" ✓ Events\n")
|
||||
}
|
||||
}
|
||||
|
||||
// getMediaProfiles retrieves media profiles and stream URIs
|
||||
func getMediaProfiles(client *onvif.Client) []*onvif.Profile {
|
||||
ctx := context.Background()
|
||||
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get profiles: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d profile(s):\n", len(profiles))
|
||||
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf("\nProfile [%d]: %s\n", i+1, profile.Name)
|
||||
|
||||
// Video configuration
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" Encoding: %s\n", profile.VideoEncoderConfiguration.Encoding)
|
||||
if profile.VideoEncoderConfiguration.Resolution != nil {
|
||||
fmt.Printf(" Resolution: %dx%d\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height)
|
||||
}
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Stream URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
||||
}
|
||||
|
||||
// Get snapshot URI
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Snapshot URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
||||
}
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
||||
|
||||
// controlPTZ demonstrates PTZ operations
|
||||
func controlPTZ(client *onvif.Client, profileToken string) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get current status
|
||||
status, err := client.GetStatus(ctx, profileToken)
|
||||
if err != nil {
|
||||
log.Printf("PTZ not supported: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("PTZ is supported!")
|
||||
|
||||
if status.Position != nil && status.Position.PanTilt != nil {
|
||||
fmt.Printf("Current Position: Pan=%.2f, Tilt=%.2f\n",
|
||||
status.Position.PanTilt.X,
|
||||
status.Position.PanTilt.Y)
|
||||
}
|
||||
|
||||
// Get presets
|
||||
presets, err := client.GetPresets(ctx, profileToken)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get presets: %v", err)
|
||||
} else {
|
||||
fmt.Printf("Available Presets: %d\n", len(presets))
|
||||
for _, preset := range presets {
|
||||
fmt.Printf(" - %s\n", preset.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Demonstrate movement (commented out to avoid camera movement)
|
||||
/*
|
||||
// Move right
|
||||
velocity := &onvif.PTZSpeed{
|
||||
PanTilt: &onvif.Vector2D{X: 0.3, Y: 0.0},
|
||||
}
|
||||
timeout := "PT1S"
|
||||
if err := client.ContinuousMove(ctx, profileToken, velocity, &timeout); err != nil {
|
||||
log.Printf("Move failed: %v", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
client.Stop(ctx, profileToken, true, false)
|
||||
|
||||
// Return to home
|
||||
home := &onvif.PTZVector{
|
||||
PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0},
|
||||
}
|
||||
client.AbsoluteMove(ctx, profileToken, home, nil)
|
||||
*/
|
||||
|
||||
fmt.Println("PTZ operations available (commented out in demo)")
|
||||
}
|
||||
|
||||
// adjustImaging demonstrates imaging settings
|
||||
func adjustImaging(client *onvif.Client, videoSourceToken string) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get current settings
|
||||
settings, err := client.GetImagingSettings(ctx, videoSourceToken)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get imaging settings: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Current Imaging Settings:")
|
||||
if settings.Brightness != nil {
|
||||
fmt.Printf(" Brightness: %.1f\n", *settings.Brightness)
|
||||
}
|
||||
if settings.Contrast != nil {
|
||||
fmt.Printf(" Contrast: %.1f\n", *settings.Contrast)
|
||||
}
|
||||
if settings.ColorSaturation != nil {
|
||||
fmt.Printf(" Saturation: %.1f\n", *settings.ColorSaturation)
|
||||
}
|
||||
if settings.Sharpness != nil {
|
||||
fmt.Printf(" Sharpness: %.1f\n", *settings.Sharpness)
|
||||
}
|
||||
|
||||
if settings.Exposure != nil {
|
||||
fmt.Printf(" Exposure Mode: %s\n", settings.Exposure.Mode)
|
||||
}
|
||||
|
||||
if settings.Focus != nil {
|
||||
fmt.Printf(" Focus Mode: %s\n", settings.Focus.AutoFocusMode)
|
||||
}
|
||||
|
||||
if settings.WhiteBalance != nil {
|
||||
fmt.Printf(" White Balance: %s\n", settings.WhiteBalance.Mode)
|
||||
}
|
||||
|
||||
// Demonstrate setting adjustment (commented out to avoid changes)
|
||||
/*
|
||||
// Adjust brightness
|
||||
newBrightness := 55.0
|
||||
settings.Brightness = &newBrightness
|
||||
|
||||
if err := client.SetImagingSettings(ctx, videoSourceToken, settings, true); err != nil {
|
||||
log.Printf("Failed to set imaging settings: %v", err)
|
||||
} else {
|
||||
fmt.Println("\nImaging settings updated!")
|
||||
}
|
||||
*/
|
||||
|
||||
fmt.Println("Imaging adjustment available (commented out in demo)")
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Camera connection details
|
||||
endpoint := "http://192.168.1.201/onvif/device_service"
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
|
||||
fmt.Println("=== Comprehensive ONVIF Camera Test ===")
|
||||
fmt.Println("Connecting to:", endpoint)
|
||||
fmt.Println()
|
||||
|
||||
// Create client
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Test 1: Get Device Information
|
||||
fmt.Println("=== Test 1: GetDeviceInformation ===")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Manufacturer: %s\n", info.Manufacturer)
|
||||
fmt.Printf("✓ Model: %s\n", info.Model)
|
||||
fmt.Printf("✓ Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf("✓ Serial Number: %s\n", info.SerialNumber)
|
||||
fmt.Printf("✓ Hardware ID: %s\n", info.HardwareID)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 2: Get System Date and Time
|
||||
fmt.Println("=== Test 2: GetSystemDateAndTime ===")
|
||||
dateTime, err := client.GetSystemDateAndTime(ctx)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ System Date/Time: %+v\n", dateTime)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 3: Get Capabilities
|
||||
fmt.Println("=== Test 3: GetCapabilities ===")
|
||||
capabilities, err := client.GetCapabilities(ctx)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✓ Capabilities retrieved successfully:")
|
||||
if capabilities.Device != nil {
|
||||
fmt.Printf(" - Device: %s\n", capabilities.Device.XAddr)
|
||||
}
|
||||
if capabilities.Media != nil {
|
||||
fmt.Printf(" - Media: %s\n", capabilities.Media.XAddr)
|
||||
}
|
||||
if capabilities.PTZ != nil {
|
||||
fmt.Printf(" - PTZ: %s\n", capabilities.PTZ.XAddr)
|
||||
}
|
||||
if capabilities.Imaging != nil {
|
||||
fmt.Printf(" - Imaging: %s\n", capabilities.Imaging.XAddr)
|
||||
}
|
||||
if capabilities.Events != nil {
|
||||
fmt.Printf(" - Events: %s\n", capabilities.Events.XAddr)
|
||||
}
|
||||
if capabilities.Analytics != nil {
|
||||
fmt.Printf(" - Analytics: %s\n", capabilities.Analytics.XAddr)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Initialize client to discover service endpoints
|
||||
fmt.Println("=== Test 4: Initialize (Discover Services) ===")
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✓ Services discovered successfully")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 5: Get Media Profiles
|
||||
fmt.Println("=== Test 5: GetProfiles ===")
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Found %d profile(s)\n", len(profiles))
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf(" Profile %d: %s (Token: %s)\n", i+1, profile.Name, profile.Token)
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" - Encoding: %s\n", profile.VideoEncoderConfiguration.Encoding)
|
||||
if profile.VideoEncoderConfiguration.Resolution != nil {
|
||||
fmt.Printf(" - Resolution: %dx%d\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 6: Get Stream URIs
|
||||
fmt.Println("=== Test 6: GetStreamURI (for first profile) ===")
|
||||
if len(profiles) > 0 {
|
||||
streamURI, err := client.GetStreamURI(ctx, profiles[0].Token)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Stream URI: %s\n", streamURI.URI)
|
||||
fmt.Printf(" - Invalid After Connect: %v\n", streamURI.InvalidAfterConnect)
|
||||
fmt.Printf(" - Invalid After Reboot: %v\n", streamURI.InvalidAfterReboot)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 7: Get Snapshot URI
|
||||
fmt.Println("=== Test 7: GetSnapshotURI (for first profile) ===")
|
||||
if len(profiles) > 0 {
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx, profiles[0].Token)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Snapshot URI: %s\n", snapshotURI.URI)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 8: Get Video Encoder Configuration
|
||||
fmt.Println("=== Test 8: GetVideoEncoderConfiguration ===")
|
||||
if len(profiles) > 0 && profiles[0].VideoEncoderConfiguration != nil {
|
||||
config, err := client.GetVideoEncoderConfiguration(ctx, profiles[0].VideoEncoderConfiguration.Token)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Video Encoder Configuration:\n")
|
||||
fmt.Printf(" - Name: %s\n", config.Name)
|
||||
fmt.Printf(" - Encoding: %s\n", config.Encoding)
|
||||
if config.Resolution != nil {
|
||||
fmt.Printf(" - Resolution: %dx%d\n", config.Resolution.Width, config.Resolution.Height)
|
||||
}
|
||||
fmt.Printf(" - Quality: %.1f\n", config.Quality)
|
||||
if config.RateControl != nil {
|
||||
fmt.Printf(" - Frame Rate Limit: %d\n", config.RateControl.FrameRateLimit)
|
||||
fmt.Printf(" - Bitrate Limit: %d\n", config.RateControl.BitrateLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 9: PTZ Operations (if PTZ is available)
|
||||
fmt.Println("=== Test 9: PTZ Operations ===")
|
||||
if len(profiles) > 0 && profiles[0].PTZConfiguration != nil {
|
||||
fmt.Println("PTZ configuration detected, testing PTZ operations...")
|
||||
|
||||
// Get PTZ Status
|
||||
ptzStatus, err := client.GetStatus(ctx, profiles[0].Token)
|
||||
if err != nil {
|
||||
log.Printf("ERROR getting PTZ status: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ PTZ Status retrieved\n")
|
||||
if ptzStatus.Position != nil {
|
||||
if ptzStatus.Position.PanTilt != nil {
|
||||
fmt.Printf(" - Pan/Tilt Position: X=%.2f, Y=%.2f\n",
|
||||
ptzStatus.Position.PanTilt.X,
|
||||
ptzStatus.Position.PanTilt.Y)
|
||||
}
|
||||
if ptzStatus.Position.Zoom != nil {
|
||||
fmt.Printf(" - Zoom Position: %.2f\n", ptzStatus.Position.Zoom.X)
|
||||
}
|
||||
}
|
||||
if ptzStatus.MoveStatus != nil {
|
||||
fmt.Printf(" - Pan/Tilt Move Status: %s\n", ptzStatus.MoveStatus.PanTilt)
|
||||
fmt.Printf(" - Zoom Move Status: %s\n", ptzStatus.MoveStatus.Zoom)
|
||||
}
|
||||
}
|
||||
|
||||
// Get PTZ Presets
|
||||
presets, err := client.GetPresets(ctx, profiles[0].Token)
|
||||
if err != nil {
|
||||
log.Printf("ERROR getting PTZ presets: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Found %d PTZ preset(s)\n", len(presets))
|
||||
for i, preset := range presets {
|
||||
fmt.Printf(" Preset %d: %s (Token: %s)\n", i+1, preset.Name, preset.Token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("⊘ No PTZ configuration found for this profile")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 10: Imaging Settings
|
||||
fmt.Println("=== Test 10: Imaging Settings ===")
|
||||
if len(profiles) > 0 && profiles[0].VideoSourceConfiguration != nil {
|
||||
settings, err := client.GetImagingSettings(ctx, profiles[0].VideoSourceConfiguration.SourceToken)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Imaging Settings:\n")
|
||||
if settings.Brightness != nil {
|
||||
fmt.Printf(" - Brightness: %.1f\n", *settings.Brightness)
|
||||
}
|
||||
if settings.ColorSaturation != nil {
|
||||
fmt.Printf(" - Color Saturation: %.1f\n", *settings.ColorSaturation)
|
||||
}
|
||||
if settings.Contrast != nil {
|
||||
fmt.Printf(" - Contrast: %.1f\n", *settings.Contrast)
|
||||
}
|
||||
if settings.Sharpness != nil {
|
||||
fmt.Printf(" - Sharpness: %.1f\n", *settings.Sharpness)
|
||||
}
|
||||
if settings.IrCutFilter != nil {
|
||||
fmt.Printf(" - IR Cut Filter: %s\n", *settings.IrCutFilter)
|
||||
}
|
||||
if settings.BacklightCompensation != nil {
|
||||
fmt.Printf(" - Backlight Compensation: %s (Level: %.1f)\n",
|
||||
settings.BacklightCompensation.Mode,
|
||||
settings.BacklightCompensation.Level)
|
||||
}
|
||||
if settings.Exposure != nil {
|
||||
fmt.Printf(" - Exposure Mode: %s\n", settings.Exposure.Mode)
|
||||
fmt.Printf(" Priority: %s\n", settings.Exposure.Priority)
|
||||
}
|
||||
if settings.Focus != nil {
|
||||
fmt.Printf(" - Focus Mode: %s\n", settings.Focus.AutoFocusMode)
|
||||
}
|
||||
if settings.WhiteBalance != nil {
|
||||
fmt.Printf(" - White Balance Mode: %s\n", settings.WhiteBalance.Mode)
|
||||
}
|
||||
if settings.WideDynamicRange != nil {
|
||||
fmt.Printf(" - Wide Dynamic Range: %s (Level: %.1f)\n",
|
||||
settings.WideDynamicRange.Mode,
|
||||
settings.WideDynamicRange.Level)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("=== Test Summary ===")
|
||||
fmt.Println("All tests completed!")
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SOAP Envelope structures
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
|
||||
Header *Header `xml:"http://www.w3.org/2003/05/soap-envelope Header,omitempty"`
|
||||
Body Body `xml:"http://www.w3.org/2003/05/soap-envelope Body"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Security *Security `xml:"Security,omitempty"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Content interface{} `xml:",omitempty"`
|
||||
}
|
||||
|
||||
type Security struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
|
||||
MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
|
||||
UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"`
|
||||
}
|
||||
|
||||
type UsernameToken struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
|
||||
Username string `xml:"Username"`
|
||||
Password Password `xml:"Password"`
|
||||
Nonce Nonce `xml:"Nonce"`
|
||||
Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
|
||||
}
|
||||
|
||||
type Password struct {
|
||||
Type string `xml:"Type,attr"`
|
||||
Password string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Nonce struct {
|
||||
Type string `xml:"EncodingType,attr"`
|
||||
Nonce string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type GetDeviceInformation struct {
|
||||
XMLName xml.Name `xml:"tds:GetDeviceInformation"`
|
||||
Xmlns string `xml:"xmlns:tds,attr"`
|
||||
}
|
||||
|
||||
func createSecurityHeader(username, password string) *Security {
|
||||
nonceBytes := make([]byte, 16)
|
||||
_, _ = rand.Read(nonceBytes)
|
||||
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
|
||||
|
||||
created := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
hash := sha1.New()
|
||||
hash.Write(nonceBytes)
|
||||
hash.Write([]byte(created))
|
||||
hash.Write([]byte(password))
|
||||
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
|
||||
return &Security{
|
||||
MustUnderstand: "1",
|
||||
UsernameToken: &UsernameToken{
|
||||
Username: username,
|
||||
Password: Password{
|
||||
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest",
|
||||
Password: digest,
|
||||
},
|
||||
Nonce: Nonce{
|
||||
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
|
||||
Nonce: nonce,
|
||||
},
|
||||
Created: created,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
endpoint := "http://192.168.1.201/onvif/device_service"
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
|
||||
fmt.Println("Testing direct SOAP request to camera...")
|
||||
|
||||
// Build request
|
||||
req := GetDeviceInformation{
|
||||
Xmlns: "http://www.onvif.org/ver10/device/wsdl",
|
||||
}
|
||||
|
||||
envelope := &Envelope{
|
||||
Header: &Header{
|
||||
Security: createSecurityHeader(username, password),
|
||||
},
|
||||
Body: Body{
|
||||
Content: req,
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to XML
|
||||
body, err := xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
xmlBody := append([]byte(xml.Header), body...)
|
||||
|
||||
fmt.Println("\n=== Request XML ===")
|
||||
fmt.Println(string(xmlBody))
|
||||
|
||||
// Create HTTP request
|
||||
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", endpoint, bytes.NewReader(xmlBody))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create request: %v", err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
|
||||
// Send request
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Read response
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== HTTP Status: %d ===\n", resp.StatusCode)
|
||||
fmt.Printf("\n=== Response Headers ===\n")
|
||||
for k, v := range resp.Header {
|
||||
fmt.Printf("%s: %v\n", k, v)
|
||||
}
|
||||
fmt.Printf("\n=== Response Body ===\n")
|
||||
fmt.Println(string(respBody))
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SOAP Envelope structures
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
|
||||
Header *Header `xml:"http://www.w3.org/2003/05/soap-envelope Header,omitempty"`
|
||||
Body Body `xml:"http://www.w3.org/2003/05/soap-envelope Body"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Security *Security `xml:"Security,omitempty"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Content interface{} `xml:",omitempty"`
|
||||
}
|
||||
|
||||
type Security struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
|
||||
MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
|
||||
UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"`
|
||||
}
|
||||
|
||||
type UsernameToken struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
|
||||
Username string `xml:"Username"`
|
||||
Password Password `xml:"Password"`
|
||||
Nonce Nonce `xml:"Nonce"`
|
||||
Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
|
||||
}
|
||||
|
||||
type Password struct {
|
||||
Type string `xml:"Type,attr"`
|
||||
Password string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Nonce struct {
|
||||
Type string `xml:"EncodingType,attr"`
|
||||
Nonce string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type GetStreamUri struct {
|
||||
XMLName xml.Name `xml:"trt:GetStreamUri"`
|
||||
Xmlns string `xml:"xmlns:trt,attr"`
|
||||
Xmlnst string `xml:"xmlns:tt,attr"`
|
||||
StreamSetup struct {
|
||||
Stream string `xml:"tt:Stream"`
|
||||
Transport struct {
|
||||
Protocol string `xml:"tt:Protocol"`
|
||||
} `xml:"tt:Transport"`
|
||||
} `xml:"trt:StreamSetup"`
|
||||
ProfileToken string `xml:"trt:ProfileToken"`
|
||||
}
|
||||
|
||||
func createSecurityHeader(username, password string) *Security {
|
||||
nonceBytes := make([]byte, 16)
|
||||
rand.Read(nonceBytes)
|
||||
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
|
||||
|
||||
created := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
hash := sha1.New()
|
||||
hash.Write(nonceBytes)
|
||||
hash.Write([]byte(created))
|
||||
hash.Write([]byte(password))
|
||||
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
|
||||
return &Security{
|
||||
MustUnderstand: "1",
|
||||
UsernameToken: &UsernameToken{
|
||||
Username: username,
|
||||
Password: Password{
|
||||
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest",
|
||||
Password: digest,
|
||||
},
|
||||
Nonce: Nonce{
|
||||
Type: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
|
||||
Nonce: nonce,
|
||||
},
|
||||
Created: created,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Using the media service endpoint
|
||||
endpoint := "http://192.168.1.201/onvif/media_service"
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
profileToken := "0"
|
||||
|
||||
fmt.Println("Testing GetStreamUri SOAP request...")
|
||||
|
||||
// Build request
|
||||
req := GetStreamUri{
|
||||
Xmlns: "http://www.onvif.org/ver10/media/wsdl",
|
||||
Xmlnst: "http://www.onvif.org/ver10/schema",
|
||||
ProfileToken: profileToken,
|
||||
}
|
||||
req.StreamSetup.Stream = "RTP-Unicast"
|
||||
req.StreamSetup.Transport.Protocol = "RTSP"
|
||||
|
||||
envelope := &Envelope{
|
||||
Header: &Header{
|
||||
Security: createSecurityHeader(username, password),
|
||||
},
|
||||
Body: Body{
|
||||
Content: req,
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal to XML
|
||||
body, err := xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
xmlBody := append([]byte(xml.Header), body...)
|
||||
|
||||
fmt.Println("\n=== Request XML ===")
|
||||
fmt.Println(string(xmlBody))
|
||||
|
||||
// Create HTTP request
|
||||
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", endpoint, bytes.NewReader(xmlBody))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create request: %v", err)
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
|
||||
// Send request
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== HTTP Status: %d ===\n", resp.StatusCode)
|
||||
fmt.Printf("\n=== Response Body ===\n")
|
||||
fmt.Println(string(respBody))
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Go ONVIF Library Demo Script
|
||||
# This script demonstrates the capabilities of the Go ONVIF library
|
||||
|
||||
echo "🎥 Go ONVIF Library - Complete Implementation Demo"
|
||||
echo "=================================================="
|
||||
echo
|
||||
|
||||
echo "📁 Project Structure:"
|
||||
echo "├── Core Library (client.go, types.go, device.go, media.go, ptz.go, imaging.go)"
|
||||
echo "├── SOAP Client (soap/soap.go) with WS-Security authentication"
|
||||
echo "├── Discovery Service (discovery/discovery.go) for network camera detection"
|
||||
echo "├── Examples (examples/*) showing various use cases"
|
||||
echo "├── CLI Tools:"
|
||||
echo "│ ├── 🔧 onvif-cli - Comprehensive interactive tool"
|
||||
echo "│ └── ⚡ onvif-quick - Simple quick-start tool"
|
||||
echo "└── Tests with mock ONVIF server"
|
||||
echo
|
||||
|
||||
echo "🚀 Available Commands:"
|
||||
echo
|
||||
|
||||
echo "1. Build & Test:"
|
||||
echo " make build # Build both CLI tools"
|
||||
echo " make test # Run test suite"
|
||||
echo " make examples # Build example programs"
|
||||
echo " make build-all # Build for multiple platforms"
|
||||
echo
|
||||
|
||||
echo "2. CLI Tools:"
|
||||
echo " ./bin/onvif-cli # Interactive comprehensive tool"
|
||||
echo " ./bin/onvif-quick # Simple quick-start tool"
|
||||
echo
|
||||
|
||||
echo "3. Library Usage Example:"
|
||||
cat << 'EOF'
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/0x524A/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create client with credentials
|
||||
client, err := onvif.NewClient(
|
||||
"http://192.168.1.100/onvif/device_service",
|
||||
onvif.WithCredentials("admin", "password"),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get device information
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Camera: %s %s\n", info.Manufacturer, info.Model)
|
||||
|
||||
// Initialize for additional services
|
||||
client.Initialize(ctx)
|
||||
|
||||
// Get media profiles
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx, profiles[0].Token)
|
||||
if err == nil {
|
||||
fmt.Printf("Stream: %s\n", streamURI.URI)
|
||||
}
|
||||
|
||||
// PTZ Control (if supported)
|
||||
velocity := &onvif.PTZSpeed{
|
||||
PanTilt: &onvif.Vector2D{X: 0.5, Y: 0.0},
|
||||
}
|
||||
timeout := "PT5S"
|
||||
client.ContinuousMove(ctx, profiles[0].Token, velocity, &timeout)
|
||||
}
|
||||
```
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "🌟 Key Features:"
|
||||
echo "✅ Complete ONVIF Profile S implementation"
|
||||
echo "✅ WS-Discovery for automatic camera detection"
|
||||
echo "✅ WS-Security authentication with digest"
|
||||
echo "✅ PTZ control (pan, tilt, zoom)"
|
||||
echo "✅ Media profile management"
|
||||
echo "✅ Imaging settings control"
|
||||
echo "✅ Device information and capabilities"
|
||||
echo "✅ Stream URI generation (RTSP/HTTP)"
|
||||
echo "✅ Context-based timeout and cancellation"
|
||||
echo "✅ Comprehensive error handling"
|
||||
echo "✅ Thread-safe credential management"
|
||||
echo "✅ Interactive CLI tools"
|
||||
echo "✅ Docker support"
|
||||
echo "✅ Cross-platform builds"
|
||||
echo "✅ Extensive test coverage"
|
||||
echo
|
||||
|
||||
echo "🛠️ Development Features:"
|
||||
echo "✅ Modern Go 1.21+ with generics support"
|
||||
echo "✅ Functional options pattern"
|
||||
echo "✅ Comprehensive type definitions"
|
||||
echo "✅ Mock server for testing"
|
||||
echo "✅ Benchmark tests"
|
||||
echo "✅ CI/CD ready"
|
||||
echo "✅ Docker containerization"
|
||||
echo "✅ Multi-platform builds"
|
||||
echo
|
||||
|
||||
echo "📋 Quick Start:"
|
||||
echo "1. go mod tidy # Install dependencies"
|
||||
echo "2. make build # Build CLI tools"
|
||||
echo "3. ./bin/onvif-quick # Run quick tool"
|
||||
echo "4. ./bin/onvif-cli # Run comprehensive tool"
|
||||
echo
|
||||
|
||||
echo "🔗 For real camera testing:"
|
||||
echo "- Set up a test camera with known IP/credentials"
|
||||
echo "- Run discovery to find cameras: ./bin/onvif-quick"
|
||||
echo "- Use device info to verify connection"
|
||||
echo "- Test PTZ movements if camera supports it"
|
||||
echo "- Get stream URLs for media playback"
|
||||
echo
|
||||
|
||||
echo "🎯 This implementation provides a production-ready,"
|
||||
echo " comprehensive ONVIF library with full CLI tooling!"
|
||||
|
||||
echo
|
||||
echo "Run 'make help' for all available commands."
|
||||
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Camera connection details
|
||||
endpoint := "http://192.168.1.100/onvif/device_service"
|
||||
username := "admin"
|
||||
password := "password"
|
||||
|
||||
fmt.Println("Connecting to ONVIF camera...")
|
||||
|
||||
// Create a new ONVIF client
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get device information
|
||||
fmt.Println("\nRetrieving device information...")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get device information: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nDevice Information:\n")
|
||||
fmt.Printf(" Manufacturer: %s\n", info.Manufacturer)
|
||||
fmt.Printf(" Model: %s\n", info.Model)
|
||||
fmt.Printf(" Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf(" Serial Number: %s\n", info.SerialNumber)
|
||||
fmt.Printf(" Hardware ID: %s\n", info.HardwareID)
|
||||
|
||||
// Initialize client (discover service endpoints)
|
||||
fmt.Println("\nInitializing client and discovering services...")
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("Failed to initialize client: %v", err)
|
||||
}
|
||||
|
||||
// Get media profiles
|
||||
fmt.Println("\nRetrieving media profiles...")
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get profiles: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nFound %d profile(s):\n", len(profiles))
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf("\nProfile #%d:\n", i+1)
|
||||
fmt.Printf(" Token: %s\n", profile.Token)
|
||||
fmt.Printf(" Name: %s\n", profile.Name)
|
||||
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" Video Encoding: %s\n", profile.VideoEncoderConfiguration.Encoding)
|
||||
if profile.VideoEncoderConfiguration.Resolution != nil {
|
||||
fmt.Printf(" Resolution: %dx%d\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height)
|
||||
}
|
||||
fmt.Printf(" Quality: %.1f\n", profile.VideoEncoderConfiguration.Quality)
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Stream URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
||||
}
|
||||
|
||||
// Get snapshot URI
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Snapshot URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nDone!")
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
"github.com/0x524a/onvif-go/discovery"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("🔍 Discovering ONVIF cameras on the network...")
|
||||
fmt.Println("This may take a few seconds...")
|
||||
fmt.Println()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
devices, err := discovery.Discover(ctx, 10*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Discovery failed: %v", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
fmt.Println("❌ No ONVIF cameras found on the network")
|
||||
fmt.Println("💡 Make sure:")
|
||||
fmt.Println(" - Camera is powered on and connected to the network")
|
||||
fmt.Println(" - ONVIF is enabled on the camera")
|
||||
fmt.Println(" - You're on the same network segment as the camera")
|
||||
fmt.Println(" - Camera IP 192.168.1.201 is reachable (try: ping 192.168.1.201)")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Found %d camera(s):\n\n", len(devices))
|
||||
|
||||
var targetDevice *discovery.Device
|
||||
for i, device := range devices {
|
||||
fmt.Printf("📹 Camera #%d:\n", i+1)
|
||||
fmt.Printf(" Endpoint: %s\n", device.GetDeviceEndpoint())
|
||||
fmt.Printf(" Name: %s\n", device.GetName())
|
||||
fmt.Printf(" Location: %s\n", device.GetLocation())
|
||||
fmt.Printf(" Types: %v\n", device.Types)
|
||||
fmt.Printf(" XAddrs: %v\n", device.XAddrs)
|
||||
fmt.Println()
|
||||
|
||||
// Check if this is our target camera (192.168.1.201)
|
||||
endpoint := device.GetDeviceEndpoint()
|
||||
if len(endpoint) > 7 {
|
||||
// Simple check if endpoint contains the IP
|
||||
if len(endpoint) > 20 && (endpoint[7:20] == "192.168.1.201" || endpoint[7:21] == "192.168.1.201:") {
|
||||
targetDevice = device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if targetDevice == nil {
|
||||
fmt.Println("⚠️ Camera at 192.168.1.201 was not discovered")
|
||||
fmt.Println("💡 You can still try to connect manually with the correct endpoint")
|
||||
return
|
||||
}
|
||||
|
||||
// Now try to connect to the discovered camera
|
||||
fmt.Printf("\n🎯 Found target camera at 192.168.1.201\n")
|
||||
fmt.Printf("Endpoint: %s\n", targetDevice.GetDeviceEndpoint())
|
||||
fmt.Println()
|
||||
|
||||
// Test connection with credentials
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
|
||||
fmt.Println("📡 Connecting with credentials...")
|
||||
client, err := onvif.NewClient(
|
||||
targetDevice.GetDeviceEndpoint(),
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx2 := context.Background()
|
||||
|
||||
// Get device information
|
||||
fmt.Println("🔍 Retrieving device information...")
|
||||
info, err := client.GetDeviceInformation(ctx2)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to get device information: %v\n\n💡 Possible issues:\n - Wrong username or password\n - Camera requires different authentication\n - Try username/password combinations like: admin/admin, admin/12345, etc.\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n✅ Device Information:\n")
|
||||
fmt.Printf(" Manufacturer: %s\n", info.Manufacturer)
|
||||
fmt.Printf(" Model: %s\n", info.Model)
|
||||
fmt.Printf(" Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf(" Serial Number: %s\n", info.SerialNumber)
|
||||
fmt.Printf(" Hardware ID: %s\n", info.HardwareID)
|
||||
|
||||
// Initialize client (discover service endpoints)
|
||||
fmt.Println("\n🔧 Initializing client and discovering services...")
|
||||
if err := client.Initialize(ctx2); err != nil {
|
||||
log.Fatalf("❌ Failed to initialize client: %v", err)
|
||||
}
|
||||
fmt.Println("✅ Services discovered successfully")
|
||||
|
||||
// Get capabilities
|
||||
fmt.Println("\n🎯 Getting device capabilities...")
|
||||
caps, err := client.GetCapabilities(ctx2)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to get capabilities: %v", err)
|
||||
} else {
|
||||
fmt.Println("✅ Supported Services:")
|
||||
if caps.Device != nil {
|
||||
fmt.Println(" ✓ Device Service")
|
||||
}
|
||||
if caps.Media != nil {
|
||||
fmt.Println(" ✓ Media Service (Streaming)")
|
||||
}
|
||||
if caps.PTZ != nil {
|
||||
fmt.Println(" ✓ PTZ Service (Pan/Tilt/Zoom)")
|
||||
}
|
||||
if caps.Imaging != nil {
|
||||
fmt.Println(" ✓ Imaging Service")
|
||||
}
|
||||
if caps.Events != nil {
|
||||
fmt.Println(" ✓ Event Service")
|
||||
}
|
||||
if caps.Analytics != nil {
|
||||
fmt.Println(" ✓ Analytics Service")
|
||||
}
|
||||
}
|
||||
|
||||
// Get media profiles
|
||||
fmt.Println("\n📹 Retrieving media profiles...")
|
||||
profiles, err := client.GetProfiles(ctx2)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to get profiles: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n✅ Found %d profile(s):\n", len(profiles))
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf("\n📺 Profile #%d:\n", i+1)
|
||||
fmt.Printf(" Token: %s\n", profile.Token)
|
||||
fmt.Printf(" Name: %s\n", profile.Name)
|
||||
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" Encoding: %s\n", profile.VideoEncoderConfiguration.Encoding)
|
||||
if profile.VideoEncoderConfiguration.Resolution != nil {
|
||||
fmt.Printf(" Resolution: %dx%d\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height)
|
||||
}
|
||||
fmt.Printf(" Quality: %.1f\n", profile.VideoEncoderConfiguration.Quality)
|
||||
if profile.VideoEncoderConfiguration.RateControl != nil {
|
||||
fmt.Printf(" Frame Rate: %d fps\n", profile.VideoEncoderConfiguration.RateControl.FrameRateLimit)
|
||||
fmt.Printf(" Bitrate: %d kbps\n", profile.VideoEncoderConfiguration.RateControl.BitrateLimit)
|
||||
}
|
||||
}
|
||||
|
||||
if profile.PTZConfiguration != nil {
|
||||
fmt.Printf(" PTZ: Enabled\n")
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx2, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Stream URI: ❌ Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
||||
fmt.Printf(" 📱 Use this URL in VLC or other RTSP player\n")
|
||||
}
|
||||
|
||||
// Get snapshot URI
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx2, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Snapshot URI: ❌ Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
||||
fmt.Printf(" 🌐 You can open this URL in a browser\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Test PTZ if available
|
||||
if len(profiles) > 0 {
|
||||
fmt.Println("\n🎮 Testing PTZ capabilities...")
|
||||
profileToken := profiles[0].Token
|
||||
|
||||
status, err := client.GetStatus(ctx2, profileToken)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ PTZ not supported or error: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✅ PTZ is supported!")
|
||||
if status.Position != nil && status.Position.PanTilt != nil {
|
||||
fmt.Printf(" Current Position: Pan=%.3f, Tilt=%.3f\n",
|
||||
status.Position.PanTilt.X,
|
||||
status.Position.PanTilt.Y)
|
||||
}
|
||||
if status.Position != nil && status.Position.Zoom != nil {
|
||||
fmt.Printf(" Current Zoom: %.3f\n", status.Position.Zoom.X)
|
||||
}
|
||||
|
||||
// Get presets
|
||||
presets, err := client.GetPresets(ctx2, profileToken)
|
||||
if err != nil {
|
||||
fmt.Printf(" Presets: ❌ Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Available Presets: %d\n", len(presets))
|
||||
for _, preset := range presets {
|
||||
fmt.Printf(" - %s (Token: %s)\n", preset.Name, preset.Token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Imaging if available
|
||||
if len(profiles) > 0 && profiles[0].VideoSourceConfiguration != nil {
|
||||
fmt.Println("\n🎨 Testing Imaging capabilities...")
|
||||
videoSourceToken := profiles[0].VideoSourceConfiguration.SourceToken
|
||||
|
||||
settings, err := client.GetImagingSettings(ctx2, videoSourceToken)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ Imaging settings not available: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✅ Current Imaging Settings:")
|
||||
if settings.Brightness != nil {
|
||||
fmt.Printf(" Brightness: %.1f\n", *settings.Brightness)
|
||||
}
|
||||
if settings.Contrast != nil {
|
||||
fmt.Printf(" Contrast: %.1f\n", *settings.Contrast)
|
||||
}
|
||||
if settings.ColorSaturation != nil {
|
||||
fmt.Printf(" Saturation: %.1f\n", *settings.ColorSaturation)
|
||||
}
|
||||
if settings.Sharpness != nil {
|
||||
fmt.Printf(" Sharpness: %.1f\n", *settings.Sharpness)
|
||||
}
|
||||
if settings.Exposure != nil {
|
||||
fmt.Printf(" Exposure Mode: %s\n", settings.Exposure.Mode)
|
||||
}
|
||||
if settings.Focus != nil {
|
||||
fmt.Printf(" Focus Mode: %s\n", settings.Focus.AutoFocusMode)
|
||||
}
|
||||
if settings.WhiteBalance != nil {
|
||||
fmt.Printf(" White Balance: %s\n", settings.WhiteBalance.Mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n✅ All tests completed successfully!")
|
||||
fmt.Println("\n💡 Next steps:")
|
||||
fmt.Println(" - Use the stream URI in VLC to view the live feed")
|
||||
fmt.Println(" - Open the snapshot URI in a browser to see still images")
|
||||
fmt.Println(" - Use the PTZ controls to move the camera (if supported)")
|
||||
fmt.Println(" - Adjust imaging settings for better image quality")
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go/discovery"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Discovering ONVIF cameras on the network...")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
devices, err := discovery.Discover(ctx, 10*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("Discovery failed: %v", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
fmt.Println("No ONVIF devices found")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\nFound %d device(s):\n\n", len(devices))
|
||||
for i, device := range devices {
|
||||
fmt.Printf("Device #%d:\n", i+1)
|
||||
fmt.Printf(" Endpoint Ref: %s\n", device.EndpointRef)
|
||||
fmt.Printf(" XAddrs: %v\n", device.XAddrs)
|
||||
fmt.Printf(" Device Endpoint: %s\n", device.GetDeviceEndpoint())
|
||||
fmt.Printf(" Name: %s\n", device.GetName())
|
||||
fmt.Printf(" Location: %s\n", device.GetLocation())
|
||||
fmt.Printf(" Types: %v\n", device.Types)
|
||||
fmt.Printf(" Scopes: %v\n", device.Scopes)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go/discovery"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Discovering ONVIF devices on the network...")
|
||||
|
||||
// Create a context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Discover devices
|
||||
devices, err := discovery.Discover(ctx, 5*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("Discovery failed: %v", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
fmt.Println("No ONVIF devices found on the network")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\nFound %d device(s):\n\n", len(devices))
|
||||
|
||||
for i, device := range devices {
|
||||
fmt.Printf("Device #%d:\n", i+1)
|
||||
fmt.Printf(" Endpoint: %s\n", device.GetDeviceEndpoint())
|
||||
fmt.Printf(" Name: %s\n", device.GetName())
|
||||
fmt.Printf(" Location: %s\n", device.GetLocation())
|
||||
fmt.Printf(" Types: %v\n", device.Types)
|
||||
fmt.Printf(" Scopes: %v\n", device.Scopes)
|
||||
fmt.Printf(" XAddrs: %v\n", device.XAddrs)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Camera connection details
|
||||
endpoint := "http://192.168.1.100/onvif/device_service"
|
||||
username := "admin"
|
||||
password := "password"
|
||||
|
||||
fmt.Println("Connecting to ONVIF camera...")
|
||||
|
||||
// Create a new ONVIF client
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Initialize client
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("Failed to initialize client: %v", err)
|
||||
}
|
||||
|
||||
// Get profiles
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get profiles: %v", err)
|
||||
}
|
||||
|
||||
if len(profiles) == 0 {
|
||||
log.Fatal("No profiles found")
|
||||
}
|
||||
|
||||
// Get video source token from profile
|
||||
profile := profiles[0]
|
||||
if profile.VideoSourceConfiguration == nil {
|
||||
log.Fatal("No video source configuration found")
|
||||
}
|
||||
|
||||
videoSourceToken := profile.VideoSourceConfiguration.SourceToken
|
||||
fmt.Printf("Using video source: %s\n\n", videoSourceToken)
|
||||
|
||||
// Get current imaging settings
|
||||
fmt.Println("Getting current imaging settings...")
|
||||
settings, err := client.GetImagingSettings(ctx, videoSourceToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get imaging settings: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nCurrent Imaging Settings:")
|
||||
if settings.Brightness != nil {
|
||||
fmt.Printf(" Brightness: %.2f\n", *settings.Brightness)
|
||||
}
|
||||
if settings.Contrast != nil {
|
||||
fmt.Printf(" Contrast: %.2f\n", *settings.Contrast)
|
||||
}
|
||||
if settings.ColorSaturation != nil {
|
||||
fmt.Printf(" Saturation: %.2f\n", *settings.ColorSaturation)
|
||||
}
|
||||
if settings.Sharpness != nil {
|
||||
fmt.Printf(" Sharpness: %.2f\n", *settings.Sharpness)
|
||||
}
|
||||
if settings.IrCutFilter != nil {
|
||||
fmt.Printf(" IR Cut Filter: %s\n", *settings.IrCutFilter)
|
||||
}
|
||||
|
||||
if settings.Exposure != nil {
|
||||
fmt.Printf(" Exposure Mode: %s\n", settings.Exposure.Mode)
|
||||
if settings.Exposure.Mode == "MANUAL" {
|
||||
fmt.Printf(" Exposure Time: %.2f\n", settings.Exposure.ExposureTime)
|
||||
fmt.Printf(" Gain: %.2f\n", settings.Exposure.Gain)
|
||||
}
|
||||
}
|
||||
|
||||
if settings.Focus != nil {
|
||||
fmt.Printf(" Focus Mode: %s\n", settings.Focus.AutoFocusMode)
|
||||
}
|
||||
|
||||
if settings.WhiteBalance != nil {
|
||||
fmt.Printf(" White Balance Mode: %s\n", settings.WhiteBalance.Mode)
|
||||
}
|
||||
|
||||
if settings.WideDynamicRange != nil {
|
||||
fmt.Printf(" WDR Mode: %s\n", settings.WideDynamicRange.Mode)
|
||||
fmt.Printf(" WDR Level: %.2f\n", settings.WideDynamicRange.Level)
|
||||
}
|
||||
|
||||
// Modify some settings
|
||||
fmt.Println("\n\nModifying imaging settings...")
|
||||
|
||||
// Increase brightness
|
||||
newBrightness := 60.0
|
||||
settings.Brightness = &newBrightness
|
||||
|
||||
// Increase contrast
|
||||
newContrast := 55.0
|
||||
settings.Contrast = &newContrast
|
||||
|
||||
// Set to auto exposure
|
||||
if settings.Exposure != nil {
|
||||
settings.Exposure.Mode = "AUTO"
|
||||
}
|
||||
|
||||
// Apply new settings
|
||||
if err := client.SetImagingSettings(ctx, videoSourceToken, settings, true); err != nil {
|
||||
log.Fatalf("Failed to set imaging settings: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Imaging settings updated successfully!")
|
||||
|
||||
// Verify changes
|
||||
fmt.Println("\nVerifying new settings...")
|
||||
updatedSettings, err := client.GetImagingSettings(ctx, videoSourceToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get updated imaging settings: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nUpdated Imaging Settings:")
|
||||
if updatedSettings.Brightness != nil {
|
||||
fmt.Printf(" Brightness: %.2f\n", *updatedSettings.Brightness)
|
||||
}
|
||||
if updatedSettings.Contrast != nil {
|
||||
fmt.Printf(" Contrast: %.2f\n", *updatedSettings.Contrast)
|
||||
}
|
||||
if updatedSettings.Exposure != nil {
|
||||
fmt.Printf(" Exposure Mode: %s\n", updatedSettings.Exposure.Mode)
|
||||
}
|
||||
|
||||
fmt.Println("\nImaging settings demonstration complete!")
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Test SOAP request manually
|
||||
endpoint := "http://192.168.1.201/onvif/device_service"
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
|
||||
fmt.Println("🔧 Manual SOAP Test for ONVIF Camera")
|
||||
fmt.Println("=====================================")
|
||||
fmt.Printf("Endpoint: %s\n", endpoint)
|
||||
fmt.Printf("Username: %s\n", username)
|
||||
fmt.Println()
|
||||
|
||||
// Simple GetDeviceInformation SOAP request (without auth for now)
|
||||
soapRequest := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
|
||||
xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<soap:Body>
|
||||
<tds:GetDeviceInformation/>
|
||||
</soap:Body>
|
||||
</soap:Envelope>`
|
||||
|
||||
fmt.Println("📤 Sending SOAP request (without authentication)...")
|
||||
fmt.Println()
|
||||
|
||||
req, err := http.NewRequest("POST", endpoint, bytes.NewBufferString(soapRequest))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to send request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf("📥 Response Status: %s\n", resp.Status)
|
||||
fmt.Println("📋 Response Headers:")
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
fmt.Printf(" %s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read response: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("📄 Response Body:")
|
||||
fmt.Println(string(body))
|
||||
fmt.Println()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
fmt.Printf("⚠️ Non-200 status code: %d\n", resp.StatusCode)
|
||||
|
||||
if resp.StatusCode == 401 {
|
||||
fmt.Println("💡 Authentication required - this is expected!")
|
||||
fmt.Println("💡 Now testing with onvif-go client library...")
|
||||
fmt.Println()
|
||||
testWithClient(username, password)
|
||||
} else {
|
||||
fmt.Println("💡 Unexpected status code. Check:")
|
||||
fmt.Println(" - Is ONVIF enabled on the camera?")
|
||||
fmt.Println(" - Is the endpoint path correct?")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("✅ Got successful response!")
|
||||
}
|
||||
}
|
||||
|
||||
func testWithClient(username, password string) {
|
||||
// Import locally to avoid conflicts
|
||||
onvif := struct{}{}
|
||||
_ = onvif
|
||||
|
||||
fmt.Println("Note: Would test with onvif-go client here, but keeping this simple.")
|
||||
fmt.Println("The camera appears to be responding to ONVIF requests.")
|
||||
fmt.Println()
|
||||
fmt.Println("💡 Next step: Check if the credentials are correct")
|
||||
fmt.Printf(" Username: %s\n", username)
|
||||
fmt.Printf(" Password: %s\n", password)
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a custom multi-lens camera configuration
|
||||
config := &server.Config{
|
||||
Host: "0.0.0.0",
|
||||
Port: 8080,
|
||||
BasePath: "/onvif",
|
||||
Timeout: 30 * time.Second,
|
||||
DeviceInfo: server.DeviceInfo{
|
||||
Manufacturer: "MultiCam Systems",
|
||||
Model: "MC-3000 Pro",
|
||||
FirmwareVersion: "2.5.1",
|
||||
SerialNumber: "MC3000-001234",
|
||||
HardwareID: "HW-MC3000",
|
||||
},
|
||||
Username: "admin",
|
||||
Password: "SecurePass123",
|
||||
SupportPTZ: true,
|
||||
SupportImaging: true,
|
||||
SupportEvents: false,
|
||||
Profiles: []server.ProfileConfig{
|
||||
// Profile 1: Main camera with 4K resolution
|
||||
{
|
||||
Token: "profile_main_4k",
|
||||
Name: "Main Camera 4K",
|
||||
VideoSource: server.VideoSourceConfig{
|
||||
Token: "video_source_main",
|
||||
Name: "Main Camera",
|
||||
Resolution: server.Resolution{Width: 3840, Height: 2160},
|
||||
Framerate: 30,
|
||||
Bounds: server.Bounds{X: 0, Y: 0, Width: 3840, Height: 2160},
|
||||
},
|
||||
VideoEncoder: server.VideoEncoderConfig{
|
||||
Encoding: "H264",
|
||||
Resolution: server.Resolution{Width: 3840, Height: 2160},
|
||||
Quality: 90,
|
||||
Framerate: 30,
|
||||
Bitrate: 20480, // 20 Mbps
|
||||
GovLength: 30,
|
||||
},
|
||||
PTZ: &server.PTZConfig{
|
||||
NodeToken: "ptz_main",
|
||||
PanRange: server.Range{Min: -180, Max: 180},
|
||||
TiltRange: server.Range{Min: -90, Max: 90},
|
||||
ZoomRange: server.Range{Min: 0, Max: 10}, // 10x optical zoom
|
||||
DefaultSpeed: server.PTZSpeed{Pan: 0.5, Tilt: 0.5, Zoom: 0.5},
|
||||
SupportsContinuous: true,
|
||||
SupportsAbsolute: true,
|
||||
SupportsRelative: true,
|
||||
Presets: []server.Preset{
|
||||
{Token: "preset_home", Name: "Home Position", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
||||
{Token: "preset_entrance", Name: "Main Entrance", Position: server.PTZPosition{Pan: -45, Tilt: -20, Zoom: 3}},
|
||||
{Token: "preset_parking", Name: "Parking Lot", Position: server.PTZPosition{Pan: 90, Tilt: -30, Zoom: 5}},
|
||||
{Token: "preset_perimeter", Name: "Perimeter View", Position: server.PTZPosition{Pan: 180, Tilt: 0, Zoom: 2}},
|
||||
},
|
||||
},
|
||||
Snapshot: server.SnapshotConfig{
|
||||
Enabled: true,
|
||||
Resolution: server.Resolution{Width: 3840, Height: 2160},
|
||||
Quality: 95,
|
||||
},
|
||||
},
|
||||
// Profile 2: Wide-angle camera for overview
|
||||
{
|
||||
Token: "profile_wide",
|
||||
Name: "Wide Angle Overview",
|
||||
VideoSource: server.VideoSourceConfig{
|
||||
Token: "video_source_wide",
|
||||
Name: "Wide Angle Camera",
|
||||
Resolution: server.Resolution{Width: 2560, Height: 1440},
|
||||
Framerate: 30,
|
||||
Bounds: server.Bounds{X: 0, Y: 0, Width: 2560, Height: 1440},
|
||||
},
|
||||
VideoEncoder: server.VideoEncoderConfig{
|
||||
Encoding: "H264",
|
||||
Resolution: server.Resolution{Width: 2560, Height: 1440},
|
||||
Quality: 85,
|
||||
Framerate: 30,
|
||||
Bitrate: 8192, // 8 Mbps
|
||||
GovLength: 30,
|
||||
},
|
||||
Snapshot: server.SnapshotConfig{
|
||||
Enabled: true,
|
||||
Resolution: server.Resolution{Width: 2560, Height: 1440},
|
||||
Quality: 90,
|
||||
},
|
||||
},
|
||||
// Profile 3: Telephoto camera for distant subjects
|
||||
{
|
||||
Token: "profile_telephoto",
|
||||
Name: "Telephoto Camera",
|
||||
VideoSource: server.VideoSourceConfig{
|
||||
Token: "video_source_telephoto",
|
||||
Name: "Telephoto Camera",
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Framerate: 60, // High framerate for smooth tracking
|
||||
Bounds: server.Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
|
||||
},
|
||||
VideoEncoder: server.VideoEncoderConfig{
|
||||
Encoding: "H264",
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 88,
|
||||
Framerate: 60,
|
||||
Bitrate: 10240, // 10 Mbps
|
||||
GovLength: 60,
|
||||
},
|
||||
PTZ: &server.PTZConfig{
|
||||
NodeToken: "ptz_telephoto",
|
||||
PanRange: server.Range{Min: -180, Max: 180},
|
||||
TiltRange: server.Range{Min: -45, Max: 45},
|
||||
ZoomRange: server.Range{Min: 0, Max: 30}, // 30x optical zoom
|
||||
DefaultSpeed: server.PTZSpeed{Pan: 0.3, Tilt: 0.3, Zoom: 0.3},
|
||||
SupportsContinuous: true,
|
||||
SupportsAbsolute: true,
|
||||
SupportsRelative: true,
|
||||
Presets: []server.Preset{
|
||||
{Token: "preset_tel_home", Name: "Home", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
||||
{Token: "preset_tel_far", Name: "Far View", Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 20}},
|
||||
{Token: "preset_tel_left", Name: "Left Side", Position: server.PTZPosition{Pan: -90, Tilt: 0, Zoom: 10}},
|
||||
{Token: "preset_tel_right", Name: "Right Side", Position: server.PTZPosition{Pan: 90, Tilt: 0, Zoom: 10}},
|
||||
},
|
||||
},
|
||||
Snapshot: server.SnapshotConfig{
|
||||
Enabled: true,
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 92,
|
||||
},
|
||||
},
|
||||
// Profile 4: Low-light camera for night vision
|
||||
{
|
||||
Token: "profile_lowlight",
|
||||
Name: "Low Light Night Camera",
|
||||
VideoSource: server.VideoSourceConfig{
|
||||
Token: "video_source_lowlight",
|
||||
Name: "Low Light Camera",
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Framerate: 30,
|
||||
Bounds: server.Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
|
||||
},
|
||||
VideoEncoder: server.VideoEncoderConfig{
|
||||
Encoding: "H264",
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 85,
|
||||
Framerate: 30,
|
||||
Bitrate: 6144, // 6 Mbps
|
||||
GovLength: 30,
|
||||
},
|
||||
Snapshot: server.SnapshotConfig{
|
||||
Enabled: true,
|
||||
Resolution: server.Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 88,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create and start server
|
||||
srv, err := server.New(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create server: %v", err)
|
||||
}
|
||||
|
||||
// Print configuration
|
||||
fmt.Println("╔════════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ ║")
|
||||
fmt.Println("║ 🎥 ONVIF Multi-Lens Camera Server Example 🎥 ║")
|
||||
fmt.Println("║ ║")
|
||||
fmt.Println("╚════════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
fmt.Println(srv.ServerInfo())
|
||||
fmt.Println()
|
||||
fmt.Println("📝 Configuration Details:")
|
||||
fmt.Println(" • 4 camera lenses with different capabilities")
|
||||
fmt.Println(" • Main camera: 4K resolution with 10x zoom PTZ")
|
||||
fmt.Println(" • Wide angle: 1440p for area overview")
|
||||
fmt.Println(" • Telephoto: 1080p@60fps with 30x zoom for distant subjects")
|
||||
fmt.Println(" • Low light: 1080p optimized for night vision")
|
||||
fmt.Println()
|
||||
fmt.Println("🔐 Credentials:")
|
||||
fmt.Println(" Username: admin")
|
||||
fmt.Println(" Password: SecurePass123")
|
||||
fmt.Println()
|
||||
fmt.Println("Press Ctrl+C to stop the server...")
|
||||
fmt.Println()
|
||||
|
||||
// Create context with cancellation
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Setup signal handler
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Start server in goroutine
|
||||
go func() {
|
||||
if err := srv.Start(ctx); err != nil {
|
||||
log.Printf("Server error: %v", err)
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal
|
||||
<-sigChan
|
||||
fmt.Println("\n🛑 Shutting down server...")
|
||||
cancel()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("✅ Server stopped successfully")
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Camera connection details
|
||||
endpoint := "http://192.168.1.100/onvif/device_service"
|
||||
username := "admin"
|
||||
password := "password"
|
||||
|
||||
fmt.Println("Connecting to ONVIF camera...")
|
||||
|
||||
// Create a new ONVIF client
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Initialize client
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("Failed to initialize client: %v", err)
|
||||
}
|
||||
|
||||
// Get profiles
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get profiles: %v", err)
|
||||
}
|
||||
|
||||
if len(profiles) == 0 {
|
||||
log.Fatal("No profiles found")
|
||||
}
|
||||
|
||||
profileToken := profiles[0].Token
|
||||
fmt.Printf("Using profile: %s\n\n", profiles[0].Name)
|
||||
|
||||
// Demonstrate PTZ controls
|
||||
demonstratePTZ(ctx, client, profileToken)
|
||||
}
|
||||
|
||||
func demonstratePTZ(ctx context.Context, client *onvif.Client, profileToken string) {
|
||||
// Get current PTZ status
|
||||
fmt.Println("Getting current PTZ status...")
|
||||
status, err := client.GetStatus(ctx, profileToken)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to get PTZ status: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Current Position:\n")
|
||||
if status.Position != nil {
|
||||
if status.Position.PanTilt != nil {
|
||||
fmt.Printf(" Pan/Tilt: X=%.2f, Y=%.2f\n",
|
||||
status.Position.PanTilt.X,
|
||||
status.Position.PanTilt.Y)
|
||||
}
|
||||
if status.Position.Zoom != nil {
|
||||
fmt.Printf(" Zoom: %.2f\n", status.Position.Zoom.X)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Get presets
|
||||
fmt.Println("Getting PTZ presets...")
|
||||
presets, err := client.GetPresets(ctx, profileToken)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to get presets: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Found %d preset(s):\n", len(presets))
|
||||
for _, preset := range presets {
|
||||
fmt.Printf(" - %s (Token: %s)\n", preset.Name, preset.Token)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Continuous move right for 2 seconds
|
||||
fmt.Println("Moving camera right...")
|
||||
velocity := &onvif.PTZSpeed{
|
||||
PanTilt: &onvif.Vector2D{
|
||||
X: 0.5, // Move right
|
||||
Y: 0.0,
|
||||
},
|
||||
}
|
||||
timeout := "PT2S" // 2 seconds
|
||||
if err := client.ContinuousMove(ctx, profileToken, velocity, &timeout); err != nil {
|
||||
log.Printf("Failed to move: %v\n", err)
|
||||
} else {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Stop movement
|
||||
fmt.Println("Stopping camera movement...")
|
||||
if err := client.Stop(ctx, profileToken, true, false); err != nil {
|
||||
log.Printf("Failed to stop: %v\n", err)
|
||||
}
|
||||
|
||||
// Relative move
|
||||
fmt.Println("\nPerforming relative move (up and zoom in)...")
|
||||
translation := &onvif.PTZVector{
|
||||
PanTilt: &onvif.Vector2D{
|
||||
X: 0.0,
|
||||
Y: 0.1, // Move up
|
||||
},
|
||||
Zoom: &onvif.Vector1D{
|
||||
X: 0.1, // Zoom in
|
||||
},
|
||||
}
|
||||
if err := client.RelativeMove(ctx, profileToken, translation, nil); err != nil {
|
||||
log.Printf("Failed to relative move: %v\n", err)
|
||||
} else {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Absolute move to home position
|
||||
fmt.Println("\nMoving to home position...")
|
||||
homePosition := &onvif.PTZVector{
|
||||
PanTilt: &onvif.Vector2D{
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
},
|
||||
Zoom: &onvif.Vector1D{
|
||||
X: 0.0,
|
||||
},
|
||||
}
|
||||
if err := client.AbsoluteMove(ctx, profileToken, homePosition, nil); err != nil {
|
||||
log.Printf("Failed to absolute move: %v\n", err)
|
||||
} else {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Go to preset if available
|
||||
if len(presets) > 0 {
|
||||
fmt.Printf("\nGoing to preset: %s\n", presets[0].Name)
|
||||
if err := client.GotoPreset(ctx, profileToken, presets[0].Token, nil); err != nil {
|
||||
log.Printf("Failed to go to preset: %v\n", err)
|
||||
} else {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nPTZ demonstration complete!")
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/0x524a/onvif-go/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starting ONVIF Server on port 8081...")
|
||||
fmt.Println("Press Ctrl+C to stop")
|
||||
fmt.Println()
|
||||
|
||||
config := server.DefaultConfig()
|
||||
config.Port = 8081
|
||||
|
||||
srv, err := server.New(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := srv.Start(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Demonstrates the three different endpoint formats supported by NewClient
|
||||
|
||||
examples := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
desc string
|
||||
}{
|
||||
{
|
||||
name: "Simple IP",
|
||||
endpoint: "192.168.1.100",
|
||||
desc: "Just the IP address - automatically adds http:// and /onvif/device_service",
|
||||
},
|
||||
{
|
||||
name: "IP with Port",
|
||||
endpoint: "192.168.1.100:8080",
|
||||
desc: "IP and port - automatically adds http:// and /onvif/device_service",
|
||||
},
|
||||
{
|
||||
name: "Full URL",
|
||||
endpoint: "http://192.168.1.100/onvif/device_service",
|
||||
desc: "Complete URL - used as-is",
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Println("ONVIF Client - Simplified Endpoint Formats Demo")
|
||||
fmt.Println("================================================")
|
||||
fmt.Println()
|
||||
|
||||
for _, ex := range examples {
|
||||
fmt.Printf("%s:\n", ex.name)
|
||||
fmt.Printf(" Input: %s\n", ex.endpoint)
|
||||
fmt.Printf(" Description: %s\n", ex.desc)
|
||||
|
||||
// Create client with simplified endpoint
|
||||
client, err := onvif.NewClient(
|
||||
ex.endpoint,
|
||||
onvif.WithCredentials("admin", "password"),
|
||||
onvif.WithTimeout(5*time.Second),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf(" Error: %v\n\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" Client created successfully!\n")
|
||||
fmt.Printf(" Endpoint will be: %s\n\n", client.Endpoint())
|
||||
|
||||
// Try to get device information (will fail if camera doesn't exist)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(" Note: Could not connect to camera (this is expected in demo)\n")
|
||||
fmt.Printf(" Error: %v\n\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Connected to: %s %s\n", info.Manufacturer, info.Model)
|
||||
fmt.Printf(" Firmware: %s\n\n", info.FirmwareVersion)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Key Benefits:")
|
||||
fmt.Println("- Simpler API: Just provide '192.168.1.100' instead of full URL")
|
||||
fmt.Println("- Flexible: Works with IP, IP:port, or full URL")
|
||||
fmt.Println("- Backward Compatible: Existing code continues to work")
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Package main tests Event and Device IO services against a real camera.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
onvif "github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
const notAvailable = "N/A"
|
||||
|
||||
func main() {
|
||||
// Command line flags.
|
||||
cameraIP := flag.String("ip", "192.168.1.201", "Camera IP address")
|
||||
username := flag.String("user", "service", "Camera username")
|
||||
password := flag.String("pass", "Service.1234", "Camera password")
|
||||
flag.Parse()
|
||||
|
||||
endpoint := fmt.Sprintf("http://%s/onvif/device_service", *cameraIP)
|
||||
|
||||
fmt.Printf("Testing Event and Device IO services on camera: %s\n", *cameraIP)
|
||||
fmt.Printf("Endpoint: %s\n", endpoint)
|
||||
fmt.Printf("Username: %s\n\n", *username)
|
||||
|
||||
// Create client.
|
||||
client, err := onvif.NewClient(endpoint,
|
||||
onvif.WithCredentials(*username, *password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create client: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Test device information first to verify connectivity.
|
||||
fmt.Println("=== Testing Device Connectivity ===")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get device information: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Device: %s %s\n", info.Manufacturer, info.Model)
|
||||
fmt.Printf("Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf("Serial: %s\n\n", info.SerialNumber)
|
||||
|
||||
// Test Event Service.
|
||||
testEventService(ctx, client)
|
||||
|
||||
// Test Device IO Service.
|
||||
testDeviceIOService(ctx, client)
|
||||
|
||||
fmt.Println("\n=== All Tests Completed ===")
|
||||
}
|
||||
|
||||
func testEventService(ctx context.Context, client *onvif.Client) {
|
||||
fmt.Println("=== Testing Event Service ===")
|
||||
|
||||
// 1. Get Event Service Capabilities.
|
||||
fmt.Println("\n1. GetEventServiceCapabilities")
|
||||
caps, err := client.GetEventServiceCapabilities(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" WSSubscriptionPolicySupport: %v\n", caps.WSSubscriptionPolicySupport)
|
||||
fmt.Printf(" MaxPullPoints: %d\n", caps.MaxPullPoints)
|
||||
fmt.Printf(" PersistentNotificationStorage: %v\n", caps.PersistentNotificationStorage)
|
||||
fmt.Printf(" EventBrokerProtocols: %v\n", caps.EventBrokerProtocols)
|
||||
fmt.Printf(" MaxEventBrokers: %d\n", caps.MaxEventBrokers)
|
||||
}
|
||||
|
||||
// 2. Get Event Properties.
|
||||
fmt.Println("\n2. GetEventProperties")
|
||||
props, err := client.GetEventProperties(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" FixedTopicSet: %v\n", props.FixedTopicSet)
|
||||
fmt.Printf(" TopicNamespaceLocations: %d\n", len(props.TopicNamespaceLocation))
|
||||
fmt.Printf(" TopicExpressionDialects: %d\n", len(props.TopicExpressionDialects))
|
||||
}
|
||||
|
||||
// 3. Create Pull Point Subscription.
|
||||
fmt.Println("\n3. CreatePullPointSubscription")
|
||||
termTime := 60 * time.Second
|
||||
sub, err := client.CreatePullPointSubscription(ctx, "", &termTime, "")
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" SubscriptionReference: %s\n", sub.SubscriptionReference)
|
||||
fmt.Printf(" CurrentTime: %v\n", sub.CurrentTime)
|
||||
fmt.Printf(" TerminationTime: %v\n", sub.TerminationTime)
|
||||
|
||||
// 4. Pull Messages.
|
||||
if sub.SubscriptionReference != "" {
|
||||
fmt.Println("\n4. PullMessages")
|
||||
messages, err := client.PullMessages(ctx, sub.SubscriptionReference, 5*time.Second, 10)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Received %d messages\n", len(messages))
|
||||
for i, msg := range messages {
|
||||
if i >= 3 {
|
||||
fmt.Printf(" ... and %d more\n", len(messages)-3)
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf(" Message %d: Topic=%s, Operation=%s\n",
|
||||
i+1, msg.Topic, msg.Message.PropertyOperation)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Renew Subscription.
|
||||
fmt.Println("\n5. RenewSubscription")
|
||||
curTime, newTermTime, err := client.RenewSubscription(ctx, sub.SubscriptionReference, 120*time.Second)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" CurrentTime: %v\n", curTime)
|
||||
fmt.Printf(" NewTerminationTime: %v\n", newTermTime)
|
||||
}
|
||||
|
||||
// 6. Unsubscribe.
|
||||
fmt.Println("\n6. Unsubscribe")
|
||||
err = client.Unsubscribe(ctx, sub.SubscriptionReference)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" Successfully unsubscribed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Get Event Brokers (optional, may not be supported).
|
||||
fmt.Println("\n7. GetEventBrokers")
|
||||
brokers, err := client.GetEventBrokers(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR (may not be supported): %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d event brokers\n", len(brokers))
|
||||
for i, broker := range brokers {
|
||||
fmt.Printf(" Broker %d: %s (Status: %s)\n", i+1, broker.Address, broker.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDeviceIOService(ctx context.Context, client *onvif.Client) {
|
||||
fmt.Println("\n=== Testing Device IO Service ===")
|
||||
|
||||
// 1. Get Device IO Service Capabilities.
|
||||
fmt.Println("\n1. GetDeviceIOServiceCapabilities")
|
||||
caps, err := client.GetDeviceIOServiceCapabilities(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" VideoSources: %d\n", caps.VideoSources)
|
||||
fmt.Printf(" VideoOutputs: %d\n", caps.VideoOutputs)
|
||||
fmt.Printf(" AudioSources: %d\n", caps.AudioSources)
|
||||
fmt.Printf(" AudioOutputs: %d\n", caps.AudioOutputs)
|
||||
fmt.Printf(" RelayOutputs: %d\n", caps.RelayOutputs)
|
||||
fmt.Printf(" DigitalInputs: %d\n", caps.DigitalInputs)
|
||||
fmt.Printf(" SerialPorts: %d\n", caps.SerialPorts)
|
||||
}
|
||||
|
||||
// 2. Get Digital Inputs.
|
||||
fmt.Println("\n2. GetDigitalInputs")
|
||||
inputs, err := client.GetDigitalInputs(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d digital inputs\n", len(inputs))
|
||||
for i, input := range inputs {
|
||||
fmt.Printf(" Input %d: Token=%s, IdleState=%s\n", i+1, input.Token, input.IdleState)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Get Video Outputs.
|
||||
fmt.Println("\n3. GetVideoOutputs")
|
||||
outputs, err := client.GetVideoOutputs(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d video outputs\n", len(outputs))
|
||||
for i, output := range outputs {
|
||||
res := notAvailable
|
||||
if output.Resolution != nil {
|
||||
res = fmt.Sprintf("%dx%d", output.Resolution.Width, output.Resolution.Height)
|
||||
}
|
||||
|
||||
fmt.Printf(" Output %d: Token=%s, Resolution=%s, RefreshRate=%.1f\n",
|
||||
i+1, output.Token, res, output.RefreshRate)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Get Serial Ports.
|
||||
fmt.Println("\n4. GetSerialPorts")
|
||||
ports, err := client.GetSerialPorts(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d serial ports\n", len(ports))
|
||||
for i, port := range ports {
|
||||
fmt.Printf(" Port %d: Token=%s, Type=%s\n", i+1, port.Token, port.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Get Relay Outputs (using existing method).
|
||||
fmt.Println("\n5. GetRelayOutputs")
|
||||
relays, err := client.GetRelayOutputs(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf(" ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Found %d relay outputs\n", len(relays))
|
||||
for i, relay := range relays {
|
||||
mode := notAvailable
|
||||
idleState := notAvailable
|
||||
if relay.Properties.Mode != "" {
|
||||
mode = string(relay.Properties.Mode)
|
||||
}
|
||||
|
||||
if relay.Properties.IdleState != "" {
|
||||
idleState = string(relay.Properties.IdleState)
|
||||
}
|
||||
|
||||
fmt.Printf(" Relay %d: Token=%s, Mode=%s, IdleState=%s\n",
|
||||
i+1, relay.Token, mode, idleState)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
var (
|
||||
endpoint = flag.String("endpoint", "http://192.168.1.201/onvif/device_service", "ONVIF device endpoint")
|
||||
username = flag.String("username", "admin", "Username for authentication")
|
||||
password = flag.String("password", "", "Password for authentication")
|
||||
verbose = flag.Bool("verbose", true, "Enable verbose output")
|
||||
output = flag.String("output", "test-results.json", "Output file for results")
|
||||
)
|
||||
|
||||
type TestResults struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
CameraInfo *CameraInfo `json:"camera_info"`
|
||||
DeviceTests map[string]interface{} `json:"device_tests"`
|
||||
MediaTests map[string]interface{} `json:"media_tests"`
|
||||
PTZTests map[string]interface{} `json:"ptz_tests"`
|
||||
ImagingTests map[string]interface{} `json:"imaging_tests"`
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
type CameraInfo struct {
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Model string `json:"model"`
|
||||
FirmwareVersion string `json:"firmware_version"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
HardwareID string `json:"hardware_id"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *password == "" {
|
||||
log.Fatal("Password is required. Use -password flag")
|
||||
}
|
||||
|
||||
log.Printf("Testing ONVIF camera at: %s", *endpoint)
|
||||
log.Printf("Username: %s", *username)
|
||||
|
||||
// Create client
|
||||
client, err := onvif.NewClient(
|
||||
*endpoint,
|
||||
onvif.WithCredentials(*username, *password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results := &TestResults{
|
||||
Timestamp: time.Now(),
|
||||
DeviceTests: make(map[string]interface{}),
|
||||
MediaTests: make(map[string]interface{}),
|
||||
PTZTests: make(map[string]interface{}),
|
||||
ImagingTests: make(map[string]interface{}),
|
||||
Errors: []string{},
|
||||
}
|
||||
|
||||
// Initialize client
|
||||
log.Println("\n=== Initializing Client ===")
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Printf("Warning: Initialize failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("Initialize: %v", err))
|
||||
}
|
||||
|
||||
// Get basic device information
|
||||
log.Println("\n=== Getting Device Information ===")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get device information: %v", err)
|
||||
}
|
||||
log.Printf("Camera: %s %s", info.Manufacturer, info.Model)
|
||||
log.Printf("Firmware: %s", info.FirmwareVersion)
|
||||
log.Printf("Serial: %s", info.SerialNumber)
|
||||
|
||||
results.CameraInfo = &CameraInfo{
|
||||
Manufacturer: info.Manufacturer,
|
||||
Model: info.Model,
|
||||
FirmwareVersion: info.FirmwareVersion,
|
||||
SerialNumber: info.SerialNumber,
|
||||
HardwareID: info.HardwareID,
|
||||
}
|
||||
|
||||
// Test NEW Device Service Methods
|
||||
testDeviceService(ctx, client, results)
|
||||
|
||||
// Test NEW Media Service Methods
|
||||
testMediaService(ctx, client, results)
|
||||
|
||||
// Test NEW PTZ Service Methods
|
||||
testPTZService(ctx, client, results)
|
||||
|
||||
// Test NEW Imaging Service Methods
|
||||
testImagingService(ctx, client, results)
|
||||
|
||||
// Save results
|
||||
saveResults(results)
|
||||
|
||||
log.Printf("\n=== Test Complete ===")
|
||||
log.Printf("Results saved to: %s", *output)
|
||||
log.Printf("Total errors: %d", len(results.Errors))
|
||||
}
|
||||
|
||||
func testDeviceService(ctx context.Context, client *onvif.Client, results *TestResults) {
|
||||
log.Println("\n=== Testing Device Service (NEW Methods) ===")
|
||||
|
||||
// Test GetHostname
|
||||
log.Println("\n--- GetHostname ---")
|
||||
if hostname, err := client.GetHostname(ctx); err != nil {
|
||||
log.Printf("❌ GetHostname failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetHostname: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Hostname: %+v", hostname)
|
||||
results.DeviceTests["hostname"] = hostname
|
||||
}
|
||||
|
||||
// Test GetDNS
|
||||
log.Println("\n--- GetDNS ---")
|
||||
if dns, err := client.GetDNS(ctx); err != nil {
|
||||
log.Printf("❌ GetDNS failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetDNS: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ DNS: FromDHCP=%v, SearchDomain=%v", dns.FromDHCP, dns.SearchDomain)
|
||||
log.Printf(" DNSFromDHCP: %+v", dns.DNSFromDHCP)
|
||||
log.Printf(" DNSManual: %+v", dns.DNSManual)
|
||||
results.DeviceTests["dns"] = dns
|
||||
}
|
||||
|
||||
// Test GetNTP
|
||||
log.Println("\n--- GetNTP ---")
|
||||
if ntp, err := client.GetNTP(ctx); err != nil {
|
||||
log.Printf("❌ GetNTP failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetNTP: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ NTP: FromDHCP=%v", ntp.FromDHCP)
|
||||
log.Printf(" NTPFromDHCP: %+v", ntp.NTPFromDHCP)
|
||||
log.Printf(" NTPManual: %+v", ntp.NTPManual)
|
||||
results.DeviceTests["ntp"] = ntp
|
||||
}
|
||||
|
||||
// Test GetNetworkInterfaces
|
||||
log.Println("\n--- GetNetworkInterfaces ---")
|
||||
if interfaces, err := client.GetNetworkInterfaces(ctx); err != nil {
|
||||
log.Printf("❌ GetNetworkInterfaces failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetNetworkInterfaces: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d network interface(s)", len(interfaces))
|
||||
for i, iface := range interfaces {
|
||||
log.Printf(" Interface %d: Token=%s, Name=%s, Enabled=%v",
|
||||
i+1, iface.Token, iface.Info.Name, iface.Enabled)
|
||||
log.Printf(" HwAddress=%s, MTU=%d", iface.Info.HwAddress, iface.Info.MTU)
|
||||
if iface.IPv4 != nil {
|
||||
log.Printf(" IPv4: Enabled=%v, DHCP=%v", iface.IPv4.Enabled, iface.IPv4.Config.DHCP)
|
||||
for _, addr := range iface.IPv4.Config.Manual {
|
||||
log.Printf(" Manual: %s/%d", addr.Address, addr.PrefixLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
results.DeviceTests["network_interfaces"] = interfaces
|
||||
}
|
||||
|
||||
// Test GetScopes
|
||||
log.Println("\n--- GetScopes ---")
|
||||
if scopes, err := client.GetScopes(ctx); err != nil {
|
||||
log.Printf("❌ GetScopes failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetScopes: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d scope(s)", len(scopes))
|
||||
for i, scope := range scopes {
|
||||
log.Printf(" Scope %d: Def=%s, Item=%s", i+1, scope.ScopeDef, scope.ScopeItem)
|
||||
}
|
||||
results.DeviceTests["scopes"] = scopes
|
||||
}
|
||||
|
||||
// Test GetUsers
|
||||
log.Println("\n--- GetUsers ---")
|
||||
if users, err := client.GetUsers(ctx); err != nil {
|
||||
log.Printf("❌ GetUsers failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetUsers: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d user(s)", len(users))
|
||||
for i, user := range users {
|
||||
log.Printf(" User %d: Username=%s, Level=%s", i+1, user.Username, user.UserLevel)
|
||||
}
|
||||
results.DeviceTests["users"] = users
|
||||
}
|
||||
}
|
||||
|
||||
func testMediaService(ctx context.Context, client *onvif.Client, results *TestResults) {
|
||||
log.Println("\n=== Testing Media Service (NEW Methods) ===")
|
||||
|
||||
// Test GetVideoSources
|
||||
log.Println("\n--- GetVideoSources ---")
|
||||
if sources, err := client.GetVideoSources(ctx); err != nil {
|
||||
log.Printf("❌ GetVideoSources failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetVideoSources: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d video source(s)", len(sources))
|
||||
for i, source := range sources {
|
||||
log.Printf(" Source %d: Token=%s, Framerate=%.1f",
|
||||
i+1, source.Token, source.Framerate)
|
||||
if source.Resolution != nil {
|
||||
log.Printf(" Resolution: %dx%d", source.Resolution.Width, source.Resolution.Height)
|
||||
}
|
||||
}
|
||||
results.MediaTests["video_sources"] = sources
|
||||
}
|
||||
|
||||
// Test GetAudioSources
|
||||
log.Println("\n--- GetAudioSources ---")
|
||||
if sources, err := client.GetAudioSources(ctx); err != nil {
|
||||
log.Printf("❌ GetAudioSources failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetAudioSources: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d audio source(s)", len(sources))
|
||||
for i, source := range sources {
|
||||
log.Printf(" Source %d: Token=%s, Channels=%d",
|
||||
i+1, source.Token, source.Channels)
|
||||
}
|
||||
results.MediaTests["audio_sources"] = sources
|
||||
}
|
||||
|
||||
// Test GetAudioOutputs
|
||||
log.Println("\n--- GetAudioOutputs ---")
|
||||
if outputs, err := client.GetAudioOutputs(ctx); err != nil {
|
||||
log.Printf("❌ GetAudioOutputs failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetAudioOutputs: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d audio output(s)", len(outputs))
|
||||
for i, output := range outputs {
|
||||
log.Printf(" Output %d: Token=%s", i+1, output.Token)
|
||||
}
|
||||
results.MediaTests["audio_outputs"] = outputs
|
||||
}
|
||||
|
||||
// Get profiles for further testing
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Could not get profiles: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(profiles) > 0 {
|
||||
log.Printf("\nUsing profile: %s (%s)", profiles[0].Name, profiles[0].Token)
|
||||
results.MediaTests["test_profile_token"] = profiles[0].Token
|
||||
}
|
||||
}
|
||||
|
||||
func testPTZService(ctx context.Context, client *onvif.Client, results *TestResults) {
|
||||
log.Println("\n=== Testing PTZ Service (NEW Methods) ===")
|
||||
|
||||
// Get profiles to find one with PTZ
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Could not get profiles for PTZ tests: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var ptzProfile *onvif.Profile
|
||||
for _, p := range profiles {
|
||||
if p.PTZConfiguration != nil {
|
||||
ptzProfile = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ptzProfile == nil {
|
||||
log.Println("⚠️ No PTZ-enabled profile found, skipping PTZ tests")
|
||||
results.PTZTests["skipped"] = "No PTZ profile found"
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Using PTZ profile: %s (%s)", ptzProfile.Name, ptzProfile.Token)
|
||||
results.PTZTests["test_profile_token"] = ptzProfile.Token
|
||||
|
||||
// Test GetConfigurations
|
||||
log.Println("\n--- GetConfigurations ---")
|
||||
if configs, err := client.GetConfigurations(ctx); err != nil {
|
||||
log.Printf("❌ GetConfigurations failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetConfigurations: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d PTZ configuration(s)", len(configs))
|
||||
for i, cfg := range configs {
|
||||
log.Printf(" Config %d: Token=%s, Name=%s, NodeToken=%s",
|
||||
i+1, cfg.Token, cfg.Name, cfg.NodeToken)
|
||||
}
|
||||
results.PTZTests["configurations"] = configs
|
||||
}
|
||||
|
||||
// Test GetConfiguration
|
||||
if ptzProfile.PTZConfiguration != nil {
|
||||
log.Println("\n--- GetConfiguration ---")
|
||||
if cfg, err := client.GetConfiguration(ctx, ptzProfile.PTZConfiguration.Token); err != nil {
|
||||
log.Printf("❌ GetConfiguration failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetConfiguration: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Configuration: Token=%s, Name=%s", cfg.Token, cfg.Name)
|
||||
results.PTZTests["configuration"] = cfg
|
||||
}
|
||||
}
|
||||
|
||||
// Test GetPresets
|
||||
log.Println("\n--- GetPresets ---")
|
||||
if presets, err := client.GetPresets(ctx, ptzProfile.Token); err != nil {
|
||||
log.Printf("❌ GetPresets failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetPresets: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Found %d preset(s)", len(presets))
|
||||
for i, preset := range presets {
|
||||
log.Printf(" Preset %d: Token=%s, Name=%s", i+1, preset.Token, preset.Name)
|
||||
if preset.PTZPosition != nil {
|
||||
if preset.PTZPosition.PanTilt != nil {
|
||||
log.Printf(" PanTilt: X=%.2f, Y=%.2f",
|
||||
preset.PTZPosition.PanTilt.X, preset.PTZPosition.PanTilt.Y)
|
||||
}
|
||||
if preset.PTZPosition.Zoom != nil {
|
||||
log.Printf(" Zoom: X=%.2f", preset.PTZPosition.Zoom.X)
|
||||
}
|
||||
}
|
||||
}
|
||||
results.PTZTests["presets"] = presets
|
||||
}
|
||||
|
||||
// Test GetStatus
|
||||
log.Println("\n--- GetStatus ---")
|
||||
if status, err := client.GetStatus(ctx, ptzProfile.Token); err != nil {
|
||||
log.Printf("❌ GetStatus failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("PTZ GetStatus: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ PTZ Status:")
|
||||
if status.Position != nil {
|
||||
if status.Position.PanTilt != nil {
|
||||
log.Printf(" Position PanTilt: X=%.2f, Y=%.2f",
|
||||
status.Position.PanTilt.X, status.Position.PanTilt.Y)
|
||||
}
|
||||
if status.Position.Zoom != nil {
|
||||
log.Printf(" Position Zoom: X=%.2f", status.Position.Zoom.X)
|
||||
}
|
||||
}
|
||||
if status.MoveStatus != nil {
|
||||
log.Printf(" MoveStatus: PanTilt=%s, Zoom=%s",
|
||||
status.MoveStatus.PanTilt, status.MoveStatus.Zoom)
|
||||
}
|
||||
results.PTZTests["status"] = status
|
||||
}
|
||||
}
|
||||
|
||||
func testImagingService(ctx context.Context, client *onvif.Client, results *TestResults) {
|
||||
log.Println("\n=== Testing Imaging Service (NEW Methods) ===")
|
||||
|
||||
// Get video sources first
|
||||
sources, err := client.GetVideoSources(ctx)
|
||||
if err != nil || len(sources) == 0 {
|
||||
log.Printf("⚠️ Could not get video sources for imaging tests: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
videoSourceToken := sources[0].Token
|
||||
log.Printf("Using video source: %s", videoSourceToken)
|
||||
results.ImagingTests["test_video_source_token"] = videoSourceToken
|
||||
|
||||
// Test GetOptions
|
||||
log.Println("\n--- GetOptions ---")
|
||||
if options, err := client.GetOptions(ctx, videoSourceToken); err != nil {
|
||||
log.Printf("❌ GetOptions failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetOptions: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Imaging Options:")
|
||||
if options.Brightness != nil {
|
||||
log.Printf(" Brightness: Min=%.1f, Max=%.1f", options.Brightness.Min, options.Brightness.Max)
|
||||
}
|
||||
if options.ColorSaturation != nil {
|
||||
log.Printf(" ColorSaturation: Min=%.1f, Max=%.1f", options.ColorSaturation.Min, options.ColorSaturation.Max)
|
||||
}
|
||||
if options.Contrast != nil {
|
||||
log.Printf(" Contrast: Min=%.1f, Max=%.1f", options.Contrast.Min, options.Contrast.Max)
|
||||
}
|
||||
results.ImagingTests["options"] = options
|
||||
}
|
||||
|
||||
// Test GetMoveOptions
|
||||
log.Println("\n--- GetMoveOptions ---")
|
||||
if moveOptions, err := client.GetMoveOptions(ctx, videoSourceToken); err != nil {
|
||||
log.Printf("❌ GetMoveOptions failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("GetMoveOptions: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Move Options:")
|
||||
if moveOptions.Absolute != nil {
|
||||
log.Printf(" Absolute Position: Min=%.1f, Max=%.1f",
|
||||
moveOptions.Absolute.Position.Min, moveOptions.Absolute.Position.Max)
|
||||
log.Printf(" Absolute Speed: Min=%.1f, Max=%.1f",
|
||||
moveOptions.Absolute.Speed.Min, moveOptions.Absolute.Speed.Max)
|
||||
}
|
||||
if moveOptions.Relative != nil {
|
||||
log.Printf(" Relative Distance: Min=%.1f, Max=%.1f",
|
||||
moveOptions.Relative.Distance.Min, moveOptions.Relative.Distance.Max)
|
||||
}
|
||||
if moveOptions.Continuous != nil {
|
||||
log.Printf(" Continuous Speed: Min=%.1f, Max=%.1f",
|
||||
moveOptions.Continuous.Speed.Min, moveOptions.Continuous.Speed.Max)
|
||||
}
|
||||
results.ImagingTests["move_options"] = moveOptions
|
||||
}
|
||||
|
||||
// Test GetImagingStatus
|
||||
log.Println("\n--- GetImagingStatus ---")
|
||||
if status, err := client.GetImagingStatus(ctx, videoSourceToken); err != nil {
|
||||
log.Printf("❌ GetImagingStatus failed: %v", err)
|
||||
results.Errors = append(results.Errors, fmt.Sprintf("Imaging GetImagingStatus: %v", err))
|
||||
} else {
|
||||
log.Printf("✅ Imaging Status:")
|
||||
if status.FocusStatus != nil {
|
||||
log.Printf(" Focus Position: %.2f", status.FocusStatus.Position)
|
||||
log.Printf(" Focus MoveStatus: %s", status.FocusStatus.MoveStatus)
|
||||
if status.FocusStatus.Error != "" {
|
||||
log.Printf(" Focus Error: %s", status.FocusStatus.Error)
|
||||
}
|
||||
}
|
||||
results.ImagingTests["status"] = status
|
||||
}
|
||||
}
|
||||
|
||||
func saveResults(results *TestResults) {
|
||||
data, err := json.MarshalIndent(results, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal results: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(*output, data, 0644); err != nil {
|
||||
log.Fatalf("Failed to write results: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
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
|
||||
// })
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Camera connection details
|
||||
endpoint := "http://192.168.1.201/onvif/device_service"
|
||||
username := "service"
|
||||
password := "Service.1234"
|
||||
|
||||
fmt.Println("Connecting to ONVIF camera at 192.168.1.201...")
|
||||
|
||||
// Create a new ONVIF client
|
||||
client, err := onvif.NewClient(
|
||||
endpoint,
|
||||
onvif.WithCredentials(username, password),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get device information
|
||||
fmt.Println("\nRetrieving device information...")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get device information: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nDevice Information:\n")
|
||||
fmt.Printf(" Manufacturer: %s\n", info.Manufacturer)
|
||||
fmt.Printf(" Model: %s\n", info.Model)
|
||||
fmt.Printf(" Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf(" Serial Number: %s\n", info.SerialNumber)
|
||||
fmt.Printf(" Hardware ID: %s\n", info.HardwareID)
|
||||
|
||||
// Initialize client (discover service endpoints)
|
||||
fmt.Println("\nInitializing client and discovering services...")
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("Failed to initialize client: %v", err)
|
||||
}
|
||||
|
||||
// Get media profiles
|
||||
fmt.Println("\nRetrieving media profiles...")
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get profiles: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nFound %d profile(s):\n", len(profiles))
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf("\nProfile #%d:\n", i+1)
|
||||
fmt.Printf(" Token: %s\n", profile.Token)
|
||||
fmt.Printf(" Name: %s\n", profile.Name)
|
||||
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" Video Encoding: %s\n", profile.VideoEncoderConfiguration.Encoding)
|
||||
if profile.VideoEncoderConfiguration.Resolution != nil {
|
||||
fmt.Printf(" Resolution: %dx%d\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height)
|
||||
}
|
||||
fmt.Printf(" Quality: %.1f\n", profile.VideoEncoderConfiguration.Quality)
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Stream URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Stream URI: %s\n", streamURI.URI)
|
||||
}
|
||||
|
||||
// Get snapshot URI
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" Snapshot URI: Error - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Snapshot URI: %s\n", snapshotURI.URI)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nDone!")
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524a/onvif-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("🧪 Testing ONVIF Server with Client Library")
|
||||
fmt.Println("===========================================")
|
||||
fmt.Println()
|
||||
|
||||
// Create client
|
||||
client, err := onvif.NewClient(
|
||||
"http://localhost:8080/onvif/device_service",
|
||||
onvif.WithCredentials("admin", "admin"),
|
||||
onvif.WithTimeout(30*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Test 1: Get device information
|
||||
fmt.Println("📋 Test 1: Getting Device Information...")
|
||||
info, err := client.GetDeviceInformation(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to get device info: %v", err)
|
||||
}
|
||||
fmt.Printf("✅ Device: %s %s\n", info.Manufacturer, info.Model)
|
||||
fmt.Printf(" Firmware: %s\n", info.FirmwareVersion)
|
||||
fmt.Printf(" Serial: %s\n", info.SerialNumber)
|
||||
fmt.Println()
|
||||
|
||||
// Test 2: Initialize and discover services
|
||||
fmt.Println("🔍 Test 2: Discovering Services...")
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
log.Fatalf("❌ Failed to initialize: %v", err)
|
||||
}
|
||||
fmt.Println("✅ Services discovered successfully")
|
||||
fmt.Println()
|
||||
|
||||
// Test 3: Get capabilities
|
||||
fmt.Println("🔧 Test 3: Getting Capabilities...")
|
||||
caps, err := client.GetCapabilities(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to get capabilities: %v", err)
|
||||
}
|
||||
fmt.Println("✅ Capabilities:")
|
||||
if caps.Media != nil {
|
||||
fmt.Println(" ✓ Media Service")
|
||||
}
|
||||
if caps.PTZ != nil {
|
||||
fmt.Println(" ✓ PTZ Service")
|
||||
}
|
||||
if caps.Imaging != nil {
|
||||
fmt.Println(" ✓ Imaging Service")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 4: Get media profiles
|
||||
fmt.Println("🎬 Test 4: Getting Media Profiles...")
|
||||
profiles, err := client.GetProfiles(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ Failed to get profiles: %v", err)
|
||||
}
|
||||
fmt.Printf("✅ Found %d camera profiles:\n", len(profiles))
|
||||
for i, profile := range profiles {
|
||||
fmt.Printf("\n Profile %d: %s\n", i+1, profile.Name)
|
||||
fmt.Printf(" Token: %s\n", profile.Token)
|
||||
|
||||
if profile.VideoEncoderConfiguration != nil {
|
||||
fmt.Printf(" Video: %dx%d @ %s\n",
|
||||
profile.VideoEncoderConfiguration.Resolution.Width,
|
||||
profile.VideoEncoderConfiguration.Resolution.Height,
|
||||
profile.VideoEncoderConfiguration.Encoding)
|
||||
}
|
||||
|
||||
// Get stream URI
|
||||
streamURI, err := client.GetStreamURI(ctx, profile.Token)
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠️ Failed to get stream URI: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" RTSP: %s\n", streamURI.URI)
|
||||
}
|
||||
|
||||
// Get snapshot URI if available
|
||||
snapshotURI, err := client.GetSnapshotURI(ctx, profile.Token)
|
||||
if err == nil {
|
||||
fmt.Printf(" Snapshot: %s\n", snapshotURI.URI)
|
||||
}
|
||||
|
||||
// Test PTZ if available
|
||||
if profile.PTZConfiguration != nil {
|
||||
fmt.Println(" PTZ: ✓ Enabled")
|
||||
|
||||
// Get PTZ status
|
||||
status, err := client.GetStatus(ctx, profile.Token)
|
||||
if err == nil {
|
||||
fmt.Printf(" Position: Pan=%.1f°, Tilt=%.1f°, Zoom=%.2f\n",
|
||||
status.Position.PanTilt.X,
|
||||
status.Position.PanTilt.Y,
|
||||
status.Position.Zoom.X)
|
||||
}
|
||||
|
||||
// Get presets
|
||||
presets, err := client.GetPresets(ctx, profile.Token)
|
||||
if err == nil && len(presets) > 0 {
|
||||
fmt.Printf(" Presets: %d available\n", len(presets))
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test 5: PTZ control (if available)
|
||||
if len(profiles) > 0 && profiles[0].PTZConfiguration != nil {
|
||||
fmt.Println("🎮 Test 5: Testing PTZ Control...")
|
||||
profileToken := profiles[0].Token
|
||||
|
||||
// Absolute move to home position
|
||||
fmt.Println(" Moving to home position...")
|
||||
position := &onvif.PTZVector{
|
||||
PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0},
|
||||
Zoom: &onvif.Vector1D{X: 0.0},
|
||||
}
|
||||
if err := client.AbsoluteMove(ctx, profileToken, position, nil); err != nil {
|
||||
fmt.Printf(" ⚠️ Failed to move: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" ✅ Moved to home position")
|
||||
}
|
||||
|
||||
// Wait a moment
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Get status after move
|
||||
status, err := client.GetStatus(ctx, profileToken)
|
||||
if err == nil {
|
||||
fmt.Printf(" New position: Pan=%.1f°, Tilt=%.1f°, Zoom=%.2f\n",
|
||||
status.Position.PanTilt.X,
|
||||
status.Position.PanTilt.Y,
|
||||
status.Position.Zoom.X)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Summary
|
||||
fmt.Println("╔════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ ║")
|
||||
fmt.Println("║ ✅ All Tests Passed! ✅ ║")
|
||||
fmt.Println("║ ║")
|
||||
fmt.Println("╚════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
fmt.Println("🎉 ONVIF Server is working correctly!")
|
||||
fmt.Println(" • Device Service: ✓")
|
||||
fmt.Println(" • Media Service: ✓")
|
||||
fmt.Println(" • PTZ Service: ✓")
|
||||
fmt.Printf(" • Multi-lens Camera: ✓ (%d profiles)\n", len(profiles))
|
||||
}
|
||||
Reference in New Issue
Block a user