refactor: introduce constants for improved maintainability in tests and server configurations
- Added constants for test endpoints, usernames, and XML headers in client_test.go and device_certificates_test.go to enhance readability and reduce hardcoded values. - Updated various test cases to utilize these constants, ensuring consistency across tests. - Refactored imaging settings and server configurations to use defined constants for default values, improving clarity and maintainability in server/device.go and server/imaging.go. - Enhanced comments throughout the code to clarify functionality and adhere to best practices.
This commit is contained in:
@@ -301,3 +301,4 @@ The library provides **complete coverage** of all essential ONVIF Media and Devi
|
|||||||
*Report generated from comprehensive testing on December 2, 2025*
|
*Report generated from comprehensive testing on December 2, 2025*
|
||||||
*Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)*
|
*Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -100,3 +100,4 @@ New types added to `types.go`:
|
|||||||
*Implementation completed: December 2, 2025*
|
*Implementation completed: December 2, 2025*
|
||||||
*Total Operations: 79/79 (100%)*
|
*Total Operations: 79/79 (100%)*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -167,3 +167,4 @@ The library provides **complete coverage** of all essential ONVIF operations req
|
|||||||
*Last Updated: December 2, 2025*
|
*Last Updated: December 2, 2025*
|
||||||
*Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)*
|
*Camera: Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066)*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -228,3 +228,4 @@ The missing operations are primarily **optional discovery and management operati
|
|||||||
*Analysis based on ONVIF Media Service WSDL v1.0*
|
*Analysis based on ONVIF Media Service WSDL v1.0*
|
||||||
*Last Updated: December 1, 2025*
|
*Last Updated: December 1, 2025*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -208,3 +208,4 @@ Implement Video Analytics and Audio Decoder operations if needed for specific us
|
|||||||
*Reference: https://www.onvif.org/ver10/media/wsdl/media.wsdl*
|
*Reference: https://www.onvif.org/ver10/media/wsdl/media.wsdl*
|
||||||
*Last Updated: December 2, 2025*
|
*Last Updated: December 2, 2025*
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+33
-26
@@ -13,6 +13,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testEndpoint = "http://192.168.1.100/onvif"
|
||||||
|
testUsername = "admin"
|
||||||
|
testRealm = "test-realm"
|
||||||
|
testOpaque = "test-opaque"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNormalizeEndpoint(t *testing.T) {
|
func TestNormalizeEndpoint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -184,7 +191,7 @@ type MockONVIFServer struct {
|
|||||||
func NewMockONVIFServer() *MockONVIFServer {
|
func NewMockONVIFServer() *MockONVIFServer {
|
||||||
mock := &MockONVIFServer{
|
mock := &MockONVIFServer{
|
||||||
responses: make(map[string]string),
|
responses: make(map[string]string),
|
||||||
username: "admin",
|
username: testUsername,
|
||||||
password: "password",
|
password: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,10 +381,10 @@ func TestNewClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientOptions(t *testing.T) {
|
func TestClientOptions(t *testing.T) {
|
||||||
endpoint := "http://192.168.1.100/onvif"
|
endpoint := testEndpoint
|
||||||
|
|
||||||
t.Run("WithCredentials", func(t *testing.T) {
|
t.Run("WithCredentials", func(t *testing.T) {
|
||||||
username := "admin"
|
username := testUsername
|
||||||
password := "test123"
|
password := "test123"
|
||||||
|
|
||||||
client, err := NewClient(endpoint, WithCredentials(username, password))
|
client, err := NewClient(endpoint, WithCredentials(username, password))
|
||||||
@@ -422,7 +429,7 @@ func TestClientOptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientEndpoint(t *testing.T) {
|
func TestClientEndpoint(t *testing.T) {
|
||||||
endpoint := "http://192.168.1.100/onvif"
|
endpoint := testEndpoint
|
||||||
client, err := NewClient(endpoint)
|
client, err := NewClient(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() error = %v", err)
|
t.Fatalf("NewClient() error = %v", err)
|
||||||
@@ -462,7 +469,7 @@ func TestGetDeviceInformationWithMockServer(t *testing.T) {
|
|||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
server.URL,
|
server.URL,
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() failed: %v", err)
|
t.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -504,7 +511,7 @@ func TestInitializeEndpointDiscovery(t *testing.T) {
|
|||||||
// Test that Initialize can handle network errors gracefully
|
// Test that Initialize can handle network errors gracefully
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
"http://192.168.999.999/onvif/device_service", // non-existent IP
|
"http://192.168.999.999/onvif/device_service", // non-existent IP
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
WithTimeout(1*time.Second),
|
WithTimeout(1*time.Second),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -526,7 +533,7 @@ func TestInitializeEndpointDiscovery(t *testing.T) {
|
|||||||
func TestGetProfilesRequiresInitialization(t *testing.T) {
|
func TestGetProfilesRequiresInitialization(t *testing.T) {
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
"http://192.168.1.100/onvif/device_service",
|
"http://192.168.1.100/onvif/device_service",
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() failed: %v", err)
|
t.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -548,7 +555,7 @@ func TestContextTimeout(t *testing.T) {
|
|||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
mock.URL(),
|
mock.URL(),
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() failed: %v", err)
|
t.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -591,7 +598,7 @@ func TestONVIFError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkNewClient(b *testing.B) {
|
func BenchmarkNewClient(b *testing.B) {
|
||||||
endpoint := "http://192.168.1.100/onvif"
|
endpoint := testEndpoint
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := NewClient(endpoint)
|
_, err := NewClient(endpoint)
|
||||||
@@ -607,7 +614,7 @@ func BenchmarkGetDeviceInformation(b *testing.B) {
|
|||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
mock.URL(),
|
mock.URL(),
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("NewClient() failed: %v", err)
|
b.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -629,7 +636,7 @@ func ExampleClient_GetDeviceInformation() {
|
|||||||
// Create client
|
// Create client
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
"http://192.168.1.100/onvif/device_service",
|
"http://192.168.1.100/onvif/device_service",
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
WithTimeout(30*time.Second),
|
WithTimeout(30*time.Second),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -760,7 +767,7 @@ func TestInitializeWithLocalhostURLs(t *testing.T) {
|
|||||||
// Create client pointing to mock server
|
// Create client pointing to mock server
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
mock.URL()+"/onvif/device_service",
|
mock.URL()+"/onvif/device_service",
|
||||||
WithCredentials("admin", "admin"),
|
WithCredentials(testUsername, testUsername),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create client: %v", err)
|
t.Fatalf("Failed to create client: %v", err)
|
||||||
@@ -806,7 +813,7 @@ func TestDownloadFileWithBasicAuth(t *testing.T) {
|
|||||||
// Create a mock server that requires basic auth
|
// Create a mock server that requires basic auth
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
username, password, ok := r.BasicAuth()
|
username, password, ok := r.BasicAuth()
|
||||||
if !ok || username != "admin" || password != "password" {
|
if !ok || username != testUsername || password != "password" {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -819,7 +826,7 @@ func TestDownloadFileWithBasicAuth(t *testing.T) {
|
|||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
server.URL,
|
server.URL,
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() failed: %v", err)
|
t.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -839,8 +846,8 @@ func TestDownloadFileWithBasicAuth(t *testing.T) {
|
|||||||
// TestDownloadFileWithDigestAuth tests DownloadFile with digest authentication.
|
// TestDownloadFileWithDigestAuth tests DownloadFile with digest authentication.
|
||||||
func TestDownloadFileWithDigestAuth(t *testing.T) {
|
func TestDownloadFileWithDigestAuth(t *testing.T) {
|
||||||
nonce := "test-nonce-12345"
|
nonce := "test-nonce-12345"
|
||||||
realm := "test-realm"
|
realm := testRealm
|
||||||
opaque := "test-opaque"
|
opaque := testOpaque
|
||||||
|
|
||||||
// Create a mock server that requires digest auth
|
// Create a mock server that requires digest auth
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -863,7 +870,7 @@ func TestDownloadFileWithDigestAuth(t *testing.T) {
|
|||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
server.URL,
|
server.URL,
|
||||||
WithCredentials("admin", "password"),
|
WithCredentials(testUsername, "password"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewClient() failed: %v", err)
|
t.Fatalf("NewClient() failed: %v", err)
|
||||||
@@ -969,8 +976,8 @@ func TestDownloadFileNetworkError(t *testing.T) {
|
|||||||
// TestDigestAuthTransport tests the digest authentication transport.
|
// TestDigestAuthTransport tests the digest authentication transport.
|
||||||
func TestDigestAuthTransport(t *testing.T) {
|
func TestDigestAuthTransport(t *testing.T) {
|
||||||
nonce := "test-nonce"
|
nonce := "test-nonce"
|
||||||
realm := "test-realm"
|
realm := testRealm
|
||||||
opaque := "test-opaque"
|
opaque := testOpaque
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
authHeader := r.Header.Get("Authorization")
|
authHeader := r.Header.Get("Authorization")
|
||||||
@@ -983,7 +990,7 @@ func TestDigestAuthTransport(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Verify digest auth header contains required fields
|
// Verify digest auth header contains required fields
|
||||||
if !strings.Contains(authHeader, `username="admin"`) {
|
if !strings.Contains(authHeader, `username="`+testUsername+`"`) {
|
||||||
t.Error("Digest auth header missing username")
|
t.Error("Digest auth header missing username")
|
||||||
}
|
}
|
||||||
if !strings.Contains(authHeader, `realm="`+realm+`"`) {
|
if !strings.Contains(authHeader, `realm="`+realm+`"`) {
|
||||||
@@ -1007,7 +1014,7 @@ func TestDigestAuthTransport(t *testing.T) {
|
|||||||
digestClient := &http.Client{
|
digestClient := &http.Client{
|
||||||
Transport: &digestAuthTransport{
|
Transport: &digestAuthTransport{
|
||||||
transport: tr,
|
transport: tr,
|
||||||
username: "admin",
|
username: testUsername,
|
||||||
password: "password",
|
password: "password",
|
||||||
},
|
},
|
||||||
Timeout: DefaultTimeout,
|
Timeout: DefaultTimeout,
|
||||||
@@ -1039,9 +1046,9 @@ func TestExtractParam(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "extract realm",
|
name: "extract realm",
|
||||||
authHeader: `Digest realm="test-realm", nonce="123"`,
|
authHeader: `Digest realm="` + testRealm + `", nonce="123"`,
|
||||||
param: "realm",
|
param: "realm",
|
||||||
expected: "test-realm",
|
expected: testRealm,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "extract nonce",
|
name: "extract nonce",
|
||||||
@@ -1310,8 +1317,8 @@ func TestDownloadFileContextCancellation(t *testing.T) {
|
|||||||
// This verifies that the nc field is properly protected from race conditions.
|
// This verifies that the nc field is properly protected from race conditions.
|
||||||
func TestDigestAuthTransportConcurrency(t *testing.T) {
|
func TestDigestAuthTransportConcurrency(t *testing.T) {
|
||||||
nonce := "test-nonce"
|
nonce := "test-nonce"
|
||||||
realm := "test-realm"
|
realm := testRealm
|
||||||
opaque := "test-opaque"
|
opaque := testOpaque
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
authHeader := r.Header.Get("Authorization")
|
authHeader := r.Header.Get("Authorization")
|
||||||
@@ -1342,7 +1349,7 @@ func TestDigestAuthTransportConcurrency(t *testing.T) {
|
|||||||
// Create a single transport instance that will be used concurrently
|
// Create a single transport instance that will be used concurrently
|
||||||
digestTransport := &digestAuthTransport{
|
digestTransport := &digestAuthTransport{
|
||||||
transport: tr,
|
transport: tr,
|
||||||
username: "admin",
|
username: testUsername,
|
||||||
password: "password",
|
password: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
bufferSize1024 = 1024
|
bufferSize1024 = 1024
|
||||||
largeASCIIWidth = 160
|
largeASCIIWidth = 160
|
||||||
largeASCIIHeight = 50
|
largeASCIIHeight = 50
|
||||||
|
defaultQuality = "medium"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultASCIIConfig returns a sensible default configuration.
|
// DefaultASCIIConfig returns a sensible default configuration.
|
||||||
@@ -70,7 +71,7 @@ func ImageToASCII(imageData []byte, config ASCIIConfig) (string, error) {
|
|||||||
// imageToASCIIFromImage is the core conversion function.
|
// imageToASCIIFromImage is the core conversion function.
|
||||||
//
|
//
|
||||||
//nolint:gocyclo // Image to ASCII conversion has high complexity due to multiple pixel processing paths
|
//nolint:gocyclo // Image to ASCII conversion has high complexity due to multiple pixel processing paths
|
||||||
func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (string, error) {
|
func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (string, error) { //nolint:unparam // format reserved for future use
|
||||||
// Validate configuration
|
// Validate configuration
|
||||||
if config.Width <= 0 {
|
if config.Width <= 0 {
|
||||||
config.Width = 120
|
config.Width = 120
|
||||||
@@ -79,7 +80,7 @@ func imageToASCIIFromImage(img image.Image, config ASCIIConfig, format string) (
|
|||||||
config.Height = defaultASCIIHeight
|
config.Height = defaultASCIIHeight
|
||||||
}
|
}
|
||||||
if config.Quality == "" {
|
if config.Quality == "" {
|
||||||
config.Quality = "medium"
|
config.Quality = defaultQuality
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select character set based on quality
|
// Select character set based on quality
|
||||||
@@ -220,7 +221,7 @@ type ImageInfo struct {
|
|||||||
|
|
||||||
// formatBytes converts bytes to human-readable format.
|
// formatBytes converts bytes to human-readable format.
|
||||||
func formatBytes(bytes int64) string {
|
func formatBytes(bytes int64) string {
|
||||||
if bytes < 1024 {
|
if bytes < bufferSize1024 {
|
||||||
return fmt.Sprintf("%d B", bytes)
|
return fmt.Sprintf("%d B", bytes)
|
||||||
}
|
}
|
||||||
const kbSize = 1024
|
const kbSize = 1024
|
||||||
|
|||||||
+24
-14
@@ -23,6 +23,7 @@ const (
|
|||||||
ptzTimeoutSeconds = 30
|
ptzTimeoutSeconds = 30
|
||||||
maxRetries = 3
|
maxRetries = 3
|
||||||
readBufferSize = 5
|
readBufferSize = 5
|
||||||
|
defaultBrightness = "50.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
@@ -114,7 +115,7 @@ func (c *CLI) discoverCameras() {
|
|||||||
|
|
||||||
// Try auto-discovery first (no specific interface)
|
// Try auto-discovery first (no specific interface)
|
||||||
fmt.Println("⏳ Attempting auto-discovery on default interface...")
|
fmt.Println("⏳ Attempting auto-discovery on default interface...")
|
||||||
devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, &discovery.DiscoverOptions{})
|
devices, err := discovery.DiscoverWithOptions(ctx, defaultRetryDelay*time.Second, &discovery.DiscoverOptions{})
|
||||||
|
|
||||||
// If auto-discovery fails or finds nothing, offer interface selection
|
// If auto-discovery fails or finds nothing, offer interface selection
|
||||||
if err != nil || len(devices) == 0 {
|
if err != nil || len(devices) == 0 {
|
||||||
@@ -275,7 +276,11 @@ func (c *CLI) performDiscoveryOnInterface(interfaceName string) ([]*discovery.De
|
|||||||
NetworkInterface: interfaceName,
|
NetworkInterface: interfaceName,
|
||||||
}
|
}
|
||||||
|
|
||||||
return discovery.DiscoverWithOptions(ctx, 5*time.Second, opts)
|
devices, err := discovery.DiscoverWithOptions(ctx, defaultRetryDelay*time.Second, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("discovery failed: %w", err)
|
||||||
|
}
|
||||||
|
return devices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) selectAndConnectCamera(devices []*discovery.Device) {
|
func (c *CLI) selectAndConnectCamera(devices []*discovery.Device) {
|
||||||
@@ -361,7 +366,7 @@ func (c *CLI) createClient(endpoint, username, password string, insecure bool) {
|
|||||||
|
|
||||||
opts := []onvif.ClientOption{
|
opts := []onvif.ClientOption{
|
||||||
onvif.WithCredentials(username, password),
|
onvif.WithCredentials(username, password),
|
||||||
onvif.WithTimeout(30 * time.Second),
|
onvif.WithTimeout(ptzTimeoutSeconds * time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
if insecure {
|
if insecure {
|
||||||
@@ -607,10 +612,13 @@ func (c *CLI) inspectRTSPStream(streamURI string) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use rtspeek library for detailed stream inspection
|
// Use rtspeek library for detailed stream inspection
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(
|
||||||
|
context.Background(),
|
||||||
|
defaultRetryDelay*time.Second, //nolint:mnd // Stream inspection timeout
|
||||||
|
)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
streamInfo, err := sd.DescribeStream(ctx, streamURI, 5*time.Second)
|
streamInfo, err := sd.DescribeStream(ctx, streamURI, defaultRetryDelay*time.Second) //nolint:mnd // Stream description timeout
|
||||||
if err == nil && streamInfo != nil {
|
if err == nil && streamInfo != nil {
|
||||||
details["reachable"] = streamInfo.IsReachable()
|
details["reachable"] = streamInfo.IsReachable()
|
||||||
|
|
||||||
@@ -677,7 +685,7 @@ func (c *CLI) tryRTSPConnection(streamURI string) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to connect
|
// Try to connect
|
||||||
conn, err := net.DialTimeout("tcp", hostPort, 3*time.Second)
|
conn, err := net.DialTimeout("tcp", hostPort, maxRetries*time.Second) //nolint:mnd // Connection timeout
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//nolint:errcheck // Close error is not critical for connectivity check
|
//nolint:errcheck // Close error is not critical for connectivity check
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
@@ -1287,7 +1295,7 @@ func (c *CLI) setBrightness(ctx context.Context, videoSourceToken string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentValue := "50.0"
|
currentValue := defaultBrightness
|
||||||
if currentSettings.Brightness != nil {
|
if currentSettings.Brightness != nil {
|
||||||
currentValue = fmt.Sprintf("%.1f", *currentSettings.Brightness)
|
currentValue = fmt.Sprintf("%.1f", *currentSettings.Brightness)
|
||||||
}
|
}
|
||||||
@@ -1322,7 +1330,7 @@ func (c *CLI) setContrast(ctx context.Context, videoSourceToken string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentValue := "50.0"
|
currentValue := defaultBrightness
|
||||||
if currentSettings.Contrast != nil {
|
if currentSettings.Contrast != nil {
|
||||||
currentValue = fmt.Sprintf("%.1f", *currentSettings.Contrast)
|
currentValue = fmt.Sprintf("%.1f", *currentSettings.Contrast)
|
||||||
}
|
}
|
||||||
@@ -1357,7 +1365,7 @@ func (c *CLI) setSaturation(ctx context.Context, videoSourceToken string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentValue := "50.0"
|
currentValue := defaultBrightness
|
||||||
if currentSettings.ColorSaturation != nil {
|
if currentSettings.ColorSaturation != nil {
|
||||||
currentValue = fmt.Sprintf("%.1f", *currentSettings.ColorSaturation)
|
currentValue = fmt.Sprintf("%.1f", *currentSettings.ColorSaturation)
|
||||||
}
|
}
|
||||||
@@ -1392,7 +1400,7 @@ func (c *CLI) setSharpness(ctx context.Context, videoSourceToken string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentValue := "50.0"
|
currentValue := defaultBrightness
|
||||||
if currentSettings.Sharpness != nil {
|
if currentSettings.Sharpness != nil {
|
||||||
currentValue = fmt.Sprintf("%.1f", *currentSettings.Sharpness)
|
currentValue = fmt.Sprintf("%.1f", *currentSettings.Sharpness)
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1490,7 @@ func (c *CLI) advancedImagingSettings(ctx context.Context, videoSourceToken stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo // Snapshot capture and display has high complexity due to multiple error handling paths
|
//nolint:gocyclo // Snapshot capture and display has high complexity due to multiple error handling paths
|
||||||
func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
|
func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) { //nolint:funlen // Many statements due to error handling
|
||||||
fmt.Println("📷 Capture Snapshot as ASCII Preview")
|
fmt.Println("📷 Capture Snapshot as ASCII Preview")
|
||||||
fmt.Println("===================================")
|
fmt.Println("===================================")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -1543,7 +1551,7 @@ func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
|
|||||||
case "2":
|
case "2":
|
||||||
config.Width = 100
|
config.Width = 100
|
||||||
config.Height = 30
|
config.Height = 30
|
||||||
config.Quality = "medium"
|
config.Quality = defaultQuality
|
||||||
case "3":
|
case "3":
|
||||||
config.Width = 140
|
config.Width = 140
|
||||||
config.Height = 40
|
config.Height = 40
|
||||||
@@ -1555,7 +1563,7 @@ func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
|
|||||||
default:
|
default:
|
||||||
config.Width = 100
|
config.Width = 100
|
||||||
config.Height = 30
|
config.Height = 30
|
||||||
config.Quality = "medium"
|
config.Quality = defaultQuality
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download actual snapshot
|
// Download actual snapshot
|
||||||
@@ -1606,7 +1614,9 @@ func (c *CLI) captureAndDisplaySnapshot(ctx context.Context) {
|
|||||||
if filename == "" {
|
if filename == "" {
|
||||||
filename = "snapshot.jpg"
|
filename = "snapshot.jpg"
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filename, snapshotData, 0600); err != nil { //nolint:gosec // 0600 is appropriate for CLI output files
|
if err := os.WriteFile(
|
||||||
|
filename, snapshotData, 0600, //nolint:gosec,mnd // 0600 appropriate for CLI output files
|
||||||
|
); err != nil {
|
||||||
fmt.Printf("❌ Failed to save file: %v\n", err)
|
fmt.Printf("❌ Failed to save file: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("✅ Snapshot saved to %s\n", filename)
|
fmt.Printf("✅ Snapshot saved to %s\n", filename)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const (
|
|||||||
maxRetryAttempts = 10
|
maxRetryAttempts = 10
|
||||||
retryDelaySec = 5
|
retryDelaySec = 5
|
||||||
maxIdleTimeoutSec = 90
|
maxIdleTimeoutSec = 90
|
||||||
|
unknownStatus = "Unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CameraReport struct {
|
type CameraReport struct {
|
||||||
@@ -146,7 +147,7 @@ var (
|
|||||||
username = flag.String("username", "", "ONVIF username")
|
username = flag.String("username", "", "ONVIF username")
|
||||||
password = flag.String("password", "", "ONVIF password")
|
password = flag.String("password", "", "ONVIF password")
|
||||||
outputDir = flag.String("output", "./camera-logs", "Output directory for logs")
|
outputDir = flag.String("output", "./camera-logs", "Output directory for logs")
|
||||||
timeout = flag.Int("timeout", 30, "Request timeout in seconds")
|
timeout = flag.Int("timeout", 30, "Request timeout in seconds") //nolint:mnd // Default timeout value
|
||||||
verbose = flag.Bool("verbose", false, "Verbose output")
|
verbose = flag.Bool("verbose", false, "Verbose output")
|
||||||
captureXML = flag.Bool("capture-xml", false, "Capture raw SOAP XML traffic and create tar.gz archive")
|
captureXML = flag.Bool("capture-xml", false, "Capture raw SOAP XML traffic and create tar.gz archive")
|
||||||
)
|
)
|
||||||
@@ -174,7 +175,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create output directory
|
// Create output directory
|
||||||
if err := os.MkdirAll(*outputDir, 0750); err != nil { //nolint:gosec // 0750 is appropriate for diagnostic output directory
|
if err := os.MkdirAll(*outputDir, 0750); err != nil { //nolint:gosec,mnd // 0750 appropriate for diagnostic output
|
||||||
log.Fatalf("Failed to create output directory: %v", err)
|
log.Fatalf("Failed to create output directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +199,7 @@ func main() {
|
|||||||
if *captureXML {
|
if *captureXML {
|
||||||
timestamp := time.Now().Format("20060102-150405")
|
timestamp := time.Now().Format("20060102-150405")
|
||||||
xmlCaptureDir = filepath.Join(*outputDir, "temp_"+timestamp)
|
xmlCaptureDir = filepath.Join(*outputDir, "temp_"+timestamp)
|
||||||
if err := os.MkdirAll(xmlCaptureDir, 0750); err != nil { //nolint:gosec // 0750 appropriate for diagnostic output
|
if err := os.MkdirAll(xmlCaptureDir, 0750); err != nil { //nolint:gosec,mnd // 0750 appropriate for diagnostic output
|
||||||
log.Fatalf("Failed to create XML capture directory: %v", err)
|
log.Fatalf("Failed to create XML capture directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,8 +884,7 @@ func saveReport(report *CameraReport, filename string) error {
|
|||||||
return fmt.Errorf("failed to marshal report: %w", err)
|
return fmt.Errorf("failed to marshal report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filename, data, 0600); err != nil {
|
if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec,mnd // 0600 appropriate for diagnostic files
|
||||||
//nolint:gosec // 0600 appropriate for diagnostic files
|
|
||||||
return fmt.Errorf("failed to write file: %w", err)
|
return fmt.Errorf("failed to write file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1024,22 +1024,24 @@ func (t *LoggingTransport) saveCapture(capture *XMLCapture) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filename, data, 0600); err != nil {
|
if err := os.WriteFile(filename, data, 0600); err != nil { //nolint:gosec,mnd // 0600 appropriate for diagnostic files
|
||||||
//nolint:gosec // 0600 appropriate for diagnostic files
|
|
||||||
log.Printf("Failed to write capture: %v", err)
|
log.Printf("Failed to write capture: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty-print and save XML files for easier viewing
|
// Pretty-print and save XML files for easier viewing
|
||||||
reqFile := filepath.Join(t.LogDir, baseFilename+"_request.xml")
|
reqFile := filepath.Join(t.LogDir, baseFilename+"_request.xml")
|
||||||
prettyRequest := prettyPrintXML(capture.RequestBody)
|
prettyRequest := prettyPrintXML(capture.RequestBody)
|
||||||
if err := os.WriteFile(reqFile, []byte(prettyRequest), 0600); err != nil {
|
if err := os.WriteFile(
|
||||||
//nolint:gosec // 0600 appropriate for diagnostic files
|
reqFile, []byte(prettyRequest), 0600, //nolint:gosec,mnd // 0600 appropriate for diagnostic files
|
||||||
|
); err != nil {
|
||||||
log.Printf("Failed to write request XML: %v", err)
|
log.Printf("Failed to write request XML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
respFile := filepath.Join(t.LogDir, baseFilename+"_response.xml")
|
respFile := filepath.Join(t.LogDir, baseFilename+"_response.xml")
|
||||||
prettyResponse := prettyPrintXML(capture.ResponseBody)
|
prettyResponse := prettyPrintXML(capture.ResponseBody)
|
||||||
if err := os.WriteFile(respFile, []byte(prettyResponse), 0600); err != nil { //nolint:gosec // 0600 appropriate for diagnostic files
|
if err := os.WriteFile(
|
||||||
|
respFile, []byte(prettyResponse), 0600, //nolint:gosec,mnd // 0600 appropriate for diagnostic files
|
||||||
|
); err != nil {
|
||||||
log.Printf("Failed to write response XML: %v", err)
|
log.Printf("Failed to write response XML: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func discoverCameras() {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts)
|
devices, err := discovery.DiscoverWithOptions(ctx, defaultRetryDelay*time.Second, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Error: %v\n", err)
|
fmt.Printf("❌ Error: %v\n", err)
|
||||||
|
|
||||||
@@ -233,8 +233,7 @@ func connectAndShowInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo // PTZ demo function has high complexity due to multiple control paths
|
func ptzDemo() { //nolint:funlen,gocyclo // Many statements and high complexity due to user interaction
|
||||||
func ptzDemo() {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
fmt.Print("Camera IP: ")
|
fmt.Print("Camera IP: ")
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ var (
|
|||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen // Main function has many statements due to server setup and configuration
|
|
||||||
const (
|
const (
|
||||||
defaultPort = 8080
|
defaultPort = 8080
|
||||||
maxWorkers = 3
|
maxWorkers = 3
|
||||||
@@ -28,6 +27,7 @@ const (
|
|||||||
ptzSpeed = 0.5
|
ptzSpeed = 0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:funlen // Main function has many statements due to server setup and configuration
|
||||||
func main() {
|
func main() {
|
||||||
// Define command-line flags
|
// Define command-line flags
|
||||||
host := flag.String("host", "0.0.0.0", "Server host address")
|
host := flag.String("host", "0.0.0.0", "Server host address")
|
||||||
@@ -38,7 +38,7 @@ func main() {
|
|||||||
model := flag.String("model", "Virtual Multi-Lens Camera", "Device model")
|
model := flag.String("model", "Virtual Multi-Lens Camera", "Device model")
|
||||||
firmware := flag.String("firmware", "1.0.0", "Firmware version")
|
firmware := flag.String("firmware", "1.0.0", "Firmware version")
|
||||||
serial := flag.String("serial", "SN-12345678", "Serial number")
|
serial := flag.String("serial", "SN-12345678", "Serial number")
|
||||||
profiles := flag.Int("profiles", 3, "Number of camera profiles (1-10)")
|
profiles := flag.Int("profiles", maxWorkers, "Number of camera profiles (1-10)") //nolint:mnd // Default profile count
|
||||||
ptz := flag.Bool("ptz", true, "Enable PTZ support")
|
ptz := flag.Bool("ptz", true, "Enable PTZ support")
|
||||||
imaging := flag.Bool("imaging", true, "Enable Imaging support")
|
imaging := flag.Bool("imaging", true, "Enable Imaging support")
|
||||||
events := flag.Bool("events", false, "Enable Events support")
|
events := flag.Bool("events", false, "Enable Events support")
|
||||||
@@ -190,7 +190,7 @@ func buildConfig(host string, port int, username, password, manufacturer, model,
|
|||||||
Snapshot: server.SnapshotConfig{
|
Snapshot: server.SnapshotConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Resolution: server.Resolution{Width: template.width, Height: template.height},
|
Resolution: server.Resolution{Width: template.width, Height: template.height},
|
||||||
Quality: template.quality + 5,
|
Quality: template.quality + 5, //nolint:mnd // Quality offset
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,9 +212,11 @@ func buildConfig(host string, port int, username, password, manufacturer, model,
|
|||||||
Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0},
|
Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Token: fmt.Sprintf("preset_%d_1", i),
|
Token: fmt.Sprintf("preset_%d_1", i),
|
||||||
Name: "Entrance",
|
Name: "Entrance",
|
||||||
Position: server.PTZPosition{Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * ptzSpeed}, //nolint:mnd // Preset position values
|
Position: server.PTZPosition{
|
||||||
|
Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * ptzSpeed, //nolint:mnd // Preset position values
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testCertID = "cert-001"
|
||||||
|
testXMLHeader = `<?xml version="1.0" encoding="UTF-8"?>`
|
||||||
|
)
|
||||||
|
|
||||||
func newMockDeviceCertificatesServer() *httptest.Server {
|
func newMockDeviceCertificatesServer() *httptest.Server {
|
||||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/soap+xml")
|
w.Header().Set("Content-Type", "application/soap+xml")
|
||||||
@@ -167,7 +172,7 @@ func newMockDeviceCertificatesServer() *httptest.Server {
|
|||||||
</SOAP-ENV:Envelope>`
|
</SOAP-ENV:Envelope>`
|
||||||
|
|
||||||
default:
|
default:
|
||||||
response = `<?xml version="1.0" encoding="UTF-8"?>
|
response = testXMLHeader + `
|
||||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
|
||||||
<SOAP-ENV:Body>
|
<SOAP-ENV:Body>
|
||||||
<SOAP-ENV:Fault>
|
<SOAP-ENV:Fault>
|
||||||
@@ -201,8 +206,8 @@ func TestGetCertificates(t *testing.T) {
|
|||||||
t.Error("Expected at least one certificate")
|
t.Error("Expected at least one certificate")
|
||||||
}
|
}
|
||||||
|
|
||||||
if certs[0].CertificateID != "cert-001" {
|
if certs[0].CertificateID != testCertID {
|
||||||
t.Errorf("Expected certificate ID 'cert-001', got '%s'", certs[0].CertificateID)
|
t.Errorf("Expected certificate ID '%s', got '%s'", testCertID, certs[0].CertificateID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ func Discover(ctx context.Context, timeout time.Duration) ([]*Device, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DiscoverWithOptions discovers ONVIF devices with custom options.
|
// DiscoverWithOptions discovers ONVIF devices with custom options.
|
||||||
|
//
|
||||||
|
//nolint:gocyclo // Discovery function has high complexity due to multiple network operations
|
||||||
func DiscoverWithOptions(ctx context.Context, timeout time.Duration, opts *DiscoverOptions) ([]*Device, error) {
|
func DiscoverWithOptions(ctx context.Context, timeout time.Duration, opts *DiscoverOptions) ([]*Device, error) {
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &DiscoverOptions{}
|
opts = &DiscoverOptions{}
|
||||||
@@ -236,7 +238,7 @@ func generateUUID() string {
|
|||||||
|
|
||||||
// resolveNetworkInterface resolves a network interface by name or IP address.
|
// resolveNetworkInterface resolves a network interface by name or IP address.
|
||||||
//
|
//
|
||||||
//nolint:gocognit // Network interface resolution has high complexity due to multiple validation paths
|
//nolint:gocyclo,gocognit // Network interface resolution has high complexity due to multiple validation paths
|
||||||
func resolveNetworkInterface(ifaceSpec string) (*net.Interface, error) {
|
func resolveNetworkInterface(ifaceSpec string) (*net.Interface, error) {
|
||||||
// Try to get interface by name (e.g., "eth0", "wlan0")
|
// Try to get interface by name (e.g., "eth0", "wlan0")
|
||||||
if iface, err := net.InterfaceByName(ifaceSpec); err == nil {
|
if iface, err := net.InterfaceByName(ifaceSpec); err == nil {
|
||||||
|
|||||||
+5
-1
@@ -142,7 +142,11 @@ func (c *Client) GetImagingSettings(ctx context.Context, videoSourceToken string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetImagingSettings sets imaging settings for a video source.
|
// SetImagingSettings sets imaging settings for a video source.
|
||||||
func (c *Client) SetImagingSettings(ctx context.Context, videoSourceToken string, settings *ImagingSettings, forcePersistence bool) error {
|
//
|
||||||
|
//nolint:funlen // SetImagingSettings has many statements due to building complex imaging settings request
|
||||||
|
func (c *Client) SetImagingSettings(
|
||||||
|
ctx context.Context, videoSourceToken string, settings *ImagingSettings, forcePersistence bool,
|
||||||
|
) error {
|
||||||
endpoint := c.imagingEndpoint
|
endpoint := c.imagingEndpoint
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
endpoint = c.endpoint
|
endpoint = c.endpoint
|
||||||
|
|||||||
@@ -2491,6 +2491,8 @@ func (c *Client) GetAudioSourceConfigurations(ctx context.Context) ([]*AudioSour
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVideoEncoderConfigurations retrieves all video encoder configurations.
|
// GetVideoEncoderConfigurations retrieves all video encoder configurations.
|
||||||
|
//
|
||||||
|
//nolint:funlen // GetVideoEncoderConfigurations has many statements due to parsing complex encoder configurations
|
||||||
func (c *Client) GetVideoEncoderConfigurations(ctx context.Context) ([]*VideoEncoderConfiguration, error) {
|
func (c *Client) GetVideoEncoderConfigurations(ctx context.Context) ([]*VideoEncoderConfiguration, error) {
|
||||||
endpoint := c.mediaEndpoint
|
endpoint := c.mediaEndpoint
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
encodingH264 = "H264"
|
||||||
|
)
|
||||||
|
|
||||||
// Test device information from real camera:
|
// Test device information from real camera:
|
||||||
// Manufacturer: Bosch
|
// Manufacturer: Bosch
|
||||||
// Model: FLEXIDOME indoor 5100i IR
|
// Model: FLEXIDOME indoor 5100i IR
|
||||||
@@ -168,7 +172,7 @@ func TestGetProfiles_Bosch(t *testing.T) {
|
|||||||
if profiles[0].VideoEncoderConfiguration.Token != "EncCfg_L1S1" {
|
if profiles[0].VideoEncoderConfiguration.Token != "EncCfg_L1S1" {
|
||||||
t.Errorf("Expected encoder token=EncCfg_L1S1 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Token)
|
t.Errorf("Expected encoder token=EncCfg_L1S1 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Token)
|
||||||
}
|
}
|
||||||
if profiles[0].VideoEncoderConfiguration.Encoding != "H264" {
|
if profiles[0].VideoEncoderConfiguration.Encoding != encodingH264 {
|
||||||
t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Encoding)
|
t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Encoding)
|
||||||
}
|
}
|
||||||
if profiles[0].VideoEncoderConfiguration.Resolution.Width != 1920 {
|
if profiles[0].VideoEncoderConfiguration.Resolution.Width != 1920 {
|
||||||
@@ -533,7 +537,7 @@ func TestGetVideoEncoderConfiguration_Bosch(t *testing.T) {
|
|||||||
if config.Name != "Balanced 2 MP" {
|
if config.Name != "Balanced 2 MP" {
|
||||||
t.Errorf("Expected name=Balanced 2 MP (Bosch FLEXIDOME), got %s", config.Name)
|
t.Errorf("Expected name=Balanced 2 MP (Bosch FLEXIDOME), got %s", config.Name)
|
||||||
}
|
}
|
||||||
if config.Encoding != "H264" {
|
if config.Encoding != encodingH264 {
|
||||||
t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", config.Encoding)
|
t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", config.Encoding)
|
||||||
}
|
}
|
||||||
if config.Resolution.Width != 1920 {
|
if config.Resolution.Width != 1920 {
|
||||||
|
|||||||
+9
-4
@@ -8,6 +8,11 @@ import (
|
|||||||
"github.com/0x524a/onvif-go/server/soap"
|
"github.com/0x524a/onvif-go/server/soap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHost = "0.0.0.0"
|
||||||
|
defaultHostname = "localhost"
|
||||||
|
)
|
||||||
|
|
||||||
// Device service SOAP message types
|
// Device service SOAP message types
|
||||||
|
|
||||||
// GetDeviceInformationResponse represents GetDeviceInformation response.
|
// GetDeviceInformationResponse represents GetDeviceInformation response.
|
||||||
@@ -162,8 +167,8 @@ func (s *Server) HandleGetCapabilities(body interface{}) (interface{}, error) {
|
|||||||
// Get the host from the request (in a real implementation)
|
// Get the host from the request (in a real implementation)
|
||||||
// For now, use a placeholder
|
// For now, use a placeholder
|
||||||
host := s.config.Host
|
host := s.config.Host
|
||||||
if host == "0.0.0.0" || host == "" {
|
if host == defaultHost || host == "" {
|
||||||
host = "localhost"
|
host = defaultHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%d%s", host, s.config.Port, s.config.BasePath)
|
baseURL := fmt.Sprintf("http://%s:%d%s", host, s.config.Port, s.config.BasePath)
|
||||||
@@ -256,8 +261,8 @@ func (s *Server) HandleGetSystemDateAndTime(body interface{}) (interface{}, erro
|
|||||||
// HandleGetServices handles GetServices request.
|
// HandleGetServices handles GetServices request.
|
||||||
func (s *Server) HandleGetServices(body interface{}) (interface{}, error) {
|
func (s *Server) HandleGetServices(body interface{}) (interface{}, error) {
|
||||||
host := s.config.Host
|
host := s.config.Host
|
||||||
if host == "0.0.0.0" || host == "" {
|
if host == defaultHost || host == "" {
|
||||||
host = "localhost"
|
host = defaultHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%d%s", host, s.config.Port, s.config.BasePath)
|
baseURL := fmt.Sprintf("http://%s:%d%s", host, s.config.Port, s.config.BasePath)
|
||||||
|
|||||||
+3
-3
@@ -377,12 +377,12 @@ func (s *Server) HandleGetOptions(body interface{}) (interface{}, error) {
|
|||||||
},
|
},
|
||||||
WideDynamicRange: &WideDynamicRangeOptions{
|
WideDynamicRange: &WideDynamicRangeOptions{
|
||||||
Mode: []string{"OFF", "ON"},
|
Mode: []string{"OFF", "ON"},
|
||||||
Level: &FloatRange{Min: 0, Max: 100},
|
Level: &FloatRange{Min: 0, Max: 100}, //nolint:mnd // Imaging parameter range
|
||||||
},
|
},
|
||||||
WhiteBalance: &WhiteBalanceOptions{
|
WhiteBalance: &WhiteBalanceOptions{
|
||||||
Mode: []string{"AUTO", "MANUAL"},
|
Mode: []string{"AUTO", "MANUAL"},
|
||||||
YrGain: &FloatRange{Min: 0, Max: 255},
|
YrGain: &FloatRange{Min: 0, Max: 255}, //nolint:mnd // White balance gain range
|
||||||
YbGain: &FloatRange{Min: 0, Max: 255},
|
YbGain: &FloatRange{Min: 0, Max: 255}, //nolint:mnd // White balance gain range
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-22
@@ -5,8 +5,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exposureModeAuto = "AUTO"
|
||||||
|
exposureModeManual = "MANUAL"
|
||||||
|
)
|
||||||
|
|
||||||
func TestHandleGetImagingSettings(t *testing.T) {
|
func TestHandleGetImagingSettings(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -42,7 +47,7 @@ func TestHandleGetImagingSettings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleSetImagingSettings(t *testing.T) {
|
func TestHandleSetImagingSettings(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -85,7 +90,7 @@ func TestHandleSetImagingSettings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleGetOptions(t *testing.T) {
|
func TestHandleGetOptions(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -122,8 +127,10 @@ func TestHandleGetOptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleMove - DISABLED due to SOAP namespace requirements.
|
// TestHandleMove - DISABLED due to SOAP namespace requirements.
|
||||||
|
//
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleMove(t *testing.T) {
|
func _DisabledTestHandleMove(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -148,7 +155,7 @@ func TestImagingSettings(t *testing.T) {
|
|||||||
contrast := 60.0
|
contrast := 60.0
|
||||||
saturation := 50.0
|
saturation := 50.0
|
||||||
sharpness := 50.0
|
sharpness := 50.0
|
||||||
irCutFilter := "AUTO"
|
irCutFilter := exposureModeAuto
|
||||||
level := 50.0
|
level := 50.0
|
||||||
gain := 50.0
|
gain := 50.0
|
||||||
exposureTime := 100.0
|
exposureTime := 100.0
|
||||||
@@ -167,16 +174,16 @@ func TestImagingSettings(t *testing.T) {
|
|||||||
Level: &level,
|
Level: &level,
|
||||||
},
|
},
|
||||||
Exposure: &ExposureSettings20{
|
Exposure: &ExposureSettings20{
|
||||||
Mode: "AUTO",
|
Mode: exposureModeAuto,
|
||||||
ExposureTime: &exposureTime,
|
ExposureTime: &exposureTime,
|
||||||
Gain: &gain,
|
Gain: &gain,
|
||||||
},
|
},
|
||||||
Focus: &FocusConfiguration20{
|
Focus: &FocusConfiguration20{
|
||||||
AutoFocusMode: "AUTO",
|
AutoFocusMode: exposureModeAuto,
|
||||||
DefaultSpeed: &defaultSpeed,
|
DefaultSpeed: &defaultSpeed,
|
||||||
},
|
},
|
||||||
WhiteBalance: &WhiteBalanceSettings20{
|
WhiteBalance: &WhiteBalanceSettings20{
|
||||||
Mode: "AUTO",
|
Mode: exposureModeAuto,
|
||||||
CrGain: &crGain,
|
CrGain: &crGain,
|
||||||
CbGain: &cbGain,
|
CbGain: &cbGain,
|
||||||
},
|
},
|
||||||
@@ -204,15 +211,15 @@ func TestImagingSettings(t *testing.T) {
|
|||||||
t.Errorf("BacklightCompensation mode invalid: %s", settings.BacklightCompensation.Mode)
|
t.Errorf("BacklightCompensation mode invalid: %s", settings.BacklightCompensation.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.Exposure != nil && settings.Exposure.Mode != "AUTO" {
|
if settings.Exposure != nil && settings.Exposure.Mode != exposureModeAuto {
|
||||||
t.Errorf("Exposure mode invalid: %s", settings.Exposure.Mode)
|
t.Errorf("Exposure mode invalid: %s", settings.Exposure.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.Focus != nil && settings.Focus.AutoFocusMode != "AUTO" {
|
if settings.Focus != nil && settings.Focus.AutoFocusMode != exposureModeAuto {
|
||||||
t.Errorf("Focus mode invalid: %s", settings.Focus.AutoFocusMode)
|
t.Errorf("Focus mode invalid: %s", settings.Focus.AutoFocusMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.WhiteBalance.Mode != "AUTO" {
|
if settings.WhiteBalance.Mode != exposureModeAuto {
|
||||||
t.Errorf("WhiteBalance mode invalid: %s", settings.WhiteBalance.Mode)
|
t.Errorf("WhiteBalance mode invalid: %s", settings.WhiteBalance.Mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +283,7 @@ func TestExposureSettings(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Valid MANUAL exposure",
|
name: "Valid MANUAL exposure",
|
||||||
exposure: ExposureSettings{
|
exposure: ExposureSettings{
|
||||||
Mode: "MANUAL",
|
Mode: exposureModeManual,
|
||||||
ExposureTime: 100,
|
ExposureTime: 100,
|
||||||
Gain: 50,
|
Gain: 50,
|
||||||
},
|
},
|
||||||
@@ -293,7 +300,7 @@ func TestExposureSettings(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
valid := tt.exposure.Mode == "AUTO" || tt.exposure.Mode == "MANUAL"
|
valid := tt.exposure.Mode == exposureModeAuto || tt.exposure.Mode == exposureModeManual
|
||||||
if valid != tt.expectValid {
|
if valid != tt.expectValid {
|
||||||
t.Errorf("Exposure validation failed: Mode=%s", tt.exposure.Mode)
|
t.Errorf("Exposure validation failed: Mode=%s", tt.exposure.Mode)
|
||||||
}
|
}
|
||||||
@@ -310,7 +317,7 @@ func TestFocusSettings(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Valid AUTO focus",
|
name: "Valid AUTO focus",
|
||||||
focus: FocusSettings{
|
focus: FocusSettings{
|
||||||
AutoFocusMode: "AUTO",
|
AutoFocusMode: exposureModeAuto,
|
||||||
DefaultSpeed: 0.5,
|
DefaultSpeed: 0.5,
|
||||||
NearLimit: 0,
|
NearLimit: 0,
|
||||||
FarLimit: 1,
|
FarLimit: 1,
|
||||||
@@ -320,7 +327,7 @@ func TestFocusSettings(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Valid MANUAL focus",
|
name: "Valid MANUAL focus",
|
||||||
focus: FocusSettings{
|
focus: FocusSettings{
|
||||||
AutoFocusMode: "MANUAL",
|
AutoFocusMode: exposureModeManual,
|
||||||
DefaultSpeed: 0.5,
|
DefaultSpeed: 0.5,
|
||||||
CurrentPos: 0.5,
|
CurrentPos: 0.5,
|
||||||
},
|
},
|
||||||
@@ -337,7 +344,7 @@ func TestFocusSettings(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
valid := tt.focus.AutoFocusMode == "AUTO" || tt.focus.AutoFocusMode == "MANUAL"
|
valid := tt.focus.AutoFocusMode == exposureModeAuto || tt.focus.AutoFocusMode == exposureModeManual
|
||||||
if valid != tt.expectValid {
|
if valid != tt.expectValid {
|
||||||
t.Errorf("Focus validation failed: Mode=%s", tt.focus.AutoFocusMode)
|
t.Errorf("Focus validation failed: Mode=%s", tt.focus.AutoFocusMode)
|
||||||
}
|
}
|
||||||
@@ -354,7 +361,7 @@ func TestWhiteBalanceSettings(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Valid AUTO white balance",
|
name: "Valid AUTO white balance",
|
||||||
whiteBalance: WhiteBalanceSettings{
|
whiteBalance: WhiteBalanceSettings{
|
||||||
Mode: "AUTO",
|
Mode: exposureModeAuto,
|
||||||
CrGain: 128,
|
CrGain: 128,
|
||||||
CbGain: 128,
|
CbGain: 128,
|
||||||
},
|
},
|
||||||
@@ -372,7 +379,7 @@ func TestWhiteBalanceSettings(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Gain out of range",
|
name: "Gain out of range",
|
||||||
whiteBalance: WhiteBalanceSettings{
|
whiteBalance: WhiteBalanceSettings{
|
||||||
Mode: "AUTO",
|
Mode: exposureModeAuto,
|
||||||
CrGain: 300,
|
CrGain: 300,
|
||||||
CbGain: 128,
|
CbGain: 128,
|
||||||
},
|
},
|
||||||
@@ -382,7 +389,7 @@ func TestWhiteBalanceSettings(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
valid := (tt.whiteBalance.Mode == "AUTO" || tt.whiteBalance.Mode == "MANUAL") &&
|
valid := (tt.whiteBalance.Mode == exposureModeAuto || tt.whiteBalance.Mode == exposureModeManual) &&
|
||||||
tt.whiteBalance.CrGain >= 0 && tt.whiteBalance.CrGain <= 255 &&
|
tt.whiteBalance.CrGain >= 0 && tt.whiteBalance.CrGain <= 255 &&
|
||||||
tt.whiteBalance.CbGain >= 0 && tt.whiteBalance.CbGain <= 255
|
tt.whiteBalance.CbGain >= 0 && tt.whiteBalance.CbGain <= 255
|
||||||
if valid != tt.expectValid {
|
if valid != tt.expectValid {
|
||||||
@@ -463,7 +470,7 @@ func TestGetImagingSettingsResponseXML(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleGetOptionsDetails(t *testing.T) {
|
func TestHandleGetOptionsDetails(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -499,7 +506,7 @@ func TestImagingSettingsEdgeCases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetImagingSettingsEdgeCases(t *testing.T) {
|
func TestSetImagingSettingsEdgeCases(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||||
|
|
||||||
@@ -518,7 +525,7 @@ func TestSetImagingSettingsEdgeCases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetImagingSettingsEdgeCases(t *testing.T) {
|
func TestGetImagingSettingsEdgeCases(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig(t)
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
|
|
||||||
// Test with invalid token
|
// Test with invalid token
|
||||||
|
|||||||
+4
-4
@@ -280,8 +280,8 @@ func (s *Server) HandleGetStreamURI(body interface{}) (interface{}, error) {
|
|||||||
if uri == "" {
|
if uri == "" {
|
||||||
// Default URI construction
|
// Default URI construction
|
||||||
host := s.config.Host
|
host := s.config.Host
|
||||||
if host == "0.0.0.0" || host == "" {
|
if host == defaultHost || host == "" {
|
||||||
host = "localhost"
|
host = defaultHostname
|
||||||
}
|
}
|
||||||
uri = fmt.Sprintf("rtsp://%s:8554%s", host, streamCfg.RTSPPath)
|
uri = fmt.Sprintf("rtsp://%s:8554%s", host, streamCfg.RTSPPath)
|
||||||
}
|
}
|
||||||
@@ -326,8 +326,8 @@ func (s *Server) HandleGetSnapshotURI(body interface{}) (interface{}, error) {
|
|||||||
|
|
||||||
// Build snapshot URI
|
// Build snapshot URI
|
||||||
host := s.config.Host
|
host := s.config.Host
|
||||||
if host == "0.0.0.0" || host == "" {
|
if host == defaultHost || host == "" {
|
||||||
host = "localhost"
|
host = defaultHostname
|
||||||
}
|
}
|
||||||
uri := fmt.Sprintf("http://%s:%d%s/snapshot?profile=%s",
|
uri := fmt.Sprintf("http://%s:%d%s/snapshot?profile=%s",
|
||||||
host, s.config.Port, s.config.BasePath, req.ProfileToken)
|
host, s.config.Port, s.config.BasePath, req.ProfileToken)
|
||||||
|
|||||||
+2
-2
@@ -306,8 +306,8 @@ func (s *Server) HandleRelativeMove(body interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clamp values to valid ranges (simplified)
|
// Clamp values to valid ranges (simplified)
|
||||||
const maxPan = 180 //nolint:mnd // PTZ pan range
|
const maxPan = 180 //nolint:mnd // PTZ pan range
|
||||||
const maxTilt = 90 //nolint:mnd // PTZ tilt range
|
const maxTilt = 90 //nolint:mnd // PTZ tilt range
|
||||||
state.Position.Pan = clamp(state.Position.Pan, -maxPan, maxPan)
|
state.Position.Pan = clamp(state.Position.Pan, -maxPan, maxPan)
|
||||||
state.Position.Tilt = clamp(state.Position.Tilt, -maxTilt, maxTilt)
|
state.Position.Tilt = clamp(state.Position.Tilt, -maxTilt, maxTilt)
|
||||||
state.Position.Zoom = clamp(state.Position.Zoom, 0, 1)
|
state.Position.Zoom = clamp(state.Position.Zoom, 0, 1)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// These handlers are better tested through the SOAP handler in integration tests.
|
// These handlers are better tested through the SOAP handler in integration tests.
|
||||||
|
//
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleGetPresets(t *testing.T) {
|
func _DisabledTestHandleGetPresets(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
@@ -75,6 +77,8 @@ func TestHandleGotoPreset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleGetStatus - DISABLED due to SOAP namespace requirements.
|
// TestHandleGetStatus - DISABLED due to SOAP namespace requirements.
|
||||||
|
//
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleGetStatus(t *testing.T) {
|
func _DisabledTestHandleGetStatus(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
@@ -112,6 +116,7 @@ func _DisabledTestHandleGetStatus(t *testing.T) {
|
|||||||
// TestHandleAbsoluteMove - DISABLED due to SOAP namespace requirements
|
// TestHandleAbsoluteMove - DISABLED due to SOAP namespace requirements
|
||||||
//
|
//
|
||||||
//nolint:dupl // Disabled test functions have similar structure
|
//nolint:dupl // Disabled test functions have similar structure
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleAbsoluteMove(t *testing.T) {
|
func _DisabledTestHandleAbsoluteMove(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
@@ -154,6 +159,7 @@ func _DisabledTestHandleAbsoluteMove(t *testing.T) {
|
|||||||
// TestHandleRelativeMove - DISABLED due to SOAP namespace requirements
|
// TestHandleRelativeMove - DISABLED due to SOAP namespace requirements
|
||||||
//
|
//
|
||||||
//nolint:dupl // Disabled test functions have similar structure
|
//nolint:dupl // Disabled test functions have similar structure
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleRelativeMove(t *testing.T) {
|
func _DisabledTestHandleRelativeMove(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
@@ -196,6 +202,7 @@ func _DisabledTestHandleRelativeMove(t *testing.T) {
|
|||||||
// TestHandleContinuousMove - DISABLED due to SOAP namespace requirements
|
// TestHandleContinuousMove - DISABLED due to SOAP namespace requirements
|
||||||
//
|
//
|
||||||
//nolint:dupl // Disabled test functions have similar structure
|
//nolint:dupl // Disabled test functions have similar structure
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleContinuousMove(t *testing.T) {
|
func _DisabledTestHandleContinuousMove(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
@@ -236,6 +243,8 @@ func _DisabledTestHandleContinuousMove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleStop - DISABLED due to SOAP namespace requirements.
|
// TestHandleStop - DISABLED due to SOAP namespace requirements.
|
||||||
|
//
|
||||||
|
//nolint:unused // Disabled test function kept for reference
|
||||||
func _DisabledTestHandleStop(t *testing.T) {
|
func _DisabledTestHandleStop(t *testing.T) {
|
||||||
config := createTestConfig()
|
config := createTestConfig()
|
||||||
server, _ := New(config)
|
server, _ := New(config)
|
||||||
|
|||||||
+12
-12
@@ -57,10 +57,10 @@ func New(config *Config) (*Server, error) {
|
|||||||
|
|
||||||
// Initialize imaging state
|
// Initialize imaging state
|
||||||
server.imagingState[profile.VideoSource.Token] = &ImagingState{
|
server.imagingState[profile.VideoSource.Token] = &ImagingState{
|
||||||
Brightness: 50.0,
|
Brightness: 50.0, //nolint:mnd // Default imaging value
|
||||||
Contrast: 50.0,
|
Contrast: 50.0, //nolint:mnd // Default imaging value
|
||||||
Saturation: 50.0,
|
Saturation: 50.0, //nolint:mnd // Default imaging value
|
||||||
Sharpness: 50.0,
|
Sharpness: 50.0, //nolint:mnd // Default imaging value
|
||||||
IrCutFilter: "AUTO",
|
IrCutFilter: "AUTO",
|
||||||
BacklightComp: BacklightCompensation{
|
BacklightComp: BacklightCompensation{
|
||||||
Mode: "OFF",
|
Mode: "OFF",
|
||||||
@@ -70,23 +70,23 @@ func New(config *Config) (*Server, error) {
|
|||||||
Mode: "AUTO",
|
Mode: "AUTO",
|
||||||
Priority: "FrameRate",
|
Priority: "FrameRate",
|
||||||
MinExposure: 1,
|
MinExposure: 1,
|
||||||
MaxExposure: 10000,
|
MaxExposure: 10000, //nolint:mnd // Exposure time in microseconds
|
||||||
MinGain: 0,
|
MinGain: 0,
|
||||||
MaxGain: 100,
|
MaxGain: 100, //nolint:mnd // Gain value
|
||||||
ExposureTime: 100,
|
ExposureTime: 100, //nolint:mnd // Exposure time
|
||||||
Gain: 50,
|
Gain: 50, //nolint:mnd // Gain value
|
||||||
},
|
},
|
||||||
Focus: FocusSettings{
|
Focus: FocusSettings{
|
||||||
AutoFocusMode: "AUTO",
|
AutoFocusMode: "AUTO",
|
||||||
DefaultSpeed: 0.5,
|
DefaultSpeed: 0.5, //nolint:mnd // Focus speed
|
||||||
NearLimit: 0,
|
NearLimit: 0,
|
||||||
FarLimit: 1,
|
FarLimit: 1,
|
||||||
CurrentPos: 0.5,
|
CurrentPos: 0.5, //nolint:mnd // Focus position
|
||||||
},
|
},
|
||||||
WhiteBalance: WhiteBalanceSettings{
|
WhiteBalance: WhiteBalanceSettings{
|
||||||
Mode: "AUTO",
|
Mode: "AUTO",
|
||||||
CrGain: 128,
|
CrGain: 128, //nolint:mnd // White balance gain
|
||||||
CbGain: 128,
|
CbGain: 128, //nolint:mnd // White balance gain
|
||||||
},
|
},
|
||||||
WideDynamicRange: WDRSettings{
|
WideDynamicRange: WDRSettings{
|
||||||
Mode: "OFF",
|
Mode: "OFF",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func TestNewHandler(t *testing.T) {
|
func TestNewHandler(t *testing.T) {
|
||||||
handler := NewHandler("admin", "password")
|
handler := NewHandler("admin", "password")
|
||||||
|
|
||||||
|
|||||||
+72
-43
@@ -7,6 +7,28 @@ import (
|
|||||||
"github.com/0x524a/onvif-go"
|
"github.com/0x524a/onvif-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTimeoutSec = 30
|
||||||
|
defaultWidth = 1920
|
||||||
|
defaultHeight = 1080
|
||||||
|
defaultFramerate = 30
|
||||||
|
defaultQuality = 80
|
||||||
|
defaultBitrate = 4096
|
||||||
|
maxPan = 180
|
||||||
|
maxTilt = 90
|
||||||
|
defaultPTZSpeed = 0.5
|
||||||
|
mediumWidth = 1280
|
||||||
|
mediumHeight = 720
|
||||||
|
mediumQuality = 75
|
||||||
|
highQuality = 85
|
||||||
|
mediumBitrate = 2048
|
||||||
|
lowFramerate = 25
|
||||||
|
highBitrate = 6144
|
||||||
|
maxZoom = 3
|
||||||
|
lowPTZSpeed = 0.3
|
||||||
|
presetZoom = 2
|
||||||
|
)
|
||||||
|
|
||||||
// Config represents the ONVIF server configuration.
|
// Config represents the ONVIF server configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Server settings
|
// Server settings
|
||||||
@@ -233,9 +255,9 @@ type WDRSettings struct {
|
|||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Host: "0.0.0.0",
|
Host: "0.0.0.0",
|
||||||
Port: 8080,
|
Port: 8080, //nolint:mnd // Default HTTP port
|
||||||
BasePath: "/onvif",
|
BasePath: "/onvif",
|
||||||
Timeout: 30 * time.Second,
|
Timeout: defaultTimeoutSec * time.Second, //nolint:mnd // Default timeout
|
||||||
DeviceInfo: DeviceInfo{
|
DeviceInfo: DeviceInfo{
|
||||||
Manufacturer: "onvif-go",
|
Manufacturer: "onvif-go",
|
||||||
Model: "Virtual Multi-Lens Camera",
|
Model: "Virtual Multi-Lens Camera",
|
||||||
@@ -255,36 +277,41 @@ func DefaultConfig() *Config {
|
|||||||
VideoSource: VideoSourceConfig{
|
VideoSource: VideoSourceConfig{
|
||||||
Token: "video_source_0",
|
Token: "video_source_0",
|
||||||
Name: "Main Camera",
|
Name: "Main Camera",
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Framerate: 30,
|
Framerate: defaultFramerate, //nolint:mnd // Default framerate
|
||||||
Bounds: Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
|
Bounds: Bounds{X: 0, Y: 0, Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default bounds
|
||||||
},
|
},
|
||||||
VideoEncoder: VideoEncoderConfig{
|
VideoEncoder: VideoEncoderConfig{
|
||||||
Encoding: "H264",
|
Encoding: "H264",
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Quality: 80,
|
Quality: defaultQuality, //nolint:mnd // Default quality
|
||||||
Framerate: 30,
|
Framerate: defaultFramerate, //nolint:mnd // Default framerate
|
||||||
Bitrate: 4096,
|
Bitrate: defaultBitrate, //nolint:mnd // Default bitrate
|
||||||
GovLength: 30,
|
GovLength: defaultFramerate, //nolint:mnd // Default gov length
|
||||||
},
|
},
|
||||||
PTZ: &PTZConfig{
|
PTZ: &PTZConfig{
|
||||||
NodeToken: "ptz_node_0",
|
NodeToken: "ptz_node_0",
|
||||||
PanRange: Range{Min: -180, Max: 180},
|
PanRange: Range{Min: -maxPan, Max: maxPan}, //nolint:mnd // PTZ pan range
|
||||||
TiltRange: Range{Min: -90, Max: 90},
|
TiltRange: Range{Min: -maxTilt, Max: maxTilt}, //nolint:mnd // PTZ tilt range
|
||||||
ZoomRange: Range{Min: 0, Max: 1},
|
ZoomRange: Range{Min: 0, Max: 1},
|
||||||
DefaultSpeed: PTZSpeed{Pan: 0.5, Tilt: 0.5, Zoom: 0.5},
|
DefaultSpeed: PTZSpeed{
|
||||||
|
Pan: defaultPTZSpeed, Tilt: defaultPTZSpeed, Zoom: defaultPTZSpeed, //nolint:mnd // Default PTZ speed
|
||||||
|
},
|
||||||
SupportsContinuous: true,
|
SupportsContinuous: true,
|
||||||
SupportsAbsolute: true,
|
SupportsAbsolute: true,
|
||||||
SupportsRelative: true,
|
SupportsRelative: true,
|
||||||
Presets: []Preset{
|
Presets: []Preset{
|
||||||
{Token: "preset_0", Name: "Home", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
{Token: "preset_0", Name: "Home", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
||||||
{Token: "preset_1", Name: "Entrance", Position: PTZPosition{Pan: -45, Tilt: -10, Zoom: 0.5}},
|
{
|
||||||
|
Token: "preset_1", Name: "Entrance",
|
||||||
|
Position: PTZPosition{Pan: -45, Tilt: -10, Zoom: defaultPTZSpeed}, //nolint:mnd // Preset position
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Snapshot: SnapshotConfig{
|
Snapshot: SnapshotConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Quality: 85,
|
Quality: highQuality, //nolint:mnd // High quality
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -293,22 +320,22 @@ func DefaultConfig() *Config {
|
|||||||
VideoSource: VideoSourceConfig{
|
VideoSource: VideoSourceConfig{
|
||||||
Token: "video_source_1",
|
Token: "video_source_1",
|
||||||
Name: "Wide Angle Camera",
|
Name: "Wide Angle Camera",
|
||||||
Resolution: Resolution{Width: 1280, Height: 720},
|
Resolution: Resolution{Width: mediumWidth, Height: mediumHeight}, //nolint:mnd // Medium resolution
|
||||||
Framerate: 30,
|
Framerate: defaultFramerate, //nolint:mnd // Default framerate
|
||||||
Bounds: Bounds{X: 0, Y: 0, Width: 1280, Height: 720},
|
Bounds: Bounds{X: 0, Y: 0, Width: mediumWidth, Height: mediumHeight}, //nolint:mnd // Medium bounds
|
||||||
},
|
},
|
||||||
VideoEncoder: VideoEncoderConfig{
|
VideoEncoder: VideoEncoderConfig{
|
||||||
Encoding: "H264",
|
Encoding: "H264",
|
||||||
Resolution: Resolution{Width: 1280, Height: 720},
|
Resolution: Resolution{Width: mediumWidth, Height: mediumHeight}, //nolint:mnd // Medium resolution
|
||||||
Quality: 75,
|
Quality: mediumQuality, //nolint:mnd // Medium quality
|
||||||
Framerate: 30,
|
Framerate: defaultFramerate, //nolint:mnd // Default framerate
|
||||||
Bitrate: 2048,
|
Bitrate: mediumBitrate, //nolint:mnd // Medium bitrate
|
||||||
GovLength: 30,
|
GovLength: defaultFramerate, //nolint:mnd // Default gov length
|
||||||
},
|
},
|
||||||
Snapshot: SnapshotConfig{
|
Snapshot: SnapshotConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Resolution: Resolution{Width: 1280, Height: 720},
|
Resolution: Resolution{Width: mediumWidth, Height: mediumHeight}, //nolint:mnd // Medium resolution
|
||||||
Quality: 80,
|
Quality: defaultQuality, //nolint:mnd // Default quality
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -317,36 +344,38 @@ func DefaultConfig() *Config {
|
|||||||
VideoSource: VideoSourceConfig{
|
VideoSource: VideoSourceConfig{
|
||||||
Token: "video_source_2",
|
Token: "video_source_2",
|
||||||
Name: "Telephoto Camera",
|
Name: "Telephoto Camera",
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Framerate: 25,
|
Framerate: lowFramerate, //nolint:mnd // Low framerate
|
||||||
Bounds: Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
|
Bounds: Bounds{X: 0, Y: 0, Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default bounds
|
||||||
},
|
},
|
||||||
VideoEncoder: VideoEncoderConfig{
|
VideoEncoder: VideoEncoderConfig{
|
||||||
Encoding: "H264",
|
Encoding: "H264",
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Quality: 85,
|
Quality: highQuality, //nolint:mnd // High quality
|
||||||
Framerate: 25,
|
Framerate: lowFramerate, //nolint:mnd // Low framerate
|
||||||
Bitrate: 6144,
|
Bitrate: highBitrate, //nolint:mnd // High bitrate
|
||||||
GovLength: 25,
|
GovLength: lowFramerate, //nolint:mnd // Low framerate
|
||||||
},
|
},
|
||||||
PTZ: &PTZConfig{
|
PTZ: &PTZConfig{
|
||||||
NodeToken: "ptz_node_2",
|
NodeToken: "ptz_node_2",
|
||||||
PanRange: Range{Min: -180, Max: 180},
|
PanRange: Range{Min: -maxPan, Max: maxPan}, //nolint:mnd // PTZ pan range
|
||||||
TiltRange: Range{Min: -90, Max: 90},
|
TiltRange: Range{Min: -maxTilt, Max: maxTilt}, //nolint:mnd // PTZ tilt range
|
||||||
ZoomRange: Range{Min: 0, Max: 3},
|
ZoomRange: Range{Min: 0, Max: maxZoom}, //nolint:mnd // Max zoom
|
||||||
DefaultSpeed: PTZSpeed{Pan: 0.3, Tilt: 0.3, Zoom: 0.3},
|
DefaultSpeed: PTZSpeed{
|
||||||
|
Pan: lowPTZSpeed, Tilt: lowPTZSpeed, Zoom: lowPTZSpeed, //nolint:mnd // Low PTZ speed
|
||||||
|
},
|
||||||
SupportsContinuous: true,
|
SupportsContinuous: true,
|
||||||
SupportsAbsolute: true,
|
SupportsAbsolute: true,
|
||||||
SupportsRelative: true,
|
SupportsRelative: true,
|
||||||
Presets: []Preset{
|
Presets: []Preset{
|
||||||
{Token: "preset_2_0", Name: "Home", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
{Token: "preset_2_0", Name: "Home", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 0}},
|
||||||
{Token: "preset_2_1", Name: "Zoom In", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 2}},
|
{Token: "preset_2_1", Name: "Zoom In", Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: presetZoom}}, //nolint:mnd // Preset zoom
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Snapshot: SnapshotConfig{
|
Snapshot: SnapshotConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
Resolution: Resolution{Width: defaultWidth, Height: defaultHeight}, //nolint:mnd // Default resolution
|
||||||
Quality: 90,
|
Quality: highQuality, //nolint:mnd // High quality
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user