Files
onvif-go/device.go
T

705 lines
20 KiB
Go

package onvif
import (
"context"
"encoding/xml"
"fmt"
"github.com/0x524a/onvif-go/internal/soap"
)
// Device service namespace
const deviceNamespace = "http://www.onvif.org/ver10/device/wsdl"
// GetDeviceInformation retrieves device information
func (c *Client) GetDeviceInformation(ctx context.Context) (*DeviceInformation, error) {
type GetDeviceInformation struct {
XMLName xml.Name `xml:"tds:GetDeviceInformation"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetDeviceInformationResponse struct {
XMLName xml.Name `xml:"GetDeviceInformationResponse"`
Manufacturer string `xml:"Manufacturer"`
Model string `xml:"Model"`
FirmwareVersion string `xml:"FirmwareVersion"`
SerialNumber string `xml:"SerialNumber"`
HardwareID string `xml:"HardwareId"`
}
req := GetDeviceInformation{
Xmlns: deviceNamespace,
}
var resp GetDeviceInformationResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetDeviceInformation failed: %w", err)
}
return &DeviceInformation{
Manufacturer: resp.Manufacturer,
Model: resp.Model,
FirmwareVersion: resp.FirmwareVersion,
SerialNumber: resp.SerialNumber,
HardwareID: resp.HardwareID,
}, nil
}
// GetCapabilities retrieves device capabilities
func (c *Client) GetCapabilities(ctx context.Context) (*Capabilities, error) {
type GetCapabilities struct {
XMLName xml.Name `xml:"tds:GetCapabilities"`
Xmlns string `xml:"xmlns:tds,attr"`
Category []string `xml:"tds:Category,omitempty"`
}
type GetCapabilitiesResponse struct {
XMLName xml.Name `xml:"GetCapabilitiesResponse"`
Capabilities struct {
Analytics *struct {
XAddr string `xml:"XAddr"`
RuleSupport bool `xml:"RuleSupport"`
AnalyticsModuleSupport bool `xml:"AnalyticsModuleSupport"`
} `xml:"Analytics"`
Device *struct {
XAddr string `xml:"XAddr"`
Network *struct {
IPFilter bool `xml:"IPFilter"`
ZeroConfiguration bool `xml:"ZeroConfiguration"`
IPVersion6 bool `xml:"IPVersion6"`
DynDNS bool `xml:"DynDNS"`
} `xml:"Network"`
System *struct {
DiscoveryResolve bool `xml:"DiscoveryResolve"`
DiscoveryBye bool `xml:"DiscoveryBye"`
RemoteDiscovery bool `xml:"RemoteDiscovery"`
SystemBackup bool `xml:"SystemBackup"`
SystemLogging bool `xml:"SystemLogging"`
FirmwareUpgrade bool `xml:"FirmwareUpgrade"`
SupportedVersions []string `xml:"SupportedVersions>Major"`
} `xml:"System"`
IO *struct {
InputConnectors int `xml:"InputConnectors"`
RelayOutputs int `xml:"RelayOutputs"`
} `xml:"IO"`
Security *struct {
TLS11 bool `xml:"TLS1.1"`
TLS12 bool `xml:"TLS1.2"`
OnboardKeyGeneration bool `xml:"OnboardKeyGeneration"`
AccessPolicyConfig bool `xml:"AccessPolicyConfig"`
X509Token bool `xml:"X.509Token"`
SAMLToken bool `xml:"SAMLToken"`
KerberosToken bool `xml:"KerberosToken"`
RELToken bool `xml:"RELToken"`
} `xml:"Security"`
} `xml:"Device"`
Events *struct {
XAddr string `xml:"XAddr"`
WSSubscriptionPolicySupport bool `xml:"WSSubscriptionPolicySupport"`
WSPullPointSupport bool `xml:"WSPullPointSupport"`
WSPausableSubscriptionSupport bool `xml:"WSPausableSubscriptionManagerInterfaceSupport"`
} `xml:"Events"`
Imaging *struct {
XAddr string `xml:"XAddr"`
} `xml:"Imaging"`
Media *struct {
XAddr string `xml:"XAddr"`
StreamingCapabilities *struct {
RTPMulticast bool `xml:"RTPMulticast"`
RTP_TCP bool `xml:"RTP_TCP"`
RTP_RTSP_TCP bool `xml:"RTP_RTSP_TCP"`
} `xml:"StreamingCapabilities"`
} `xml:"Media"`
PTZ *struct {
XAddr string `xml:"XAddr"`
} `xml:"PTZ"`
} `xml:"Capabilities"`
}
req := GetCapabilities{
Xmlns: deviceNamespace,
Category: []string{"All"},
}
var resp GetCapabilitiesResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetCapabilities failed: %w", err)
}
capabilities := &Capabilities{}
// Map Analytics
if resp.Capabilities.Analytics != nil {
capabilities.Analytics = &AnalyticsCapabilities{
XAddr: resp.Capabilities.Analytics.XAddr,
RuleSupport: resp.Capabilities.Analytics.RuleSupport,
AnalyticsModuleSupport: resp.Capabilities.Analytics.AnalyticsModuleSupport,
}
}
// Map Device
if resp.Capabilities.Device != nil {
capabilities.Device = &DeviceCapabilities{
XAddr: resp.Capabilities.Device.XAddr,
}
if resp.Capabilities.Device.Network != nil {
capabilities.Device.Network = &NetworkCapabilities{
IPFilter: resp.Capabilities.Device.Network.IPFilter,
ZeroConfiguration: resp.Capabilities.Device.Network.ZeroConfiguration,
IPVersion6: resp.Capabilities.Device.Network.IPVersion6,
DynDNS: resp.Capabilities.Device.Network.DynDNS,
}
}
if resp.Capabilities.Device.System != nil {
capabilities.Device.System = &SystemCapabilities{
DiscoveryResolve: resp.Capabilities.Device.System.DiscoveryResolve,
DiscoveryBye: resp.Capabilities.Device.System.DiscoveryBye,
RemoteDiscovery: resp.Capabilities.Device.System.RemoteDiscovery,
SystemBackup: resp.Capabilities.Device.System.SystemBackup,
SystemLogging: resp.Capabilities.Device.System.SystemLogging,
FirmwareUpgrade: resp.Capabilities.Device.System.FirmwareUpgrade,
SupportedVersions: resp.Capabilities.Device.System.SupportedVersions,
}
}
if resp.Capabilities.Device.IO != nil {
capabilities.Device.IO = &IOCapabilities{
InputConnectors: resp.Capabilities.Device.IO.InputConnectors,
RelayOutputs: resp.Capabilities.Device.IO.RelayOutputs,
}
}
if resp.Capabilities.Device.Security != nil {
capabilities.Device.Security = &SecurityCapabilities{
TLS11: resp.Capabilities.Device.Security.TLS11,
TLS12: resp.Capabilities.Device.Security.TLS12,
OnboardKeyGeneration: resp.Capabilities.Device.Security.OnboardKeyGeneration,
AccessPolicyConfig: resp.Capabilities.Device.Security.AccessPolicyConfig,
X509Token: resp.Capabilities.Device.Security.X509Token,
SAMLToken: resp.Capabilities.Device.Security.SAMLToken,
KerberosToken: resp.Capabilities.Device.Security.KerberosToken,
RELToken: resp.Capabilities.Device.Security.RELToken,
}
}
}
// Map Events
if resp.Capabilities.Events != nil {
capabilities.Events = &EventCapabilities{
XAddr: resp.Capabilities.Events.XAddr,
WSSubscriptionPolicySupport: resp.Capabilities.Events.WSSubscriptionPolicySupport,
WSPullPointSupport: resp.Capabilities.Events.WSPullPointSupport,
WSPausableSubscriptionSupport: resp.Capabilities.Events.WSPausableSubscriptionSupport,
}
}
// Map Imaging
if resp.Capabilities.Imaging != nil {
capabilities.Imaging = &ImagingCapabilities{
XAddr: resp.Capabilities.Imaging.XAddr,
}
}
// Map Media
if resp.Capabilities.Media != nil {
capabilities.Media = &MediaCapabilities{
XAddr: resp.Capabilities.Media.XAddr,
}
if resp.Capabilities.Media.StreamingCapabilities != nil {
capabilities.Media.StreamingCapabilities = &StreamingCapabilities{
RTPMulticast: resp.Capabilities.Media.StreamingCapabilities.RTPMulticast,
RTP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_TCP,
RTP_RTSP_TCP: resp.Capabilities.Media.StreamingCapabilities.RTP_RTSP_TCP,
}
}
}
// Map PTZ
if resp.Capabilities.PTZ != nil {
capabilities.PTZ = &PTZCapabilities{
XAddr: resp.Capabilities.PTZ.XAddr,
}
}
return capabilities, nil
}
// SystemReboot reboots the device
func (c *Client) SystemReboot(ctx context.Context) (string, error) {
type SystemReboot struct {
XMLName xml.Name `xml:"tds:SystemReboot"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type SystemRebootResponse struct {
XMLName xml.Name `xml:"SystemRebootResponse"`
Message string `xml:"Message"`
}
req := SystemReboot{
Xmlns: deviceNamespace,
}
var resp SystemRebootResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return "", fmt.Errorf("SystemReboot failed: %w", err)
}
return resp.Message, nil
}
// GetSystemDateAndTime retrieves the device's system date and time
func (c *Client) GetSystemDateAndTime(ctx context.Context) (interface{}, error) {
type GetSystemDateAndTime struct {
XMLName xml.Name `xml:"tds:GetSystemDateAndTime"`
Xmlns string `xml:"xmlns:tds,attr"`
}
req := GetSystemDateAndTime{
Xmlns: deviceNamespace,
}
var resp interface{}
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetSystemDateAndTime failed: %w", err)
}
return resp, nil
}
// GetHostname retrieves the device's hostname
func (c *Client) GetHostname(ctx context.Context) (*HostnameInformation, error) {
type GetHostname struct {
XMLName xml.Name `xml:"tds:GetHostname"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetHostnameResponse struct {
XMLName xml.Name `xml:"GetHostnameResponse"`
HostnameInformation struct {
FromDHCP bool `xml:"FromDHCP"`
Name string `xml:"Name"`
} `xml:"HostnameInformation"`
}
req := GetHostname{
Xmlns: deviceNamespace,
}
var resp GetHostnameResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetHostname failed: %w", err)
}
return &HostnameInformation{
FromDHCP: resp.HostnameInformation.FromDHCP,
Name: resp.HostnameInformation.Name,
}, nil
}
// SetHostname sets the device's hostname
func (c *Client) SetHostname(ctx context.Context, name string) error {
type SetHostname struct {
XMLName xml.Name `xml:"tds:SetHostname"`
Xmlns string `xml:"xmlns:tds,attr"`
Name string `xml:"tds:Name"`
}
req := SetHostname{
Xmlns: deviceNamespace,
Name: name,
}
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetHostname failed: %w", err)
}
return nil
}
// GetDNS retrieves DNS configuration
func (c *Client) GetDNS(ctx context.Context) (*DNSInformation, error) {
type GetDNS struct {
XMLName xml.Name `xml:"tds:GetDNS"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetDNSResponse struct {
XMLName xml.Name `xml:"GetDNSResponse"`
DNSInformation struct {
FromDHCP bool `xml:"FromDHCP"`
SearchDomain []string `xml:"SearchDomain"`
DNSFromDHCP []struct {
Type string `xml:"Type"`
IPv4Address string `xml:"IPv4Address"`
} `xml:"DNSFromDHCP"`
DNSManual []struct {
Type string `xml:"Type"`
IPv4Address string `xml:"IPv4Address"`
} `xml:"DNSManual"`
} `xml:"DNSInformation"`
}
req := GetDNS{
Xmlns: deviceNamespace,
}
var resp GetDNSResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetDNS failed: %w", err)
}
dns := &DNSInformation{
FromDHCP: resp.DNSInformation.FromDHCP,
SearchDomain: resp.DNSInformation.SearchDomain,
}
for _, d := range resp.DNSInformation.DNSFromDHCP {
dns.DNSFromDHCP = append(dns.DNSFromDHCP, IPAddress{
Type: d.Type,
IPv4Address: d.IPv4Address,
})
}
for _, d := range resp.DNSInformation.DNSManual {
dns.DNSManual = append(dns.DNSManual, IPAddress{
Type: d.Type,
IPv4Address: d.IPv4Address,
})
}
return dns, nil
}
// GetNTP retrieves NTP configuration
func (c *Client) GetNTP(ctx context.Context) (*NTPInformation, error) {
type GetNTP struct {
XMLName xml.Name `xml:"tds:GetNTP"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetNTPResponse struct {
XMLName xml.Name `xml:"GetNTPResponse"`
NTPInformation struct {
FromDHCP bool `xml:"FromDHCP"`
NTPFromDHCP []struct {
Type string `xml:"Type"`
IPv4Address string `xml:"IPv4Address"`
DNSname string `xml:"DNSname"`
} `xml:"NTPFromDHCP"`
NTPManual []struct {
Type string `xml:"Type"`
IPv4Address string `xml:"IPv4Address"`
DNSname string `xml:"DNSname"`
} `xml:"NTPManual"`
} `xml:"NTPInformation"`
}
req := GetNTP{
Xmlns: deviceNamespace,
}
var resp GetNTPResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetNTP failed: %w", err)
}
ntp := &NTPInformation{
FromDHCP: resp.NTPInformation.FromDHCP,
}
for _, n := range resp.NTPInformation.NTPFromDHCP {
ntp.NTPFromDHCP = append(ntp.NTPFromDHCP, NetworkHost{
Type: n.Type,
IPv4Address: n.IPv4Address,
DNSname: n.DNSname,
})
}
for _, n := range resp.NTPInformation.NTPManual {
ntp.NTPManual = append(ntp.NTPManual, NetworkHost{
Type: n.Type,
IPv4Address: n.IPv4Address,
DNSname: n.DNSname,
})
}
return ntp, nil
}
// GetNetworkInterfaces retrieves network interface configuration
func (c *Client) GetNetworkInterfaces(ctx context.Context) ([]*NetworkInterface, error) {
type GetNetworkInterfaces struct {
XMLName xml.Name `xml:"tds:GetNetworkInterfaces"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetNetworkInterfacesResponse struct {
XMLName xml.Name `xml:"GetNetworkInterfacesResponse"`
NetworkInterfaces []struct {
Token string `xml:"token,attr"`
Enabled bool `xml:"Enabled"`
Info struct {
Name string `xml:"Name"`
HwAddress string `xml:"HwAddress"`
MTU int `xml:"MTU"`
} `xml:"Info"`
IPv4 struct {
Enabled bool `xml:"Enabled"`
Config struct {
Manual []struct {
Address string `xml:"Address"`
PrefixLength int `xml:"PrefixLength"`
} `xml:"Manual"`
DHCP bool `xml:"DHCP"`
} `xml:"Config"`
} `xml:"IPv4"`
} `xml:"NetworkInterfaces"`
}
req := GetNetworkInterfaces{
Xmlns: deviceNamespace,
}
var resp GetNetworkInterfacesResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetNetworkInterfaces failed: %w", err)
}
interfaces := make([]*NetworkInterface, len(resp.NetworkInterfaces))
for i, iface := range resp.NetworkInterfaces {
ni := &NetworkInterface{
Token: iface.Token,
Enabled: iface.Enabled,
Info: NetworkInterfaceInfo{
Name: iface.Info.Name,
HwAddress: iface.Info.HwAddress,
MTU: iface.Info.MTU,
},
}
if iface.IPv4.Enabled {
ni.IPv4 = &IPv4NetworkInterface{
Enabled: iface.IPv4.Enabled,
Config: IPv4Configuration{
DHCP: iface.IPv4.Config.DHCP,
},
}
for _, m := range iface.IPv4.Config.Manual {
ni.IPv4.Config.Manual = append(ni.IPv4.Config.Manual, PrefixedIPv4Address{
Address: m.Address,
PrefixLength: m.PrefixLength,
})
}
}
interfaces[i] = ni
}
return interfaces, nil
}
// GetScopes retrieves configured scopes
func (c *Client) GetScopes(ctx context.Context) ([]*Scope, error) {
type GetScopes struct {
XMLName xml.Name `xml:"tds:GetScopes"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetScopesResponse struct {
XMLName xml.Name `xml:"GetScopesResponse"`
Scopes []struct {
ScopeDef string `xml:"ScopeDef"`
ScopeItem string `xml:"ScopeItem"`
} `xml:"Scopes"`
}
req := GetScopes{
Xmlns: deviceNamespace,
}
var resp GetScopesResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetScopes failed: %w", err)
}
scopes := make([]*Scope, len(resp.Scopes))
for i, s := range resp.Scopes {
scopes[i] = &Scope{
ScopeDef: s.ScopeDef,
ScopeItem: s.ScopeItem,
}
}
return scopes, nil
}
// GetUsers retrieves user accounts
func (c *Client) GetUsers(ctx context.Context) ([]*User, error) {
type GetUsers struct {
XMLName xml.Name `xml:"tds:GetUsers"`
Xmlns string `xml:"xmlns:tds,attr"`
}
type GetUsersResponse struct {
XMLName xml.Name `xml:"GetUsersResponse"`
User []struct {
Username string `xml:"Username"`
UserLevel string `xml:"UserLevel"`
} `xml:"User"`
}
req := GetUsers{
Xmlns: deviceNamespace,
}
var resp GetUsersResponse
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, &resp); err != nil {
return nil, fmt.Errorf("GetUsers failed: %w", err)
}
users := make([]*User, len(resp.User))
for i, u := range resp.User {
users[i] = &User{
Username: u.Username,
UserLevel: u.UserLevel,
}
}
return users, nil
}
// CreateUsers creates new user accounts
func (c *Client) CreateUsers(ctx context.Context, users []*User) error {
type CreateUsers struct {
XMLName xml.Name `xml:"tds:CreateUsers"`
Xmlns string `xml:"xmlns:tds,attr"`
User []struct {
Username string `xml:"tds:Username"`
Password string `xml:"tds:Password"`
UserLevel string `xml:"tds:UserLevel"`
} `xml:"tds:User"`
}
req := CreateUsers{
Xmlns: deviceNamespace,
}
for _, user := range users {
req.User = append(req.User, struct {
Username string `xml:"tds:Username"`
Password string `xml:"tds:Password"`
UserLevel string `xml:"tds:UserLevel"`
}{
Username: user.Username,
Password: user.Password,
UserLevel: user.UserLevel,
})
}
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("CreateUsers failed: %w", err)
}
return nil
}
// DeleteUsers deletes user accounts
func (c *Client) DeleteUsers(ctx context.Context, usernames []string) error {
type DeleteUsers struct {
XMLName xml.Name `xml:"tds:DeleteUsers"`
Xmlns string `xml:"xmlns:tds,attr"`
Username []string `xml:"tds:Username"`
}
req := DeleteUsers{
Xmlns: deviceNamespace,
Username: usernames,
}
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("DeleteUsers failed: %w", err)
}
return nil
}
// SetUser modifies an existing user account
func (c *Client) SetUser(ctx context.Context, user *User) error {
type SetUser struct {
XMLName xml.Name `xml:"tds:SetUser"`
Xmlns string `xml:"xmlns:tds,attr"`
User struct {
Username string `xml:"tds:Username"`
Password *string `xml:"tds:Password,omitempty"`
UserLevel string `xml:"tds:UserLevel"`
} `xml:"tds:User"`
}
req := SetUser{
Xmlns: deviceNamespace,
}
req.User.Username = user.Username
if user.Password != "" {
req.User.Password = &user.Password
}
req.User.UserLevel = user.UserLevel
username, password := c.GetCredentials()
soapClient := soap.NewClient(c.httpClient, username, password)
if err := soapClient.Call(ctx, c.endpoint, "", req, nil); err != nil {
return fmt.Errorf("SetUser failed: %w", err)
}
return nil
}