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 (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
|
@ -119,14 +120,50 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// handleScan triggers ONVIF network discovery.
|
||||
// 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) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Error: "method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: M3 — real ONVIF WS-Discovery.
|
||||
// For now, return a stub response with the expected IP range.
|
||||
var req ScanRequest
|
||||
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 {
|
||||
IP string `json:"ip"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
|
|
@ -135,7 +172,6 @@ func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
|||
RTSPSub string `json:"rtsp_sub"`
|
||||
Found bool `json:"found"`
|
||||
}
|
||||
|
||||
results := make([]DiscoveredCamera, 0)
|
||||
for ip := 201; ip <= 209; ip++ {
|
||||
if ip == 204 {
|
||||
|
|
@ -143,18 +179,28 @@ func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
ipStr := "192.168.1." + itoa(ip)
|
||||
results = append(results, DiscoveredCamera{
|
||||
IP: ipStr,
|
||||
Manufacturer: "Unknown",
|
||||
Model: "ONVIF Camera",
|
||||
RTSPMain: "rtsp://" + ipStr + ":554/Streaming/Channels/101",
|
||||
RTSPSub: "rtsp://" + ipStr + ":554/Streaming/Channels/102",
|
||||
Found: false, // requires real ONVIF probe in M3
|
||||
IP: ipStr,
|
||||
Found: false,
|
||||
RTSPMain: "rtsp://" + ipStr + ":554/Streaming/Channels/101",
|
||||
RTSPSub: "rtsp://" + ipStr + ":554/Streaming/Channels/102",
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
// GET /api/status
|
||||
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue