diff --git a/examples/onvif_client/main.go b/examples/onvif_client/main.go
new file mode 100644
index 00000000..03dd12ba
--- /dev/null
+++ b/examples/onvif_client/main.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "log"
+ "net"
+ "net/url"
+ "os"
+
+ "github.com/AlexxIT/go2rtc/pkg/onvif"
+)
+
+func main() {
+ var rawURL = os.Args[1]
+ var operation = os.Args[2]
+ var token string
+ if len(os.Args) > 3 {
+ token = os.Args[3]
+ }
+
+ client, err := onvif.NewClient(rawURL)
+ if err != nil {
+ log.Panic(err)
+ }
+
+ var b []byte
+
+ switch operation {
+ case onvif.ServiceGetServiceCapabilities:
+ b, err = client.MediaRequest(operation)
+ case onvif.DeviceGetCapabilities,
+ onvif.DeviceGetDeviceInformation,
+ onvif.DeviceGetDiscoveryMode,
+ onvif.DeviceGetDNS,
+ onvif.DeviceGetHostname,
+ onvif.DeviceGetNetworkDefaultGateway,
+ onvif.DeviceGetNetworkInterfaces,
+ onvif.DeviceGetNetworkProtocols,
+ onvif.DeviceGetNTP,
+ onvif.DeviceGetScopes,
+ onvif.DeviceGetServices,
+ onvif.DeviceGetSystemDateAndTime,
+ onvif.DeviceSystemReboot:
+ b, err = client.DeviceRequest(operation)
+ case onvif.MediaGetProfiles, onvif.MediaGetVideoSources:
+ b, err = client.MediaRequest(operation)
+ case onvif.MediaGetProfile:
+ b, err = client.GetProfile(token)
+ case onvif.MediaGetVideoSourceConfiguration:
+ b, err = client.GetVideoSourceConfiguration(token)
+ case onvif.MediaGetStreamUri:
+ b, err = client.GetStreamUri(token)
+ case onvif.MediaGetSnapshotUri:
+ b, err = client.GetSnapshotUri(token)
+ default:
+ log.Printf("unknown action\n")
+ }
+
+ if err != nil {
+ log.Printf("%s\n", err)
+ }
+
+ u, err := url.Parse(rawURL)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ host, _, _ := net.SplitHostPort(u.Host)
+
+ if err = os.WriteFile(host+"_"+operation+".xml", b, 0644); err != nil {
+ log.Printf("%s\n", err)
+ }
+}
diff --git a/internal/onvif/README.md b/internal/onvif/README.md
new file mode 100644
index 00000000..ee922fbf
--- /dev/null
+++ b/internal/onvif/README.md
@@ -0,0 +1,25 @@
+# ONVIF
+
+A regular camera has a single video source (`GetVideoSources`) and two profiles (`GetProfiles`).
+
+Go2rtc has one video source and one profile per stream.
+
+## Tested clients
+
+Go2rtc works as ONVIF server:
+
+- Happytime onvif client (windows)
+- Home Assistant ONVIF integration (linux)
+- Onvier (android)
+- ONVIF Device Manager (windows)
+
+PS. Support only TCP transport for RTSP protocol. UDP and HTTP transports - unsupported yet.
+
+## Tested cameras
+
+Go2rtc works as ONVIF client:
+
+- Dahua IPC-K42
+- OpenIPC
+- Reolink RLC-520A
+- TP-Link Tapo TC60
diff --git a/internal/onvif/init.go b/internal/onvif/onvif.go
similarity index 67%
rename from internal/onvif/init.go
rename to internal/onvif/onvif.go
index e5ed9a7c..d332ca38 100644
--- a/internal/onvif/init.go
+++ b/internal/onvif/onvif.go
@@ -55,55 +55,65 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
return
}
- action := onvif.GetRequestAction(b)
- if action == "" {
+ operation := onvif.GetRequestAction(b)
+ if operation == "" {
http.Error(w, "malformed request body", http.StatusBadRequest)
return
}
- log.Trace().Msgf("[onvif] %s", action)
+ log.Trace().Msgf("[onvif] server request %s %s:\n%s", r.Method, r.RequestURI, b)
- var res string
+ switch operation {
+ case onvif.DeviceGetNetworkInterfaces, // important for Hass
+ onvif.DeviceGetSystemDateAndTime, // important for Hass
+ onvif.DeviceGetDiscoveryMode,
+ onvif.DeviceGetDNS,
+ onvif.DeviceGetHostname,
+ onvif.DeviceGetNetworkDefaultGateway,
+ onvif.DeviceGetNetworkProtocols,
+ onvif.DeviceGetNTP,
+ onvif.DeviceGetScopes:
+ b = onvif.StaticResponse(operation)
- switch action {
- case onvif.ActionGetCapabilities:
+ case onvif.DeviceGetCapabilities:
// important for Hass: Media section
- res = onvif.GetCapabilitiesResponse(r.Host)
+ b = onvif.GetCapabilitiesResponse(r.Host)
- case onvif.ActionGetServices:
- res = onvif.GetServicesResponse(r.Host)
+ case onvif.DeviceGetServices:
+ b = onvif.GetServicesResponse(r.Host)
- case onvif.ActionGetSystemDateAndTime:
- // important for Hass
- res = onvif.GetSystemDateAndTimeResponse()
-
- case onvif.ActionGetNetworkInterfaces:
- // important for Hass: none
- res = onvif.GetNetworkInterfacesResponse()
-
- case onvif.ActionGetDeviceInformation:
+ case onvif.DeviceGetDeviceInformation:
// important for Hass: SerialNumber (unique server ID)
- res = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
+ b = onvif.GetDeviceInformationResponse("", "go2rtc", app.Version, r.Host)
- case onvif.ActionGetServiceCapabilities:
+ case onvif.ServiceGetServiceCapabilities:
// important for Hass
- res = onvif.GetServiceCapabilitiesResponse()
+ // TODO: check path links to media
+ b = onvif.GetMediaServiceCapabilitiesResponse()
- case onvif.ActionSystemReboot:
- res = onvif.SystemRebootResponse()
+ case onvif.DeviceSystemReboot:
+ b = onvif.StaticResponse(operation)
time.AfterFunc(time.Second, func() {
os.Exit(0)
})
- case onvif.ActionGetProfiles:
+ case onvif.MediaGetVideoSources:
+ b = onvif.GetVideoSourcesResponse(streams.GetAll())
+
+ case onvif.MediaGetProfiles:
// important for Hass: H264 codec, width, height
- res = onvif.GetProfilesResponse(streams.GetAll())
+ b = onvif.GetProfilesResponse(streams.GetAll())
- case onvif.ActionGetVideoSources:
- res = onvif.GetVideoSourcesResponse(streams.GetAll())
+ case onvif.MediaGetProfile:
+ token := onvif.FindTagValue(b, "ProfileToken")
+ b = onvif.GetProfileResponse(token)
- case onvif.ActionGetStreamUri:
+ case onvif.MediaGetVideoSourceConfiguration:
+ token := onvif.FindTagValue(b, "ConfigurationToken")
+ b = onvif.GetVideoSourceConfigurationResponse(token)
+
+ case onvif.MediaGetStreamUri:
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -111,20 +121,22 @@ func onvifDeviceService(w http.ResponseWriter, r *http.Request) {
}
uri := "rtsp://" + host + ":" + rtsp.Port + "/" + onvif.FindTagValue(b, "ProfileToken")
- res = onvif.GetStreamUriResponse(uri)
+ b = onvif.GetStreamUriResponse(uri)
- case onvif.ActionGetSnapshotUri:
+ case onvif.MediaGetSnapshotUri:
uri := "http://" + r.Host + "/api/frame.jpeg?src=" + onvif.FindTagValue(b, "ProfileToken")
- res = onvif.GetSnapshotUriResponse(uri)
+ b = onvif.GetSnapshotUriResponse(uri)
default:
- http.Error(w, "unsupported action", http.StatusBadRequest)
+ http.Error(w, "unsupported operation", http.StatusBadRequest)
log.Debug().Msgf("[onvif] unsupported request:\n%s", b)
return
}
+ log.Trace().Msgf("[onvif] server response:\n%s", b)
+
w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
- if _, err = w.Write([]byte(res)); err != nil {
+ if _, err = w.Write(b); err != nil {
log.Error().Err(err).Caller().Send()
}
}
@@ -170,7 +182,7 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) {
}
if l := log.Trace(); l.Enabled() {
- b, _ := client.GetProfiles()
+ b, _ := client.MediaRequest(onvif.MediaGetProfiles)
l.Msgf("[onvif] src=%s profiles:\n%s", src, b)
}
diff --git a/pkg/onvif/README.md b/pkg/onvif/README.md
new file mode 100644
index 00000000..73267379
--- /dev/null
+++ b/pkg/onvif/README.md
@@ -0,0 +1,38 @@
+## Profiles
+
+- Profile A - For access control configuration
+- Profile C - For door control and event management
+- Profile S - For basic video streaming
+ - Video streaming and configuration
+- Profile T - For advanced video streaming
+ - H.264 / H.265 video compression
+ - Imaging settings
+ - Motion alarm and tampering events
+ - Metadata streaming
+ - Bi-directional audio
+
+## Services
+
+https://www.onvif.org/profiles/specifications/
+
+- https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl
+- https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl
+- https://www.onvif.org/ver10/media/wsdl/media.wsdl
+
+## TMP
+
+| | Dahua | Reolink | TP-Link |
+|------------------------|---------|---------|---------|
+| GetCapabilities | no auth | no auth | no auth |
+| GetServices | no auth | no auth | no auth |
+| GetServiceCapabilities | no auth | no auth | auth |
+| GetSystemDateAndTime | no auth | no auth | no auth |
+| GetNetworkInterfaces | auth | auth | auth |
+| GetDeviceInformation | auth | auth | auth |
+| GetProfiles | auth | auth | auth |
+| GetScopes | auth | auth | auth |
+
+- Dahua - onvif://192.168.10.90:80
+- Reolink - onvif://192.168.10.92:8000
+- TP-Link - onvif://192.168.10.91:2020/onvif/device_service
+-
\ No newline at end of file
diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go
index 97bfd8dc..cb6221e1 100644
--- a/pkg/onvif/client.go
+++ b/pkg/onvif/client.go
@@ -2,8 +2,6 @@ package onvif
import (
"bytes"
- "crypto/sha1"
- "encoding/base64"
"errors"
"html"
"io"
@@ -12,8 +10,6 @@ import (
"regexp"
"strings"
"time"
-
- "github.com/AlexxIT/go2rtc/pkg/core"
)
const PathDevice = "/onvif/device_service"
@@ -41,7 +37,7 @@ func NewClient(rawURL string) (*Client, error) {
client.deviceURL = baseURL + u.Path
}
- b, err := client.GetCapabilities()
+ b, err := client.DeviceRequest(DeviceGetCapabilities)
if err != nil {
return nil, err
}
@@ -95,7 +91,7 @@ func (c *Client) GetURI() (string, error) {
}
func (c *Client) GetName() (string, error) {
- b, err := c.GetDeviceInformation()
+ b, err := c.DeviceRequest(DeviceGetDeviceInformation)
if err != nil {
return "", err
}
@@ -104,7 +100,7 @@ func (c *Client) GetName() (string, error) {
}
func (c *Client) GetProfilesTokens() ([]string, error) {
- b, err := c.GetProfiles()
+ b, err := c.MediaRequest(MediaGetProfiles)
if err != nil {
return nil, err
}
@@ -127,86 +123,53 @@ func (c *Client) HasSnapshots() bool {
return strings.Contains(string(b), `SnapshotUri="true"`)
}
-func (c *Client) GetCapabilities() ([]byte, error) {
+func (c *Client) GetProfile(token string) ([]byte, error) {
return c.Request(
- c.deviceURL,
- `
- All
-`,
+ c.mediaURL, ``+token+``,
)
}
-func (c *Client) GetNetworkInterfaces() ([]byte, error) {
- return c.Request(
- c.deviceURL, ``,
- )
-}
-
-func (c *Client) GetDeviceInformation() ([]byte, error) {
- return c.Request(
- c.deviceURL, ``,
- )
-}
-
-func (c *Client) GetProfiles() ([]byte, error) {
- return c.Request(
- c.mediaURL, ``,
- )
+func (c *Client) GetVideoSourceConfiguration(token string) ([]byte, error) {
+ return c.Request(c.mediaURL, `
+ `+token+`
+`)
}
func (c *Client) GetStreamUri(token string) ([]byte, error) {
- return c.Request(
- c.mediaURL,
- `
+ return c.Request(c.mediaURL, `
RTP-Unicast
RTSP
`+token+`
-`,
- )
+`)
}
func (c *Client) GetSnapshotUri(token string) ([]byte, error) {
return c.Request(
- c.imaginURL,
- `
- `+token+`
-`,
- )
-}
-
-func (c *Client) GetSystemDateAndTime() ([]byte, error) {
- return c.Request(
- c.deviceURL, ``,
+ c.imaginURL, ``+token+``,
)
}
func (c *Client) GetServiceCapabilities() ([]byte, error) {
// some cameras answer GetServiceCapabilities for media only for path = "/onvif/media"
return c.Request(
- c.mediaURL, ``,
+ c.mediaURL, ``,
)
}
-func (c *Client) SystemReboot() ([]byte, error) {
- return c.Request(
- c.deviceURL, ``,
- )
+func (c *Client) DeviceRequest(operation string) ([]byte, error) {
+ if operation == DeviceGetServices {
+ operation = `true`
+ } else {
+ operation = ``
+ }
+ return c.Request(c.deviceURL, operation)
}
-func (c *Client) GetServices() ([]byte, error) {
- return c.Request(
- c.deviceURL, `
- true
-`,
- )
-}
-
-func (c *Client) GetScopes() ([]byte, error) {
- return c.Request(
- c.deviceURL, ``,
- )
+func (c *Client) MediaRequest(operation string) ([]byte, error) {
+ operation = ``
+ return c.Request(c.mediaURL, operation)
}
func (c *Client) Request(url, body string) ([]byte, error) {
@@ -214,35 +177,11 @@ func (c *Client) Request(url, body string) ([]byte, error) {
return nil, errors.New("onvif: unsupported service")
}
- buf := bytes.NewBuffer(nil)
- buf.WriteString(
- ``,
- )
-
- if user := c.url.User; user != nil {
- nonce := core.RandString(16, 36)
- created := time.Now().UTC().Format(time.RFC3339Nano)
- pass, _ := user.Password()
-
- h := sha1.New()
- h.Write([]byte(nonce + created + pass))
-
- buf.WriteString(`
-
-
-` + user.Username() + `
-` + base64.StdEncoding.EncodeToString(h.Sum(nil)) + `
-` + base64.StdEncoding.EncodeToString([]byte(nonce)) + `
-` + created + `
-
-
-`)
- }
-
- buf.WriteString(`` + body + ``)
+ e := NewEnvelopeWithUser(c.url.User)
+ e.Append(body)
client := &http.Client{Timeout: time.Second * 5000}
- res, err := client.Post(url, `application/soap+xml;charset=utf-8`, buf)
+ res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes()))
if err != nil {
return nil, err
}
diff --git a/pkg/onvif/envelope.go b/pkg/onvif/envelope.go
new file mode 100644
index 00000000..f0e1b29c
--- /dev/null
+++ b/pkg/onvif/envelope.go
@@ -0,0 +1,79 @@
+package onvif
+
+import (
+ "crypto/sha1"
+ "encoding/base64"
+ "fmt"
+ "net/url"
+ "time"
+
+ "github.com/AlexxIT/go2rtc/pkg/core"
+)
+
+type Envelope struct {
+ buf []byte
+}
+
+const (
+ prefix1 = `
+
+`
+ prefix2 = `
+`
+ suffix = `
+
+`
+)
+
+func NewEnvelope() *Envelope {
+ e := &Envelope{buf: make([]byte, 0, 1024)}
+ e.Append(prefix1, prefix2)
+ return e
+}
+
+func NewEnvelopeWithUser(user *url.Userinfo) *Envelope {
+ if user == nil {
+ return NewEnvelope()
+ }
+
+ nonce := core.RandString(16, 36)
+ created := time.Now().UTC().Format(time.RFC3339Nano)
+ pass, _ := user.Password()
+
+ h := sha1.New()
+ h.Write([]byte(nonce + created + pass))
+
+ e := &Envelope{buf: make([]byte, 0, 1024)}
+ e.Append(prefix1)
+ e.Appendf(`
+
+
+ %s
+ %s
+ %s
+ %s
+
+
+
+`,
+ user.Username(),
+ base64.StdEncoding.EncodeToString(h.Sum(nil)),
+ base64.StdEncoding.EncodeToString([]byte(nonce)),
+ created)
+ e.Append(prefix2)
+ return e
+}
+
+func (e *Envelope) Append(args ...string) {
+ for _, s := range args {
+ e.buf = append(e.buf, s...)
+ }
+}
+
+func (e *Envelope) Appendf(format string, args ...any) {
+ e.buf = fmt.Appendf(e.buf, format, args...)
+}
+
+func (e *Envelope) Bytes() []byte {
+ return append(e.buf, suffix...)
+}
diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go
index fc9c8392..251f4579 100644
--- a/pkg/onvif/helpers.go
+++ b/pkg/onvif/helpers.go
@@ -1,6 +1,7 @@
package onvif
import (
+ "fmt"
"net"
"regexp"
"strconv"
@@ -106,3 +107,25 @@ func atoi(s string) int {
}
return i
}
+
+func GetPosixTZ(current time.Time) string {
+ // Thanks to https://github.com/Path-Variable/go-posix-time
+ _, offset := current.Zone()
+
+ if current.IsDST() {
+ _, end := current.ZoneBounds()
+ endPlus1 := end.Add(time.Hour * 25)
+ _, offset = endPlus1.Zone()
+ }
+
+ var prefix string
+ if offset < 0 {
+ prefix = "GMT+"
+ offset = -offset / 60
+ } else {
+ prefix = "GMT-"
+ offset = offset / 60
+ }
+
+ return prefix + fmt.Sprintf("%02d:%02d", offset/60, offset%60)
+}
diff --git a/pkg/onvif/server.go b/pkg/onvif/server.go
index bc3f8ffe..42343d37 100644
--- a/pkg/onvif/server.go
+++ b/pkg/onvif/server.go
@@ -2,31 +2,40 @@ package onvif
import (
"bytes"
- "fmt"
"regexp"
- "strconv"
"time"
)
-const (
- ActionGetCapabilities = "GetCapabilities"
- ActionGetSystemDateAndTime = "GetSystemDateAndTime"
- ActionGetNetworkInterfaces = "GetNetworkInterfaces"
- ActionGetDeviceInformation = "GetDeviceInformation"
- ActionGetServiceCapabilities = "GetServiceCapabilities"
- ActionGetProfiles = "GetProfiles"
- ActionGetStreamUri = "GetStreamUri"
- ActionGetSnapshotUri = "GetSnapshotUri"
- ActionSystemReboot = "SystemReboot"
+const ServiceGetServiceCapabilities = "GetServiceCapabilities"
- ActionGetServices = "GetServices"
- ActionGetScopes = "GetScopes"
- ActionGetVideoSources = "GetVideoSources"
- ActionGetAudioSources = "GetAudioSources"
- ActionGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
- ActionGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
- ActionGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
- ActionGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
+const (
+ DeviceGetCapabilities = "GetCapabilities"
+ DeviceGetDeviceInformation = "GetDeviceInformation"
+ DeviceGetDiscoveryMode = "GetDiscoveryMode"
+ DeviceGetDNS = "GetDNS"
+ DeviceGetHostname = "GetHostname"
+ DeviceGetNetworkDefaultGateway = "GetNetworkDefaultGateway"
+ DeviceGetNetworkInterfaces = "GetNetworkInterfaces"
+ DeviceGetNetworkProtocols = "GetNetworkProtocols"
+ DeviceGetNTP = "GetNTP"
+ DeviceGetScopes = "GetScopes"
+ DeviceGetServices = "GetServices"
+ DeviceGetSystemDateAndTime = "GetSystemDateAndTime"
+ DeviceSystemReboot = "SystemReboot"
+)
+
+const (
+ MediaGetAudioEncoderConfigurations = "GetAudioEncoderConfigurations"
+ MediaGetAudioSources = "GetAudioSources"
+ MediaGetAudioSourceConfigurations = "GetAudioSourceConfigurations"
+ MediaGetProfile = "GetProfile"
+ MediaGetProfiles = "GetProfiles"
+ MediaGetSnapshotUri = "GetSnapshotUri"
+ MediaGetStreamUri = "GetStreamUri"
+ MediaGetVideoEncoderConfigurations = "GetVideoEncoderConfigurations"
+ MediaGetVideoSources = "GetVideoSources"
+ MediaGetVideoSourceConfiguration = "GetVideoSourceConfiguration"
+ MediaGetVideoSourceConfigurations = "GetVideoSourceConfigurations"
)
func GetRequestAction(b []byte) string {
@@ -43,236 +52,199 @@ func GetRequestAction(b []byte) string {
return string(m[1])
}
-func GetCapabilitiesResponse(host string) string {
- return `
-
-
-
-
-
- http://` + host + `/onvif/device_service
-
-
- http://` + host + `/onvif/media_service
-
- false
- false
- true
-
-
-
-
-
-`
+func GetCapabilitiesResponse(host string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+
+ http://`, host, `/onvif/device_service
+
+
+ http://`, host, `/onvif/media_service
+
+ false
+ false
+ true
+
+
+
+`)
+ return e.Bytes()
}
-func GetServicesResponse(host string) string {
- return `
-
-
-
-
- http://www.onvif.org/ver10/device/wsdl
- http://` + host + `/onvif/device_service
-
- 2
- 5
-
-
-
- http://www.onvif.org/ver10/media/wsdl
- http://` + host + `/onvif/media_service
-
- 2
- 5
-
-
-
-
-`
+func GetServicesResponse(host string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+ http://www.onvif.org/ver10/device/wsdl
+ http://`, host, `/onvif/device_service
+ 25
+
+
+ http://www.onvif.org/ver10/media/wsdl
+ http://`, host, `/onvif/media_service
+ 25
+
+`)
+ return e.Bytes()
}
-func GetSystemDateAndTimeResponse() string {
+func GetSystemDateAndTimeResponse() []byte {
loc := time.Now()
utc := loc.UTC()
- return fmt.Sprintf(`
-
-
-
-
- NTP
- false
-
- GMT%s
-
-
-
- %d
- %d
- %d
-
-
- %d
- %d
- %d
-
-
-
-
- %d
- %d
- %d
-
-
- %d
- %d
- %d
-
-
-
-
-
-`,
- loc.Format("-07:00"),
+ e := NewEnvelope()
+ e.Appendf(`
+
+ NTP
+ true
+
+ %s
+
+
+ %d%d%d
+ %d%d%d
+
+
+ %d%d%d
+ %d%d%d
+
+
+`,
+ GetPosixTZ(loc),
utc.Hour(), utc.Minute(), utc.Second(), utc.Year(), utc.Month(), utc.Day(),
loc.Hour(), loc.Minute(), loc.Second(), loc.Year(), loc.Month(), loc.Day(),
)
+ return e.Bytes()
}
-func GetNetworkInterfacesResponse() string {
- return `
-
-
-
-
-`
+func GetDeviceInformationResponse(manuf, model, firmware, serial string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+ `, manuf, `
+ `, model, `
+ `, firmware, `
+ `, serial, `
+ 1.00
+`)
+ return e.Bytes()
}
-func GetDeviceInformationResponse(manuf, model, firmware, serial string) string {
- return `
-
-
-
- ` + manuf + `
- ` + model + `
- ` + firmware + `
- ` + serial + `
- 1.00
-
-
-`
+func GetMediaServiceCapabilitiesResponse() []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+
+
+`)
+ return e.Bytes()
}
-func GetServiceCapabilitiesResponse() string {
- return `
-
-
-
-
-
-
-
-
-`
+func GetProfilesResponse(names []string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+`)
+ for _, name := range names {
+ appendProfile(e, "Profiles", name)
+ }
+ e.Append(``)
+ return e.Bytes()
}
-func SystemRebootResponse() string {
- return `
-
-
-
- system reboot in 1 second...
-
-
-`
+func GetProfileResponse(name string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+`)
+ appendProfile(e, "Profile", name)
+ e.Append(``)
+ return e.Bytes()
}
-func GetProfilesResponse(names []string) string {
- buf := bytes.NewBuffer(nil)
- buf.WriteString(`
-
-
- `)
+func appendProfile(e *Envelope, tag, name string) {
+ e.Append(`
+ `, name, `
+
+ VSC
+ `, name, `
+
+
+
+ VEC
+ H264
+ 19201080
+
+
+`)
+}
- for i, name := range names {
- buf.WriteString(`
-
- ` + name + `
-
- ` + name + `
- H264
-
- 1920
- 1080
-
-
-
-
-
- ` + name + `
- ` + strconv.Itoa(i) + `
-
-
- `)
+func GetVideoSourceConfigurationResponse(name string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+
+ VSC
+ `, name, `
+
+
+`)
+ return e.Bytes()
+}
+
+func GetVideoSourcesResponse(names []string) []byte {
+ e := NewEnvelope()
+ e.Append(`
+`)
+ for _, name := range names {
+ e.Append(`
+ 30.000000
+ 19201080
+
+`)
+ }
+ e.Append(``)
+ return e.Bytes()
+}
+
+func GetStreamUriResponse(uri string) []byte {
+ e := NewEnvelope()
+ e.Append(``, uri, ``)
+ return e.Bytes()
+}
+
+func GetSnapshotUriResponse(uri string) []byte {
+ e := NewEnvelope()
+ e.Append(``, uri, ``)
+ return e.Bytes()
+}
+
+func StaticResponse(operation string) []byte {
+ switch operation {
+ case DeviceGetSystemDateAndTime:
+ return GetSystemDateAndTimeResponse()
}
- buf.WriteString(`
-
-
-`)
-
- return buf.String()
-}
-
-
-func GetVideoSourcesResponse(names []string) string {
- buf := bytes.NewBuffer(nil)
- buf.WriteString(`
-
-
- `)
-
- for i, _ := range names {
- buf.WriteString(`
-
-
- 1920
- 1080
-
- `)
+ e := NewEnvelope()
+ e.Append(responses[operation])
+ b := e.Bytes()
+ if operation == DeviceGetNetworkInterfaces {
+ println()
}
-
- buf.WriteString(`
-
-
-`)
-
- return buf.String()
+ return b
}
-func GetStreamUriResponse(uri string) string {
- return `
-
-
-
-
- ` + uri + `
-
-
-
-`
-}
+var responses = map[string]string{
+ DeviceGetDiscoveryMode: `Discoverable`,
+ DeviceGetDNS: ``,
+ DeviceGetHostname: ``,
+ DeviceGetNetworkDefaultGateway: ``,
+ DeviceGetNTP: ``,
+ DeviceSystemReboot: `OK`,
-func GetSnapshotUriResponse(uri string) string {
- return `
-
-
-
-
- ` + uri + `
-
-
-
-`
+ DeviceGetNetworkInterfaces: ``,
+ DeviceGetNetworkProtocols: ``,
+ DeviceGetScopes: `
+ Fixedonvif://www.onvif.org/name/go2rtc
+ Fixedonvif://www.onvif.org/location/github
+ Fixedonvif://www.onvif.org/Profile/Streaming
+ Fixedonvif://www.onvif.org/type/Network_Video_Transmitter
+`,
}