onvif

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 21, 2022 License: MIT Imports: 9 Imported by: 0

README

pkg.go.dev

About

When trying to use Go to talk to ONVIF IP cameras, I became frustrated with existing ONVIF or SOAP libraries. Some had poor documentation and some were too highly abstracted to use easily. XML, SOAP, and ONVIF are frustrating enough by themselves, because of the many (incomplete, varying, bad) implementations out there, but ultimately it's just POSTing some XML structures to a service URL.

This library is mostly a small SOAP wrapper built on top of net/http.Client with some ONVIF helper functions. There is as little magic as possible. There's only a few types, and requests and responses are easily inspected (client.Debug).

It's not a project goal to have pre-built types for all the ONVIF types; instead the user should construct the types they need (gowsdl can be a useful tool to do that).

pkg.go.dev has a full example.

API Stability

I don't expect the API to change at this point, but because it's fairly new, it won't be tagged with v1.x.x until the library gets more usage.

Interacting with an ONVIF device

Generally, you want to interact with an ONVIF device by finding out what services it supports (and what the service URLs are):

c := &onvif.Client{}
services, err := c.GetServices("192.168.0.64:80")
if err != nil {
    // handle err
    panic(err)
}

Next you can verify the device has a specific service by checking if it supports that service namespace:

// check if media service is supported
mediaURL := services.URL(onvif.NamespaceMedia)
if mediaURL == "" {
    // handle service not being supported
}

Next you can create an onvif.Request using the namespace, URL, and an ONVIF request type (see Creating Types section below), and execute it with onvif.client.Do. The response body can be Unmarshaled into an ONVIF response type (see Creating Types section below) with soap.Body.Unmarshal.

See a full example on pkg.go.dev.

Creating Types

If you're not familiar with SOAP/XML, creating Go types to marshal/unmarshal ONVIF types can be frustrating, because you get to deal with XML namespaces and prefixes. There's a few issues with Go's handling of XML namespaces in encoding/xml (most of which are outlined here), so we have to be careful of how types are constructed. We'll take a look at GetCapabilities and GetCapabilitiesResponse as an example:

// GetCapabilities is an ONVIF GetCapabilities operation
type GetCapabilities struct {
    XMLName  xml.Name `xml:"tds:GetCapabilities"`
    Category string   `xml:"tds:Category"`
}

You can see that the request type, GetCapabilities, has the tds prefix in the xml tags. This is because SOAP servers usually expect a prefix on tags. It doesn't matter what the prefix as, as long as it matches the correct namespace in the Request:

req := &Request{
    URL:        deviceURL,
    Namespaces: soap.Namespaces{"tds": onvif.NamespaceDevice},
    Body:       &GetCapabilities{Category: "All"},
}

The response type, GetCapabilitiesResponse, does not have any prefixes:

// GetCapabilitiesResponse is an ONVIF GetServicesResponse response
type GetCapabilitiesResponse struct {
    DeviceURL          string `xml:"Capabilities>Device>XAddr"`
    EventsURL          string `xml:"Capabilities>Events>XAddr"`
    ImagingURL         string `xml:"Capabilities>Imaging>XAddr"`
    MediaURL           string `xml:"Capabilities>Media>XAddr"`
    PTZURL             string `xml:"Capabilities>PTZ>XAddr"`
    DeviceIOURL        string `xml:"Capabilities>Extension>DeviceIO>XAddr"`
    DisplayURL         string `xml:"Capabilities>Extension>Display>XAddr"`
    RecordingURL       string `xml:"Capabilities>Extension>Recording>XAddr"`
    SearchURL          string `xml:"Capabilities>Extension>Search>XAddr"`
    ReplayURL          string `xml:"Capabilities>Extension>Replay>XAddr"`
    ReceiverURL        string `xml:"Capabilities>Extension>Receiver>XAddr"`
    AnalyticsDeviceURL string `xml:"Capabilities>Extension>AnalyticsDevice>XAddr"`
}

This is because of the current Go parser issues of matching tags with prefixes. Instead you should just use the tag name by itself.

