diff --git a/CHANGELOG.md b/CHANGELOG.md index 88891b3..04c962e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,11 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **Project Structure**: Implemented ideal Go project layout - Moved `soap/` to `internal/soap/` (private implementation) + - Moved `test/test-server.go` to `examples/test-server/` for clarity + - Removed empty `test/` directory - Public API remains at root level for clean imports - Follows Standard Go Project Layout for libraries - Updated all imports throughout codebase - See `docs/PROJECT_STRUCTURE.md` and `docs/ARCHITECTURE.md` for details - Updated `docs/ARCHITECTURE.md` to reflect new project structure +- Updated module path from `github.com/0x524A/onvif-go` to `github.com/0x524a/onvif-go` (lowercase) - ONVIF Client with context support - Device service implementation - GetDeviceInformation diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 072fcbc..c82fe89 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -20,7 +20,7 @@ onvif-go/ ├── cmd/ # Command-line tools ├── examples/ # Usage examples ├── docs/ # Documentation -├── test/ # Testing utilities +├── testing/ # Testing helpers └── testdata/ # Test fixtures ``` diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index a9632ec..9effc88 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -50,6 +50,7 @@ onvif-go/ │ ├── imaging-settings/ # Imaging configuration │ ├── complete-demo/ # Full feature demo │ ├── simplified-endpoint/ # Endpoint format demo +│ ├── test-server/ # Server testing example │ └── .../ # Additional examples │ ├── docs/ # Documentation @@ -58,7 +59,6 @@ onvif-go/ │ ├── SIMPLIFIED_ENDPOINT.md # Endpoint API docs │ └── .../ # Additional documentation │ -├── test/ # Additional test utilities ├── testdata/ # Test fixtures and data ├── testing/ # Testing helpers │ diff --git a/examples/test-server/main.go b/examples/test-server/main.go index e0f5be6..a48ebda 100644 --- a/examples/test-server/main.go +++ b/examples/test-server/main.go @@ -7,184 +7,157 @@ import ( "time" "github.com/0x524a/onvif-go" - "github.com/0x524a/onvif-go/server" ) func main() { - fmt.Println("🧪 Testing ONVIF Server Implementation") - fmt.Println("======================================") + fmt.Println("🧪 Testing ONVIF Server with Client Library") + fmt.Println("===========================================") fmt.Println() - // Create and start server in background - config := server.DefaultConfig() - config.Port = 8081 // Use different port to avoid conflicts - - srv, err := server.New(config) - if err != nil { - log.Fatalf("Failed to create server: %v", err) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Start server in background - serverReady := make(chan bool) - go func() { - // Give server a moment to start - time.Sleep(500 * time.Millisecond) - serverReady <- true - - if err := srv.Start(ctx); err != nil { - log.Printf("Server error: %v", err) - } - }() - - // Wait for server to be ready - <-serverReady - fmt.Println("✅ Server started on port 8081") - fmt.Println() - - // Create ONVIF client + // Create client client, err := onvif.NewClient( - "http://localhost:8081/onvif/device_service", + "http://localhost:8080/onvif/device_service", onvif.WithCredentials("admin", "admin"), - onvif.WithTimeout(10*time.Second), + onvif.WithTimeout(30*time.Second), ) if err != nil { - log.Fatalf("Failed to create client: %v", err) + log.Fatalf("❌ Failed to create client: %v", err) } - testCtx := context.Background() + ctx := context.Background() - // Test 1: Get Device Information - fmt.Println("Test 1: GetDeviceInformation") - info, err := client.GetDeviceInformation(testCtx) + // Test 1: Get device information + fmt.Println("📋 Test 1: Getting Device Information...") + info, err := client.GetDeviceInformation(ctx) if err != nil { - log.Fatalf("❌ GetDeviceInformation failed: %v", err) + log.Fatalf("❌ Failed to get device info: %v", err) } - fmt.Printf("✅ Device: %s %s (Firmware: %s)\n", info.Manufacturer, info.Model, info.FirmwareVersion) + 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: Get Capabilities - fmt.Println("Test 2: GetCapabilities") - if err := client.Initialize(testCtx); err != nil { - log.Fatalf("❌ Initialize (GetCapabilities) failed: %v", err) + // 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("✅ Capabilities retrieved successfully") + fmt.Println("✅ Services discovered successfully") fmt.Println() - // Test 3: Get Profiles - fmt.Println("Test 3: GetProfiles") - profiles, err := client.GetProfiles(testCtx) + // Test 3: Get capabilities + fmt.Println("🔧 Test 3: Getting Capabilities...") + caps, err := client.GetCapabilities(ctx) if err != nil { - log.Fatalf("❌ GetProfiles failed: %v", err) + log.Fatalf("❌ Failed to get capabilities: %v", err) } - fmt.Printf("✅ Found %d profiles:\n", len(profiles)) + 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(" [%d] %s (Token: %s)\n", i+1, profile.Name, profile.Token) + 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", + 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 4: Get Stream URI - if len(profiles) > 0 { - fmt.Println("Test 4: GetStreamURI") - streamURI, err := client.GetStreamURI(testCtx, profiles[0].Token) - if err != nil { - log.Fatalf("❌ GetStreamURI failed: %v", err) - } - fmt.Printf("✅ Stream URI: %s\n", streamURI.URI) - fmt.Println() - } - - // Test 5: Get Snapshot URI - if len(profiles) > 0 { - fmt.Println("Test 5: GetSnapshotURI") - snapshotURI, err := client.GetSnapshotURI(testCtx, profiles[0].Token) - if err != nil { - log.Fatalf("❌ GetSnapshotURI failed: %v", err) - } - fmt.Printf("✅ Snapshot URI: %s\n", snapshotURI.URI) - fmt.Println() - } - - // Test 6: PTZ Status (if PTZ is available) + // Test 5: PTZ control (if available) if len(profiles) > 0 && profiles[0].PTZConfiguration != nil { - fmt.Println("Test 6: PTZ GetStatus") - status, err := client.GetStatus(testCtx, profiles[0].Token) - if err != nil { - log.Fatalf("❌ GetStatus failed: %v", err) - } - fmt.Printf("✅ PTZ Position: Pan=%.2f, Tilt=%.2f, Zoom=%.2f\n", - status.Position.PanTilt.X, - status.Position.PanTilt.Y, - status.Position.Zoom.X) - fmt.Println() - - // Test 7: PTZ Absolute Move - fmt.Println("Test 7: PTZ AbsoluteMove") + 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: 10.0, Y: -5.0}, - Zoom: &onvif.Vector1D{X: 0.5}, + PanTilt: &onvif.Vector2D{X: 0.0, Y: 0.0}, + Zoom: &onvif.Vector1D{X: 0.0}, } - if err := client.AbsoluteMove(testCtx, profiles[0].Token, position, nil); err != nil { - log.Fatalf("❌ AbsoluteMove failed: %v", err) + 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") } - fmt.Println("✅ PTZ moved to absolute position") - fmt.Println() - // Wait a bit for movement to complete - time.Sleep(600 * time.Millisecond) + // Wait a moment + time.Sleep(500 * time.Millisecond) - // Verify new position - fmt.Println("Test 8: Verify PTZ Position") - status, err = client.GetStatus(testCtx, profiles[0].Token) - if err != nil { - log.Fatalf("❌ GetStatus failed: %v", err) - } - fmt.Printf("✅ New PTZ Position: Pan=%.2f, Tilt=%.2f, Zoom=%.2f\n", - status.Position.PanTilt.X, - status.Position.PanTilt.Y, - status.Position.Zoom.X) - fmt.Println() - - // Test 9: PTZ Presets - fmt.Println("Test 9: Get PTZ Presets") - presets, err := client.GetPresets(testCtx, profiles[0].Token) - if err != nil { - log.Fatalf("❌ GetPresets failed: %v", err) - } - fmt.Printf("✅ Found %d presets:\n", len(presets)) - for i, preset := range presets { - fmt.Printf(" [%d] %s (Token: %s)\n", i+1, preset.Name, preset.Token) + // 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() } - // Test 10: Get System Date and Time - fmt.Println("Test 10: GetSystemDateAndTime") - _, err = client.GetSystemDateAndTime(testCtx) - if err != nil { - log.Fatalf("❌ GetSystemDateAndTime failed: %v", err) - } - fmt.Println("✅ System date and time retrieved successfully") + // Summary + fmt.Println("╔════════════════════════════════════════════════════════════╗") + fmt.Println("║ ║") + fmt.Println("║ ✅ All Tests Passed! ✅ ║") + fmt.Println("║ ║") + fmt.Println("╚════════════════════════════════════════════════════════════╝") fmt.Println() - - // All tests passed! - fmt.Println("╔══════════════════════════════════════════════════════════╗") - fmt.Println("║ ║") - fmt.Println("║ ✅ All Tests Passed! ONVIF Server is working! ✅ ║") - fmt.Println("║ ║") - fmt.Println("╚══════════════════════════════════════════════════════════╝") - fmt.Println() - - // Stop the server - cancel() - time.Sleep(500 * time.Millisecond) + 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)) } diff --git a/test/test-server.go b/test/test-server.go deleted file mode 100644 index a48ebda..0000000 --- a/test/test-server.go +++ /dev/null @@ -1,163 +0,0 @@ -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)) -}