Add camera test framework and initial tests for Bosch FLEXIDOME indoor 5100i IR
- Introduced a new directory `testdata/captures/` containing captured XML archives and README documentation for the camera test framework. - Added a mock server implementation to replay captured SOAP responses for testing. - Created automated tests for Bosch FLEXIDOME indoor 5100i IR using captured responses, validating device information, system date and time, capabilities, and profiles. - Implemented enhanced device features tests, covering hostname, DNS, NTP, network interfaces, scopes, and user management. - Added support for enhanced media and imaging features, including video and audio sources, and imaging options. - Updated types to include new configurations and options for network, imaging, and device capabilities.
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524A/go-onvif"
|
||||
)
|
||||
|
||||
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 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,255 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524A/go-onvif"
|
||||
"github.com/0x524A/go-onvif/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/go-onvif/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,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 go-onvif 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 go-onvif 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,444 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/0x524A/go-onvif"
|
||||
)
|
||||
|
||||
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,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/0x524A/go-onvif"
|
||||
)
|
||||
|
||||
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!")
|
||||
}
|
||||
Reference in New Issue
Block a user