Note: the > characters in the xml tag are a feature of xml/encoding unmarshaling. They allow you to select sub elements at the root struct instead of creating several layers of structs to get to the field. Don't confuse them as being a special syntax needed for ONVIF response types.

Documentation

Overview

package onvif aims to be a simple to use, idiomatic ONVIF client library.

Example

Example of getting all video modes from a camera

package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"strings"

	"github.com/korylprince/go-onvif"
	"github.com/korylprince/go-onvif/soap"
)

// GetVideoSources is an ONVIF GetVideoSources operation
type GetVideoSources struct {
	XMLName xml.Name `xml:"trt:GetVideoSources"`
}

// GetVideoSourcesResponse is an ONVIF GetVideoSourcesResponse response
type GetVideoSourcesResponse struct {
	VideoSources []*struct {
		Token     string `xml:"token,attr"`
		Framerate float64
		Width     int `xml:"Resolution>Width"`
		Height    int `xml:"Resolution>Height"`
	}
}

// StringList is an XML StringList type
type StringList []string

func (sl *StringList) UnmarshalText(text []byte) error {
	for _, b := range bytes.Split(text, []byte(" ")) {
		*sl = append(*sl, string(b))
	}
	return nil
}

// GetVideoSourceModes is an ONVIF GetVideoSourceModes operation
type GetVideoSourceModes struct {
	XMLName xml.Name `xml:"trt:GetVideoSourceModes"`
	Token   string   `xml:"trt:VideoSourceToken"`
}

// GetVideoSourceModesResponse is an ONVIF GetVideoSourceModesResponse response
type GetVideoSourceModesResponse struct {
	VideoSourceModes []*struct {
		Token        string `xml:"token,attr"`
		MaxFramerate float64
		MaxWidth     int `xml:"MaxResolution>Width"`
		MaxHeight    int `xml:"MaxResolution>Height"`
		Encodings    StringList
	}
}

// Example of getting all video modes from a camera
func main() {
	c := &onvif.Client{Username: "admin", Password: "12345"}
	services, err := c.GetServices("192.168.0.64:80")
	if err != nil {
		// handle err
		panic(err)
	}

	var (
		mediaNS  string
		mediaURL string
	)

	// check if media service is supported
	if s := services.URL(onvif.NamespaceMedia); s != "" {
		mediaNS = onvif.NamespaceMedia
		mediaURL = s
	}

	if mediaNS == "" {
		// handle media service not supported
		panic("media service not supported")
	}

	r := &onvif.Request{
		URL:        mediaURL,
		Namespaces: soap.Namespaces{"trt": mediaNS},
		Body:       &GetVideoSources{},
	}

	env, err := c.Do(r)
	if err != nil {
		// handle err
		panic(err)
	}

	resp := new(GetVideoSourcesResponse)
	if err := env.Body.Unmarshal(resp); err != nil {
		// handle err
		panic(err)
	}

	// check if media ver20 service is supported so newer encodings will be returned
	if s := services.URL(onvif.NamespaceMedia2); s != "" {
		mediaNS = onvif.NamespaceMedia2
		mediaURL = s
	}

	for _, source := range resp.VideoSources {
		fmt.Printf("Found video source \"%s\": (%dx%d@%0.2f)\n", source.Token, source.Width, source.Height, source.Framerate)

		r := &onvif.Request{
			URL:        mediaURL,
			Namespaces: soap.Namespaces{"trt": mediaNS},
			Body:       &GetVideoSourceModes{Token: source.Token},
		}

		env, err := c.Do(r)
		if err != nil {
			// handle err
			panic(err)
		}

		resp := new(GetVideoSourceModesResponse)
		if err := env.Body.Unmarshal(resp); err != nil {
			// handle err
			panic(err)
		}
		for _, mode := range resp.VideoSourceModes {
			fmt.Printf("\tFound mode \"%s\": (%dx%d@%0.2f); Codecs: %s\n", mode.Token, mode.MaxWidth, mode.MaxHeight, mode.MaxFramerate, strings.Join(mode.Encodings, ", "))
		}
	}

}
Output:

