chore: v0.3.0 — real ONVIF TCP probe in /api/scan, greenfield wizard with IP range
This commit is contained in:
parent
e8b795503f
commit
e94d49253e
1 changed files with 56 additions and 10 deletions
66
api.go
66
api.go
|
|
@ -4,6 +4,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -119,14 +120,50 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// handleScan triggers ONVIF network discovery.
|
// handleScan triggers ONVIF network discovery.
|
||||||
// POST /api/scan
|
// POST /api/scan
|
||||||
|
// Body: {"method":"range","from":"192.168.1.200","to":"192.168.1.210","username":"admin","password":"..."}
|
||||||
func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Error: "method not allowed"})
|
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Error: "method not allowed"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: M3 — real ONVIF WS-Discovery.
|
var req ScanRequest
|
||||||
// For now, return a stub response with the expected IP range.
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
// Fall back to legacy stub scan if no body provided.
|
||||||
|
s.legacyScan(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.From == "" || req.To == "" {
|
||||||
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Error: "from and to IP addresses required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fromIP := net.ParseIP(req.From)
|
||||||
|
toIP := net.ParseIP(req.To)
|
||||||
|
if fromIP == nil || toIP == nil {
|
||||||
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Error: "invalid IP address"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]ONVIFDiscovery, 0)
|
||||||
|
// Inclusive scan from fromIP to toIP.
|
||||||
|
ip := make(net.IP, len(fromIP))
|
||||||
|
copy(ip, fromIP)
|
||||||
|
for {
|
||||||
|
d := probeDevice(ip.String(), req.Username, req.Password)
|
||||||
|
results = append(results, d)
|
||||||
|
if ip.Equal(toIP) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ip = nextIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: results})
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacyScan is the old stub handler for backward compatibility.
|
||||||
|
func (s *Server) legacyScan(w http.ResponseWriter) {
|
||||||
type DiscoveredCamera struct {
|
type DiscoveredCamera struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Manufacturer string `json:"manufacturer"`
|
Manufacturer string `json:"manufacturer"`
|
||||||
|
|
@ -135,7 +172,6 @@ func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
||||||
RTSPSub string `json:"rtsp_sub"`
|
RTSPSub string `json:"rtsp_sub"`
|
||||||
Found bool `json:"found"`
|
Found bool `json:"found"`
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]DiscoveredCamera, 0)
|
results := make([]DiscoveredCamera, 0)
|
||||||
for ip := 201; ip <= 209; ip++ {
|
for ip := 201; ip <= 209; ip++ {
|
||||||
if ip == 204 {
|
if ip == 204 {
|
||||||
|
|
@ -143,18 +179,28 @@ func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
ipStr := "192.168.1." + itoa(ip)
|
ipStr := "192.168.1." + itoa(ip)
|
||||||
results = append(results, DiscoveredCamera{
|
results = append(results, DiscoveredCamera{
|
||||||
IP: ipStr,
|
IP: ipStr,
|
||||||
Manufacturer: "Unknown",
|
Found: false,
|
||||||
Model: "ONVIF Camera",
|
RTSPMain: "rtsp://" + ipStr + ":554/Streaming/Channels/101",
|
||||||
RTSPMain: "rtsp://" + ipStr + ":554/Streaming/Channels/101",
|
RTSPSub: "rtsp://" + ipStr + ":554/Streaming/Channels/102",
|
||||||
RTSPSub: "rtsp://" + ipStr + ":554/Streaming/Channels/102",
|
|
||||||
Found: false, // requires real ONVIF probe in M3
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: results})
|
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: results})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nextIP returns the next IP address in sequence.
|
||||||
|
func nextIP(ip net.IP) net.IP {
|
||||||
|
next := make(net.IP, len(ip))
|
||||||
|
copy(next, ip)
|
||||||
|
for i := len(next) - 1; i >= 0; i-- {
|
||||||
|
next[i]++
|
||||||
|
if next[i] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
// handleStatus returns server health and runtime information.
|
// handleStatus returns server health and runtime information.
|
||||||
// GET /api/status
|
// GET /api/status
|
||||||
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue