Files
onvif-go/cmd/onvif-server/main.go
T
0x524a c1daba5be6 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.
2025-12-02 21:39:54 -05:00

245 lines
7.9 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/0x524a/onvif-go/server"
)
var (
version = "1.0.0"
)
const (
defaultPort = 8080
maxWorkers = 3
defaultTimeout = 30
ptzStepSize = 5
ptzMaxPan = 180
ptzMaxTilt = 90
ptzSpeed = 0.5
)
//nolint:funlen // Main function has many statements due to server setup and configuration
func main() {
// Define command-line flags
host := flag.String("host", "0.0.0.0", "Server host address")
port := flag.Int("port", defaultPort, "Server port")
username := flag.String("username", "admin", "Authentication username")
password := flag.String("password", "admin", "Authentication password")
manufacturer := flag.String("manufacturer", "onvif-go", "Device manufacturer")
model := flag.String("model", "Virtual Multi-Lens Camera", "Device model")
firmware := flag.String("firmware", "1.0.0", "Firmware version")
serial := flag.String("serial", "SN-12345678", "Serial number")
profiles := flag.Int("profiles", maxWorkers, "Number of camera profiles (1-10)") //nolint:mnd // Default profile count
ptz := flag.Bool("ptz", true, "Enable PTZ support")
imaging := flag.Bool("imaging", true, "Enable Imaging support")
events := flag.Bool("events", false, "Enable Events support")
info := flag.Bool("info", false, "Show server info and exit")
showVersion := flag.Bool("version", false, "Show version and exit")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "ONVIF Server - Virtual IP Camera Simulator\n\n")
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " # Start with default settings (3 profiles, PTZ enabled)\n")
fmt.Fprintf(os.Stderr, " %s\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " # Start with custom credentials and 5 profiles\n")
fmt.Fprintf(os.Stderr, " %s -username myuser -password mypass -profiles 5\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " # Start on specific port without PTZ\n")
fmt.Fprintf(os.Stderr, " %s -port 9000 -ptz=false\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " # Show server information\n")
fmt.Fprintf(os.Stderr, " %s -info\n\n", os.Args[0])
}
flag.Parse()
// Handle version flag
if *showVersion {
fmt.Printf("onvif-server version %s\n", version)
os.Exit(0)
}
// Validate profiles count
if *profiles < 1 || *profiles > 10 {
log.Fatal("Number of profiles must be between 1 and 10")
}
// Create server configuration
config := buildConfig(*host, *port, *username, *password, *manufacturer, *model,
*firmware, *serial, *profiles, *ptz, *imaging, *events)
// Create server
srv, err := server.New(config)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
// Handle info flag
if *info {
fmt.Println(srv.ServerInfo())
os.Exit(0)
}
// Print banner
printBanner()
// Create context that listens for interrupt signals
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Setup signal handler
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Start server in goroutine
go func() {
if err := srv.Start(ctx); err != nil {
log.Printf("Server error: %v", err)
cancel()
}
}()
// Wait for interrupt signal
<-sigChan
fmt.Println("\n🛑 Received interrupt signal, shutting down...")
cancel()
// Give the server a moment to shut down gracefully
time.Sleep(1 * time.Second)
fmt.Println("✅ Server stopped")
}
// buildConfig creates a server configuration from command-line arguments.
func buildConfig(host string, port int, username, password, manufacturer, model,
firmware, serial string, numProfiles int, ptz, imaging, events bool) *server.Config {
config := &server.Config{
Host: host,
Port: port,
BasePath: "/onvif",
Timeout: defaultTimeout * time.Second,
DeviceInfo: server.DeviceInfo{
Manufacturer: manufacturer,
Model: model,
FirmwareVersion: firmware,
SerialNumber: serial,
HardwareID: "HW-87654321",
},
Username: username,
Password: password,
SupportPTZ: ptz,
SupportImaging: imaging,
SupportEvents: events,
Profiles: make([]server.ProfileConfig, numProfiles),
}
// Define profile templates
templates := []struct {
name string
width int
height int
framerate int
bitrate int
quality float64
hasPTZ bool
ptzZoomMax float64
}{
{"Main Camera - High Quality", 1920, 1080, 30, 4096, 80, true, 1},
{"Wide Angle Camera", 1280, 720, 30, 2048, 75, false, 0},
{"Telephoto Camera", 1920, 1080, 25, 6144, 85, true, 3},
{"Low Light Camera", 1920, 1080, 30, 4096, 80, false, 0},
{"Ultra HD Camera", 3840, 2160, 30, 16384, 90, true, 2},
{"Compact Camera", 640, 480, 30, 512, 70, false, 0},
{"PTZ Dome Camera", 1920, 1080, 30, 4096, 80, true, 2},
{"Fisheye Camera", 1920, 1080, 30, 4096, 80, false, 0},
{"Thermal Camera", 640, 480, 30, 1024, 75, true, 1},
{"License Plate Camera", 1920, 1080, 60, 8192, 90, true, 5},
}
// Generate profiles
for i := 0; i < numProfiles; i++ {
template := templates[i%len(templates)]
profile := server.ProfileConfig{
Token: fmt.Sprintf("profile_%d", i),
Name: template.name,
VideoSource: server.VideoSourceConfig{
Token: fmt.Sprintf("video_source_%d", i),
Name: template.name,
Resolution: server.Resolution{Width: template.width, Height: template.height},
Framerate: template.framerate,
Bounds: server.Bounds{X: 0, Y: 0, Width: template.width, Height: template.height},
},
VideoEncoder: server.VideoEncoderConfig{
Encoding: "H264",
Resolution: server.Resolution{Width: template.width, Height: template.height},
Quality: template.quality,
Framerate: template.framerate,
Bitrate: template.bitrate,
GovLength: template.framerate,
},
Snapshot: server.SnapshotConfig{
Enabled: true,
Resolution: server.Resolution{Width: template.width, Height: template.height},
Quality: template.quality + 5, //nolint:mnd // Quality offset
},
}
// Add PTZ if enabled and template supports it
if ptz && template.hasPTZ {
profile.PTZ = &server.PTZConfig{
NodeToken: fmt.Sprintf("ptz_node_%d", i),
PanRange: server.Range{Min: -ptzMaxPan, Max: ptzMaxPan},
TiltRange: server.Range{Min: -ptzMaxTilt, Max: ptzMaxTilt},
ZoomRange: server.Range{Min: 0, Max: template.ptzZoomMax},
DefaultSpeed: server.PTZSpeed{Pan: ptzSpeed, Tilt: ptzSpeed, Zoom: ptzSpeed},
SupportsContinuous: true,
SupportsAbsolute: true,
SupportsRelative: true,
Presets: []server.Preset{
{
Token: fmt.Sprintf("preset_%d_0", i),
Name: "Home",
Position: server.PTZPosition{Pan: 0, Tilt: 0, Zoom: 0},
},
{
Token: fmt.Sprintf("preset_%d_1", i),
Name: "Entrance",
Position: server.PTZPosition{
Pan: -45, Tilt: -10, Zoom: template.ptzZoomMax * ptzSpeed, //nolint:mnd // Preset position values
},
},
},
}
}
config.Profiles[i] = profile
}
return config
}
// printBanner prints the application banner.
func printBanner() {
banner := `
╔═══════════════════════════════════════════════════════════╗
║ ║
║ 🎥 ONVIF Virtual Camera Server 🎥 ║
║ ║
║ Simulate multi-lens IP cameras with ONVIF support ║
║ Version: ` + version + `
║ ║
╚═══════════════════════════════════════════════════════════╝
`
fmt.Println(banner)
}