Found video source "VideoSource": (3096x2202@30.00)
		Found mode "5M_1x1_FISHEYE": (2192x2192@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_16x9_PANORAMA": (1920x1080@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_16x9_WPANORAMA": (1920x1080@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_QUAD_CEILING": (1600x1200@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_QUAD_WALL": (1600x1200@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_SINGLE_CEILING": (1600x1200@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_SINGLE_WALL": (1600x1200@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_PANORAMA": (2192x2192@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_WPANORAMA": (2192x2192@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_QUAD": (2192x2192@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_SINGLE": (2192x2192@30.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_4STREAM": (1600x1200@30.00); Codecs: H264, H265
		Found mode "5M_1x1_FISHEYE_25fps": (2192x2192@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_16x9_PANORAMA_25fps": (1920x1080@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_16x9_WPANORAMA_25fps": (1920x1080@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_QUAD_CEILING_25fps": (1600x1200@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_QUAD_WALL_25fps": (1600x1200@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_SINGLE_CEILING_25fps": (1600x1200@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_PTZ_SINGLE_WALL_25fps": (1600x1200@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_PANORAMA_25fps": (2192x2192@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_WPANORAMA_25fps": (2192x2192@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_QUAD_25fps": (2192x2192@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_FISHEYE_SINGLE_25fps": (2192x2192@25.00); Codecs: JPEG, H264, H265
		Found mode "5M_4x3_4STREAM_25fps": (1600x1200@25.00); Codecs: H264, H265

Index

Examples

Constants

View Source
const (
	NamespaceONVIF  = "http://www.onvif.org/ver10/schema"
	NamespaceDevice = "http://www.onvif.org/ver10/device/wsdl"
	NamespaceEvents = "http://www.onvif.org/ver10/events/wsdl"

	NamespaceAccessControl          = "http://www.onvif.org/ver10/accesscontrol/wsdl"
	NamespaceAccessRules            = "http://www.onvif.org/ver10/accessrules/wsdl"
	NamespaceActionEngine           = "http://www.onvif.org/ver10/actionengine/wsdl"
	NamespaceAdvancedSecurity       = "http://www.onvif.org/ver10/advancedsecurity/wsdl"
	NamespaceAnalytics              = "http://www.onvif.org/ver20/analytics/wsdl"
	NamespaceAnalyticsDevice        = "http://www.onvif.org/ver10/analyticsdevice/wsdl"
	NamespaceAppMgmt                = "http://www.onvif.org/ver10/appmgmt/wsdl"
	NamespaceAuthenticationBehavior = "http://www.onvif.org/ver10/authenticationbehavior/wsdl"
	NamespaceCredential             = "http://www.onvif.org/ver10/credential/wsdl"
	NamespaceDeviceIO               = "http://www.onvif.org/ver10/deviceIO/wsdl"
	NamespaceDisplay                = "http://www.onvif.org/ver10/display/wsdl"
	NamespaceDoorControl            = "http://www.onvif.org/ver10/doorcontrol/wsdl"
	NamespaceFederatedSearch        = "http://www.onvif.org/ver10/federatedsearch/wsdl"
	NamespaceImaging                = "http://www.onvif.org/ver20/imaging/wsdl"
	NamespaceMedia                  = "http://www.onvif.org/ver10/media/wsdl"
	NamespaceMedia2                 = "http://www.onvif.org/ver20/media/wsdl"
	NamespacePTZ                    = "http://www.onvif.org/ver20/ptz/wsdl"
	NamespaceProvisioning           = "http://www.onvif.org/ver10/provisioning/wsdl"
	NamespaceReceiver               = "http://www.onvif.org/ver10/receiver/wsdl"
	NamespaceRecording              = "http://www.onvif.org/ver10/recording/wsdl"
	NamespaceReplay                 = "http://www.onvif.org/ver10/replay/wsdl"
	NamespaceSchedule               = "http://www.onvif.org/ver10/schedule/wsdl"
	NamespaceSearch                 = "http://www.onvif.org/ver10/search/wsdl"
	NamespaceThermal                = "http://www.onvif.org/ver10/thermal/wsdl"
	NamespaceUplink                 = "http://www.onvif.org/ver10/uplink/wsdl"
)

ONVIF Namespaces

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthMode added in v0.1.3

type AuthMode int

AuthMode represents an ONVIF request authentication mode. See Client.AuthMode for more information

const (
	AuthModeNone AuthMode = iota
	AuthModeDigest
	AuthModeWSSecurity
)

ONVIF authentication modes

type Client

type Client struct {
	// AuthMode specifies which authentication mode to use to authenticate requests.
	// If set to AuthModeNone (the default value), the Client will not use authentication unless an authorization error occurs.
	// In that case, if Username and Password are set, the Client will attempt to detect the correct AuthMode,
	// update Client.AuthMode, and authenticate all future requests.
	// If AuthMode is set to AuthModeWSSecurity and an HTTP 401 response is returned (indicated WS Security tokens are not supported),
	// AuthMode will be set to AuthModeDigest.
	AuthMode
	Username string
	Password string
	// HTTPClient is the *http.Client to use for the request. If nil, http.DefaultClient is used
	HTTPClient *http.Client
	// If Debug is true, the client will print the full request and response to stdout
	Debug bool
}

Client is an ONVIF client

func (*Client) Do

func (c *Client) Do(r *Request) (*soap.Envelope, error)

Do executes a SOAP request. The response envelope is returned, which can be further unmarshaled with soap.Body.Unmarshal If the device returns a *soap.Fault, it will be returned as an error

func (*Client) GetCapabilities

func (c *Client) GetCapabilities(addr string) (Services, error)

GetCapabilities returns the service urls from the remote device. Most users should use GetServices instead. addr is the host:port pair of the device. Just the host part can be specified as well.

func (*Client) GetServices

func (c *Client) GetServices(addr string) (Services, error)

GetServices returns the service urls from the remote device. addr is the host:port pair of the device. Just the host part can be specified as well.

type GetCapabilities

type GetCapabilities struct {
	XMLName  xml.Name `xml:"tds:GetCapabilities"`
	Category string   `xml:"tds:Category"`
}

GetCapabilities is an ONVIF GetCapabilities operation

type GetCapabilitiesResponse

type GetCapabilitiesResponse struct {
	DeviceURL          string `xml:"Capabilities>Device>XAddr"`
	EventsURL          string `xml:"Capabilities>Events>XAddr"`
	ImagingURL         string `xml:"Capabilities>Imaging>XAddr"`
	MediaURL           string `xml:"Capabilities>Media>XAddr"`
	PTZURL             string `xml:"Capabilities>PTZ>XAddr"`
	DeviceIOURL        string `xml:"Capabilities>Extension>DeviceIO>XAddr"`
	DisplayURL         string `xml:"Capabilities>Extension>Display>XAddr"`
	RecordingURL       string `xml:"Capabilities>Extension>Recording>XAddr"`
	SearchURL          string `xml:"Capabilities>Extension>Search>XAddr"`
	ReplayURL          string `xml:"Capabilities>Extension>Replay>XAddr"`
	ReceiverURL        string `xml:"Capabilities>Extension>Receiver>XAddr"`
	AnalyticsDeviceURL string `xml:"Capabilities>Extension>AnalyticsDevice>XAddr"`
}

GetCapabilitiesResponse is an ONVIF GetServicesResponse response

type GetServices

type GetServices struct {
	XMLName           xml.Name `xml:"tds:GetServices"`
	IncludeCapability bool     `xml:"tds:IncludeCapability"`
}

GetServices is an ONVIF GetServices operation

type GetServicesResponse

type GetServicesResponse struct {
	Service Services
}

GetServicesResponse is an ONVIF GetServicesResponse response

type Request

type Request struct {
	URL string
	// Namespaces will be added to the SOAP envelope
	Namespaces soap.Namespaces
	// Body will be marshaled to XML as the SOAP body contents
	Body interface{}
}

Request is a SOAP request

type Service

type Service struct {
	Namespace    string
	URL          string `xml:"XAddr"`
	VersionMajor int    `xml:"Version>Major"`
	VersionMinor int    `xml:"Version>Minor"`
}

Service represents an ONVIF service

type Services

type Services []*Service

Services is a list of Services

func (Services) URL

func (s Services) URL(namespace string) string

URL returns the service URL for the given namespace or the empty string if the service isn't found

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL