Merge pull request #462 from dbuezas/dvrip/discovery
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
package dvrip
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/dvrip"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
streams.HandleFunc("dvrip", handle)
|
||||
// DVRIP client autodiscovery
|
||||
api.HandleFunc("api/dvrip", apiDvrip)
|
||||
}
|
||||
|
||||
func handle(url string) (core.Producer, error) {
|
||||
@@ -23,3 +35,182 @@ func handle(url string) (core.Producer, error) {
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
NetCommon NetCommon `json:"NetWork.NetCommon"`
|
||||
Ret int `json:"Ret"`
|
||||
SessionID string `json:"SessionID"`
|
||||
}
|
||||
|
||||
type NetCommon struct {
|
||||
BuildDate string `json:"BuildDate"`
|
||||
ChannelNum int `json:"ChannelNum"`
|
||||
DeviceType int `json:"DeviceType"`
|
||||
GateWay string `json:"GateWay"`
|
||||
HostIP string `json:"HostIP"`
|
||||
HostName string `json:"HostName"`
|
||||
HttpPort int `json:"HttpPort"`
|
||||
MAC string `json:"MAC"`
|
||||
MonMode string `json:"MonMode"`
|
||||
NetConnectState int `json:"NetConnectState"`
|
||||
OtherFunction string `json:"OtherFunction"`
|
||||
SN string `json:"SN"`
|
||||
SSLPort int `json:"SSLPort"`
|
||||
Submask string `json:"Submask"`
|
||||
TCPMaxConn int `json:"TCPMaxConn"`
|
||||
TCPPort int `json:"TCPPort"`
|
||||
UDPPort int `json:"UDPPort"`
|
||||
UseHSDownLoad bool `json:"UseHSDownLoad"`
|
||||
Version string `json:"Version"`
|
||||
}
|
||||
|
||||
const (
|
||||
Port = 34569 // UDP port number for dvrip discovery
|
||||
Timeout = 1 * time.Second // Timeout for receiving responses
|
||||
)
|
||||
|
||||
func discover() ([]api.Stream, error) {
|
||||
log.Info().Msgf("[dvrip] discovering.")
|
||||
address := net.UDPAddr{
|
||||
Port: Port,
|
||||
IP: net.IP{239, 255, 255, 250},
|
||||
}
|
||||
|
||||
connection, err := net.ListenUDP("udp", &address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
responseChan := make(chan []byte)
|
||||
|
||||
go receiveResponses(connection, responseChan)
|
||||
go sendBroadcasts(connection)
|
||||
var items []api.Stream
|
||||
|
||||
// Process received responses
|
||||
for response := range responseChan {
|
||||
n := len(response)
|
||||
if n < 20+1 {
|
||||
log.Debug().Msg("[dvrip] No valid JSON data found in the message")
|
||||
continue
|
||||
}
|
||||
|
||||
jsonData := response[20 : n-1]
|
||||
if len(jsonData) == 0 {
|
||||
log.Err(err).Msgf("[dvrip] No valid JSON data found in the message")
|
||||
continue
|
||||
}
|
||||
var msg Message
|
||||
err = json.Unmarshal(jsonData, &msg)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("[dvrip] Error parsing JSON: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.NetCommon.HostIP != "" && msg.NetCommon.HostName != "" {
|
||||
hostIP, err := hexToDecimalBytes(msg.NetCommon.HostIP)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("[dvrip] Error parsing IP: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: "dvrip",
|
||||
Host: hostIP,
|
||||
Path: "",
|
||||
User: url.UserPassword("admin", "pass"),
|
||||
}
|
||||
queryParams := url.Values{}
|
||||
queryParams.Add("channel", "0")
|
||||
queryParams.Add("subtype", "0")
|
||||
u.RawQuery = queryParams.Encode()
|
||||
|
||||
uri := u.String()
|
||||
|
||||
exists := false
|
||||
for _, otherUrl := range items {
|
||||
if otherUrl.URL == uri {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
items = append(items, api.Stream{Name: msg.NetCommon.HostName, URL: uri})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func receiveResponses(conn *net.UDPConn, responseChan chan<- []byte) {
|
||||
buffer := make([]byte, 1024)
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(Timeout))
|
||||
n, _, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
|
||||
close(responseChan)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msgf("Error while receiving response:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy received response to a new slice to avoid data race
|
||||
responseCopy := make([]byte, n)
|
||||
copy(responseCopy, buffer[:n])
|
||||
|
||||
responseChan <- responseCopy
|
||||
}
|
||||
}
|
||||
|
||||
func sendBroadcasts(conn *net.UDPConn) {
|
||||
// broadcasting the same multiple times because the devies some times don't answer
|
||||
hexStreams := []string{
|
||||
"ff00000000000000000000000000fa0500000000",
|
||||
"ff00000000000000000000000000fa0500000000",
|
||||
"ff00000000000000000000000000fa0500000000",
|
||||
}
|
||||
|
||||
for _, hexStream := range hexStreams {
|
||||
data, err := hex.DecodeString(hexStream)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("[dvrip] Failed to decode hex stream:", err)
|
||||
continue
|
||||
}
|
||||
address := net.UDPAddr{
|
||||
Port: Port,
|
||||
IP: net.IP{255, 255, 255, 255},
|
||||
}
|
||||
|
||||
_, err = conn.WriteToUDP(data, &address)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("[dvrip] Error while sending broadcast:", err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func hexToDecimalBytes(hexIP string) (string, error) {
|
||||
// Remove the '0x' prefix
|
||||
hexIP = hexIP[2:]
|
||||
|
||||
decimalBytes, err := hex.DecodeString(hexIP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%d.%d.%d.%d", decimalBytes[3], decimalBytes[2], decimalBytes[1], decimalBytes[0]), nil
|
||||
}
|
||||
|
||||
func apiDvrip(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := discover()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
api.ResponseStreams(w, items)
|
||||
}
|
||||
|
||||
@@ -263,6 +263,18 @@
|
||||
</script>
|
||||
|
||||
|
||||
<button id="dvrip">DVRIP</button>
|
||||
<div class="module">
|
||||
<table id="dvrip-table"></table>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('dvrip').addEventListener('click', async ev => {
|
||||
ev.target.nextElementSibling.style.display = 'block'
|
||||
await getStreams('api/dvrip', 'dvrip-table')
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<button id="roborock">Roborock</button>
|
||||
<div class="module">
|
||||
<form id="roborock-form" style="margin-bottom: 10px">
|
||||
|
||||
Reference in New Issue
Block a user