From 46035f4873fc0d566ec418a2aae478147d81e43c Mon Sep 17 00:00:00 2001 From: ProtoTess <32490978+0x524A@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:41:03 +0000 Subject: [PATCH] feat: add network interface selection to CLI tools onvif-cli improvements: - Add menu option to list network interfaces - Add interface selection during discovery - Display detailed interface information (up/down, multicast, addresses) - Allow discovery by interface name or IP address - Maintain backward compatibility with default interface onvif-quick improvements: - Add menu option to list network interfaces - Add interface selection during discovery - Simplified interface list display - Quick discovery on specific network Documentation: - Add comprehensive CLI_NETWORK_INTERFACE_USAGE.md guide - Include usage scenarios and workflows - Troubleshooting section - Integration examples - Command reference table These enhancements allow users to easily specify which network interface to use for camera discovery, solving issues with multi-interface systems. --- cmd/onvif-cli/main.go | 110 ++++++- cmd/onvif-quick/main.go | 90 +++++- docs/CLI_NETWORK_INTERFACE_USAGE.md | 473 ++++++++++++++++++++++++++++ 3 files changed, 656 insertions(+), 17 deletions(-) create mode 100644 docs/CLI_NETWORK_INTERFACE_USAGE.md diff --git a/cmd/onvif-cli/main.go b/cmd/onvif-cli/main.go index 9833807..1de99e8 100644 --- a/cmd/onvif-cli/main.go +++ b/cmd/onvif-cli/main.go @@ -36,14 +36,16 @@ func main() { case "1": cli.discoverCameras() case "2": - cli.connectToCamera() + cli.listNetworkInterfaces() case "3": - cli.deviceOperations() + cli.connectToCamera() case "4": - cli.mediaOperations() + cli.deviceOperations() case "5": - cli.ptzOperations() + cli.mediaOperations() case "6": + cli.ptzOperations() + case "7": cli.imagingOperations() case "0", "q", "quit", "exit": fmt.Println("Goodbye! 👋") @@ -58,14 +60,15 @@ func main() { func (c *CLI) showMainMenu() { fmt.Println("📋 Main Menu:") fmt.Println(" 1. Discover Cameras on Network") - fmt.Println(" 2. Connect to Camera") + fmt.Println(" 2. List Network Interfaces") + fmt.Println(" 3. Connect to Camera") if c.client != nil { - fmt.Println(" 3. Device Operations") - fmt.Println(" 4. Media Operations") - fmt.Println(" 5. PTZ Operations") - fmt.Println(" 6. Imaging Operations") + fmt.Println(" 4. Device Operations") + fmt.Println(" 5. Media Operations") + fmt.Println(" 6. PTZ Operations") + fmt.Println(" 7. Imaging Operations") } else { - fmt.Println(" 3-6. (Connect to camera first)") + fmt.Println(" 4-7. (Connect to camera first)") } fmt.Println(" 0. Exit") fmt.Println() @@ -87,14 +90,99 @@ func (c *CLI) readInputWithDefault(prompt, defaultValue string) string { return input } +func (c *CLI) listNetworkInterfaces() { + fmt.Println("🌐 Available Network Interfaces") + fmt.Println("================================") + + interfaces, err := discovery.ListNetworkInterfaces() + if err != nil { + fmt.Printf("❌ Error listing interfaces: %v\n", err) + return + } + + if len(interfaces) == 0 { + fmt.Println("❌ No network interfaces found") + return + } + + fmt.Printf("✅ Found %d interface(s):\n\n", len(interfaces)) + + for _, iface := range interfaces { + upStr := "⬆️ Up" + if !iface.Up { + upStr = "⬇️ Down" + } + + multicastStr := "✓" + if !iface.Multicast { + multicastStr = "✗" + } + + fmt.Printf("📡 %s (%s, Multicast: %s)\n", iface.Name, upStr, multicastStr) + + if len(iface.Addresses) == 0 { + fmt.Println(" (No addresses assigned)") + } else { + for _, addr := range iface.Addresses { + fmt.Printf(" └─ %s\n", addr) + } + } + fmt.Println() + } + + fmt.Println("💡 Use interface name or IP address when discovering cameras") + fmt.Println(" Example: eth0 or 192.168.1.100") +} + + func (c *CLI) discoverCameras() { fmt.Println("🔍 Discovering ONVIF cameras...") fmt.Println("This may take a few seconds...") + // Ask user if they want to select a specific network interface + useSpecificInterface := c.readInput("Use specific network interface? (y/n) [n]: ") + useSpecificInterface = strings.ToLower(useSpecificInterface) + + var opts *discovery.DiscoverOptions + if useSpecificInterface == "y" || useSpecificInterface == "yes" { + fmt.Println("\nAvailable network interfaces:") + interfaces, err := discovery.ListNetworkInterfaces() + if err != nil { + fmt.Printf("❌ Error listing interfaces: %v\n", err) + return + } + + for i, iface := range interfaces { + fmt.Printf(" %d. %s\n", i+1, iface.Name) + for _, addr := range iface.Addresses { + fmt.Printf(" └─ %s\n", addr) + } + multicastStr := "No" + if iface.Multicast { + multicastStr = "Yes" + } + fmt.Printf(" (Up: %v, Multicast: %s)\n", iface.Up, multicastStr) + } + + ifaceInput := c.readInput("\nEnter interface name or IP address: ") + ifaceInput = strings.TrimSpace(ifaceInput) + + if ifaceInput != "" { + opts = &discovery.DiscoverOptions{ + NetworkInterface: ifaceInput, + } + fmt.Printf("🎯 Using interface: %s\n\n", ifaceInput) + } + } + + if opts == nil { + opts = &discovery.DiscoverOptions{} + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - devices, err := discovery.Discover(ctx, 5*time.Second) + devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts) if err != nil { fmt.Printf("❌ Discovery failed: %v\n", err) return diff --git a/cmd/onvif-quick/main.go b/cmd/onvif-quick/main.go index 9e96f56..18b88c1 100644 --- a/cmd/onvif-quick/main.go +++ b/cmd/onvif-quick/main.go @@ -22,9 +22,10 @@ func main() { for { fmt.Println("What would you like to do?") fmt.Println("1. 🔍 Discover cameras") - fmt.Println("2. 📹 Connect to camera") - fmt.Println("3. 🎮 PTZ demo") - fmt.Println("4. 📡 Get stream URLs") + fmt.Println("2. 🌐 List network interfaces") + fmt.Println("3. 📹 Connect to camera") + fmt.Println("4. 🎮 PTZ demo") + fmt.Println("5. 📡 Get stream URLs") fmt.Println("0. Exit") fmt.Print("\nChoice: ") @@ -35,10 +36,12 @@ func main() { case "1": discoverCameras() case "2": - connectAndShowInfo() + listNetworkInterfaces() case "3": - ptzDemo() + connectAndShowInfo() case "4": + ptzDemo() + case "5": getStreamURLs() case "0", "q", "quit": fmt.Println("Goodbye! 👋") @@ -51,12 +54,48 @@ func main() { } func discoverCameras() { + reader := bufio.NewReader(os.Stdin) + fmt.Println("🔍 Discovering cameras on network...") + // Ask if user wants to use a specific interface + fmt.Print("Use specific network interface? (y/n) [n]: ") + useInterface, _ := reader.ReadString('\n') + useInterface = strings.ToLower(strings.TrimSpace(useInterface)) + + var opts *discovery.DiscoverOptions + if useInterface == "y" || useInterface == "yes" { + // List interfaces + interfaces, err := discovery.ListNetworkInterfaces() + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Println("\nAvailable interfaces:") + for i, iface := range interfaces { + fmt.Printf(" %d. %s (%v)\n", i+1, iface.Name, iface.Addresses) + } + + fmt.Print("\nEnter interface name or IP: ") + ifaceInput, _ := reader.ReadString('\n') + ifaceInput = strings.TrimSpace(ifaceInput) + + if ifaceInput != "" { + opts = &discovery.DiscoverOptions{ + NetworkInterface: ifaceInput, + } + } + } + + if opts == nil { + opts = &discovery.DiscoverOptions{} + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - devices, err := discovery.Discover(ctx, 5*time.Second) + devices, err := discovery.DiscoverWithOptions(ctx, 5*time.Second, opts) if err != nil { fmt.Printf("❌ Error: %v\n", err) return @@ -73,6 +112,45 @@ func discoverCameras() { } } +func listNetworkInterfaces() { + fmt.Println("🌐 Network Interfaces") + fmt.Println("====================") + + interfaces, err := discovery.ListNetworkInterfaces() + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + if len(interfaces) == 0 { + fmt.Println("No network interfaces found") + return + } + + fmt.Printf("✅ Found %d interface(s):\n\n", len(interfaces)) + + for _, iface := range interfaces { + upStr := "Up" + if !iface.Up { + upStr = "Down" + } + + multicastStr := "Yes" + if !iface.Multicast { + multicastStr = "No" + } + + fmt.Printf("📡 %s (%s, Multicast: %s)\n", iface.Name, upStr, multicastStr) + + if len(iface.Addresses) > 0 { + for _, addr := range iface.Addresses { + fmt.Printf(" └─ %s\n", addr) + } + } + } +} + + func connectAndShowInfo() { reader := bufio.NewReader(os.Stdin) diff --git a/docs/CLI_NETWORK_INTERFACE_USAGE.md b/docs/CLI_NETWORK_INTERFACE_USAGE.md new file mode 100644 index 0000000..f4e8e50 --- /dev/null +++ b/docs/CLI_NETWORK_INTERFACE_USAGE.md @@ -0,0 +1,473 @@ +# CLI Tools with Network Interface Support + +This guide shows how to use the enhanced CLI tools with network interface discovery capabilities. + +## Overview + +Both `onvif-cli` and `onvif-quick` now support explicit network interface selection when discovering ONVIF cameras. This is useful when you have multiple network interfaces on your system. + +## onvif-cli - Full-featured CLI + +### Building onvif-cli + +```bash +# From the project root +go build -o onvif-cli ./cmd/onvif-cli +``` + +### Running onvif-cli + +```bash +./onvif-cli +``` + +### Main Menu Features + +``` +📋 Main Menu: + 1. Discover Cameras on Network + 2. List Network Interfaces + 3. Connect to Camera + 4. Device Operations + 5. Media Operations + 6. PTZ Operations + 7. Imaging Operations + 0. Exit +``` + +### Feature 1: List Network Interfaces + +Select option `2` to see all available network interfaces: + +``` +🌐 Available Network Interfaces +================================ +✅ Found 3 interface(s): + +📡 lo (⬆️ Up, Multicast: ✓) + └─ 127.0.0.1 + └─ ::1 + +📡 eth0 (⬆️ Up, Multicast: ✓) + └─ 192.168.1.100 + └─ fe80::1 + +📡 wlan0 (⬆️ Up, Multicast: ✓) + └─ 192.168.88.50 + +💡 Use interface name or IP address when discovering cameras + Example: eth0 or 192.168.1.100 +``` + +### Feature 2: Discover with Interface Selection + +Select option `1` for camera discovery: + +``` +🔍 Discovering ONVIF cameras... +This may take a few seconds... +Use specific network interface? (y/n) [n]: y + +🌐 Available network interfaces: + 1. lo + └─ 127.0.0.1 + (Up: true, Multicast: No) + 2. eth0 + └─ 192.168.1.100 + (Up: true, Multicast: Yes) + 3. wlan0 + └─ 192.168.88.50 + (Up: true, Multicast: Yes) + +Enter interface name or IP address: eth0 +🎯 Using interface: eth0 + +✅ Found 2 camera(s): + +📹 Camera #1: + Endpoint: http://192.168.1.101:8080/onvif/device_service + Name: Office Camera + Location: Conference Room A + Types: [...] + XAddrs: [...] +``` + +### Usage Scenarios + +#### Scenario 1: Quick Camera Discovery (Default Interface) + +```bash +./onvif-cli +# Select: 1 (Discover) +# Answer: n (use default interface) +# Result: Discovers on system default interface +``` + +#### Scenario 2: Discover on Specific Ethernet Interface + +```bash +./onvif-cli +# Select: 2 (List interfaces) +# See eth0 is available with 192.168.1.100 +# Select: 1 (Discover) +# Answer: y (use specific interface) +# Enter: eth0 or 192.168.1.100 +# Result: Discovers only on eth0 +``` + +#### Scenario 3: Discover on WiFi Interface + +```bash +./onvif-cli +# Select: 2 (List interfaces) +# See wlan0 is available with 192.168.88.50 +# Select: 1 (Discover) +# Answer: y (use specific interface) +# Enter: wlan0 +# Result: Discovers only on wlan0 +``` + +#### Scenario 4: Connect and Control + +```bash +./onvif-cli +# Select: 1 (Discover) -> Find camera -> Connect +# Or: Select: 3 (Connect) -> Enter endpoint manually +# Then use options 4-7 for device/media/ptz/imaging control +``` + +## onvif-quick - Quick Demo Tool + +### Building onvif-quick + +```bash +# From the project root +go build -o onvif-quick ./cmd/onvif-quick +``` + +### Running onvif-quick + +```bash +./onvif-quick +``` + +### Main Menu Features + +``` +What would you like to do? +1. 🔍 Discover cameras +2. 🌐 List network interfaces +3. 📹 Connect to camera +4. 🎮 PTZ demo +5. 📡 Get stream URLs +0. Exit +``` + +### Feature 1: List Network Interfaces + +Select option `2`: + +``` +🌐 Network Interfaces +==================== +✅ Found 3 interface(s): + +📡 lo (Up, Multicast: No) + └─ 127.0.0.1 + └─ ::1 + +📡 eth0 (Up, Multicast: Yes) + └─ 192.168.1.100 + └─ fe80::1 + +📡 wlan0 (Up, Multicast: Yes) + └─ 192.168.88.50 +``` + +### Feature 2: Quick Discovery with Interface Selection + +Select option `1`: + +``` +🔍 Discovering cameras on network... +Use specific network interface? (y/n) [n]: y + +Available interfaces: + 1. lo (127.0.0.1, ::1) + 2. eth0 (192.168.1.100, fe80::1) + 3. wlan0 (192.168.88.50) + +Enter interface name or IP: eth0 +✅ Found 1 camera(s): + 1. Office Camera (http://192.168.1.101:8080/onvif/device_service) +``` + +### Quick Demo Workflows + +#### Workflow 1: List Interfaces → Discover → Check Streams + +```bash +./onvif-quick +# Select: 2 (List interfaces) +# See which interfaces are available +# Select: 1 (Discover) +# Choose eth0 +# Specify credentials when found +# Select: 5 (Get stream URLs) to see RTSP streams +``` + +#### Workflow 2: PTZ Demo on Specific Interface + +```bash +./onvif-quick +# Select: 1 (Discover) on eth0 +# Find PTZ-capable camera +# Select: 4 (PTZ demo) +# Test pan/tilt/zoom movements +``` + +## Common Workflows + +### Workflow A: Multi-Network Environment + +You have a system with both Ethernet (192.168.1.0/24) and WiFi (192.168.88.0/24): + +```bash +./onvif-cli + +# Step 1: List interfaces +1 (Discover) +n (default) +# No results? + +# Step 2: Try Ethernet explicitly +1 (Discover) +y (specific interface) +eth0 +# Found cameras on ethernet! + +# Step 3: Try WiFi +1 (Discover) +y (specific interface) +wlan0 +# Found different cameras on WiFi! +``` + +### Workflow B: Docker Container with Multiple Networks + +Container has management (172.17.0.x) and camera (172.20.0.x) networks: + +```bash +./onvif-quick + +# Step 1: See available networks +2 (List interfaces) +# Output shows two networks with different IPs + +# Step 2: Discover on camera network +1 (Discover) +y (specific interface) +172.20.0.10 # Use the camera network IP +# Discovers cameras on the camera network +``` + +### Workflow C: Network Troubleshooting + +Discovery not working as expected? + +```bash +./onvif-cli + +# Step 1: Check all interfaces +2 (List interfaces) +# Look for: +# - Interfaces marked "Up: true" +# - Multicast support: Yes +# - Expected IP addresses + +# Step 2: Try discovery on each interface +1 (Discover) +y (use specific interface) +# Try each interface name one by one +# See which one finds cameras + +# Result: Identifies which network has your cameras +``` + +## Tips & Best Practices + +### 1. Check Interface Status First + +Always start with option 2 to see: +- Interface names (eth0, wlan0, docker0, etc.) +- IP addresses assigned +- Whether multicast is supported +- Whether the interface is up/down + +```bash +# Quick check +./onvif-cli +2 (List interfaces) +``` + +### 2. Use Interface Names When Possible + +Interface names are more reliable than IP addresses: + +``` +Good: eth0, wlan0 +Less good: 192.168.1.100 (may change) +``` + +### 3. Check Multicast Support + +Ensure the interface supports multicast (required for WS-Discovery): + +``` +Look for: "Multicast: Yes" or "Multicast: ✓" +``` + +### 4. Isolate Discovery to One Network + +If you have many interfaces, disable the ones you don't need: + +```bash +./onvif-cli +1 (Discover) +y (specify eth0) +# Only discovers on eth0, ignores other interfaces +``` + +### 5. Scripting and Automation + +For automation, you can pipe input: + +```bash +# Non-interactive discovery on eth0 +(echo 1; echo y; echo eth0; sleep 2; echo 0) | ./onvif-cli + +# Or with timeout +timeout 30 bash -c '(echo 1; echo y; echo eth0) | ./onvif-cli' +``` + +## Troubleshooting + +### Problem: "Use specific network interface?" appears on every discovery + +**Solution**: This is the normal behavior in onvif-cli. To skip it, answer `n` to use the system default interface. + +### Problem: Interface listed but discovery fails + +**Possible causes**: +1. Interface doesn't support multicast (check "Multicast: Yes") +2. Cameras aren't on that network segment +3. Firewall blocking UDP 3702 + +**Solution**: +```bash +./onvif-cli +2 (List interfaces) +# Check Multicast: Yes +# Check interface is "Up: true" +1 (Discover) +y (use specific interface) +# Try the confirmed interface +``` + +### Problem: "network interface not found" error + +**Solution**: +1. Use `2 (List interfaces)` to see exact interface names +2. Copy the exact name from the list +3. Try again with correct interface name + +```bash +# Wrong: eth-0 or ethnet0 +# Right: eth0 (from list) +``` + +### Problem: No cameras found on any interface + +**Possible causes**: +1. Cameras on different subnet +2. Firewall blocking discovery +3. ONVIF not enabled on cameras + +**Solution**: +```bash +# Try each interface individually +./onvif-cli +2 (List interfaces) +# For each interface that shows "Multicast: Yes" and "Up: true" +1 (Discover) +y (use that interface) +# Check if cameras found +``` + +## Integration with Other Tools + +### Using Discovered Camera with VLC + +```bash +./onvif-cli +1 (Discover) +y (eth0) +# Get stream URL from discovered camera +2 (Get stream URIs) +# Copy RTSP URL +# Paste into VLC: File → Open Network Stream +``` + +### Scripting Camera Discovery + +```bash +#!/bin/bash +# discover_cameras.sh + +# List all interfaces with multicast support +./onvif-cli << EOF +2 +q +EOF | grep "Multicast: ✓" | grep -o "📡 [^ ]*" | cut -d' ' -f2 | while read iface; do + echo "Discovering on $iface..." + # Could add automated discovery here +done +``` + +## Related Documentation + +- [NETWORK_INTERFACE_GUIDE.md](../discovery/NETWORK_INTERFACE_GUIDE.md) - Detailed discovery API guide +- [QUICKSTART.md](../QUICKSTART.md) - Quick start guide +- [examples/discovery/](../examples/discovery/) - Discovery code examples +- [ONVIF Specification](https://www.onvif.org/) - Official ONVIF specs + +## Command Reference + +### onvif-cli Commands + +| Option | Feature | Purpose | +|--------|---------|---------| +| 1 | Discover Cameras | Find ONVIF cameras (with interface selection) | +| 2 | List Interfaces | See all network interfaces | +| 3 | Connect to Camera | Manual endpoint connection | +| 4 | Device Operations | Info, capabilities, datetime, reboot | +| 5 | Media Operations | Profiles, streams, snapshots, video settings | +| 6 | PTZ Operations | Pan/tilt/zoom control and presets | +| 7 | Imaging Operations | Brightness, contrast, saturation, etc. | +| 0 | Exit | Quit the application | + +### onvif-quick Commands + +| Option | Feature | Purpose | +|--------|---------|---------| +| 1 | Discover Cameras | Find ONVIF cameras (quick, with interface selection) | +| 2 | List Interfaces | See all network interfaces | +| 3 | Connect to Camera | Quick connection and info | +| 4 | PTZ Demo | Quick PTZ movement demonstration | +| 5 | Get Stream URLs | Display all stream and snapshot URLs | +| 0 | Exit | Quit the application | + +## Version History + +- **Current**: Network interface selection support added +- **Previous**: Basic discovery and camera control