mirror of
https://github.com/kerberos-io/onvif.git
synced 2025-10-28 10:01:34 +08:00
merge Device.go
This commit is contained in:
144
Device.go
144
Device.go
@@ -2,16 +2,17 @@ package goonvif
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"fmt"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/yakovlevdmv/gosoap"
|
||||
"strconv"
|
||||
"reflect"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"github.com/yakovlevdmv/WS-Discovery"
|
||||
"strings"
|
||||
"github.com/yakovlevdmv/goonvif/Device"
|
||||
"github.com/yakovlevdmv/WS-Discovery"
|
||||
"errors"
|
||||
"reflect"
|
||||
"github.com/yakovlevdmv/goonvif/networking"
|
||||
)
|
||||
|
||||
@@ -81,54 +82,112 @@ type device struct {
|
||||
|
||||
}
|
||||
|
||||
func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) {
|
||||
func (dev *device)GetServices() map[string]string {
|
||||
return dev.endpoints
|
||||
}
|
||||
|
||||
func readResponse(resp *http.Response) string {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) []device {
|
||||
/*
|
||||
Call an WS-Discovery Probe Message to Discover NVT type Devices
|
||||
*/
|
||||
devices := WS_Discovery.SendProbe(interfaceName, nil, []string{"dn:"+NVT.String()}, map[string]string{"dn":"http://www.onvif.org/ver10/network/wsdl"})
|
||||
nvtDevices := make([]device, 0)
|
||||
////fmt.Println(devices)
|
||||
for _, j := range devices {
|
||||
fmt.Println(j)
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromString(j); err != nil {
|
||||
fmt.Errorf("%s", err.Error())
|
||||
return nil
|
||||
}
|
||||
////fmt.Println(j)
|
||||
endpoints := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch/XAddrs")
|
||||
for _, xaddr := range endpoints {
|
||||
//fmt.Println(xaddr.Tag,strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2] )
|
||||
xaddr := strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2]
|
||||
fmt.Println(xaddr)
|
||||
c := 0
|
||||
for c = 0; c < len(nvtDevices); c++ {
|
||||
if nvtDevices[c].xaddr == xaddr {
|
||||
fmt.Println(nvtDevices[c].xaddr, "==", xaddr)
|
||||
break
|
||||
}
|
||||
}
|
||||
if c < len(nvtDevices) {
|
||||
continue
|
||||
}
|
||||
dev, err := NewDevice(strings.Split(xaddr, " ")[0])
|
||||
//fmt.Println(dev)
|
||||
if err != nil {
|
||||
fmt.Println("Error", xaddr)
|
||||
fmt.Println(err)
|
||||
continue
|
||||
} else {
|
||||
////fmt.Println(dev)
|
||||
nvtDevices = append(nvtDevices, *dev)
|
||||
}
|
||||
}
|
||||
////fmt.Println(j)
|
||||
//nvtDevices[i] = NewDevice()
|
||||
}
|
||||
return nvtDevices
|
||||
}
|
||||
|
||||
func (dev *device) getSupportedServices() {
|
||||
resp, err := dev.CallMethod(Device.GetCapabilities{})
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return
|
||||
} else {
|
||||
func (dev *device) getSupportedServices(resp *http.Response) {
|
||||
//resp, err := dev.CallMethod(Device.GetCapabilities{Category:"All"})
|
||||
//if err != nil {
|
||||
// log.Println(err.Error())
|
||||
//return
|
||||
//} else {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromString(resp); err != nil {
|
||||
log.Println(err.Error())
|
||||
|
||||
data, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err := doc.ReadFromBytes(data); err != nil {
|
||||
//log.Println(err.Error())
|
||||
return
|
||||
}
|
||||
services := doc.FindElements("./Envelope/Body/GetCapabilitiesResponse/Capabilities/*/XAddr")
|
||||
for _, j := range services{
|
||||
fmt.Println(j.Text())
|
||||
fmt.Println(j.Parent().Tag)
|
||||
////fmt.Println(j.Text())
|
||||
////fmt.Println(j.Parent().Tag)
|
||||
dev.addEndpoint(j.Parent().Tag, j.Text())
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
//NewDevice function construct a ONVIF Device entity
|
||||
func NewDevice(xaddr string) *device {
|
||||
func NewDevice(xaddr string) (*device, error) {
|
||||
dev := new(device)
|
||||
dev.xaddr = xaddr
|
||||
dev.endpoints = make(map[string]string)
|
||||
dev.addEndpoint("Device", "http://"+xaddr+"/onvif/device_service")
|
||||
dev.getSupportedServices()
|
||||
return dev
|
||||
|
||||
getCapabilities := Device.GetCapabilities{Category: "All"}
|
||||
|
||||
resp, err := dev.CallMethod(getCapabilities)
|
||||
//fmt.Println(resp.Request.Host)
|
||||
//fmt.Println(readResponse(resp))
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
//panic(errors.New("camera is not available at " + xaddr + " or it does not support ONVIF services"))
|
||||
return nil, errors.New("camera is not available at " + xaddr + " or it does not support ONVIF services")
|
||||
}
|
||||
|
||||
dev.getSupportedServices(resp)
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func (dev *device)addEndpoint(Key, Value string) {
|
||||
dev.endpoints[Key]=Value
|
||||
}
|
||||
|
||||
func newDeviceEntity() *device {
|
||||
return &device{}
|
||||
}
|
||||
|
||||
//Authenticate function authenticate client in the ONVIF Device.
|
||||
//Function takes <username> and <password> params.
|
||||
//You should use this function to allow authorized requests to the ONVIF Device
|
||||
@@ -146,6 +205,7 @@ func buildMethodSOAP(msg string) (gosoap.SoapMessage, error) {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromString(msg); err != nil {
|
||||
//log.Println("Got error")
|
||||
|
||||
return "", err
|
||||
}
|
||||
element := doc.Root()
|
||||
@@ -162,7 +222,7 @@ func buildMethodSOAP(msg string) (gosoap.SoapMessage, error) {
|
||||
|
||||
//CallMethod functions call an method, defined <method> struct.
|
||||
//You should use Authenticate method to call authorized requests.
|
||||
func (dev device) CallMethod(method interface{}) (string, error) {
|
||||
func (dev device) CallMethod(method interface{}) (*http.Response, error) {
|
||||
pkgPath := strings.Split(reflect.TypeOf(method).PkgPath(),"/")
|
||||
pkg := pkgPath[len(pkgPath)-1]
|
||||
|
||||
@@ -175,20 +235,30 @@ func (dev device) CallMethod(method interface{}) (string, error) {
|
||||
case "PTZ": endpoint = dev.endpoints["PTZ"]
|
||||
}
|
||||
|
||||
if len(endpoint) == 0 {
|
||||
return "", errors.New("requested service is not implemented")
|
||||
}
|
||||
|
||||
//TODO: Get endpoint automatically
|
||||
if dev.login != "" && dev.password != "" {
|
||||
return dev.CallAuthorizedMethod(endpoint, method)
|
||||
/*resp, err := dev.сallAuthorizedMethod(endpoint, method)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, err*/
|
||||
return dev.callAuthorizedMethod(endpoint, method)
|
||||
} else {
|
||||
return dev.CallNonAuthorizedMethod(endpoint, method)
|
||||
/*resp, err := dev.сallAuthorizedMethod(endpoint, method)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return resp, err
|
||||
}
|
||||
return resp, err*/
|
||||
return dev.callNonAuthorizedMethod(endpoint, method)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//CallNonAuthorizedMethod functions call an method, defined <method> struct without authentication data
|
||||
func (dev device) CallNonAuthorizedMethod(endpoint string, method interface{}) (string, error) {
|
||||
func (dev device) callNonAuthorizedMethod(endpoint string, method interface{}) (*http.Response, error) {
|
||||
//TODO: Get endpoint automatically
|
||||
/*
|
||||
Converting <method> struct to xml string representation
|
||||
@@ -196,7 +266,7 @@ func (dev device) CallNonAuthorizedMethod(endpoint string, method interface{}) (
|
||||
output, err := xml.MarshalIndent(method, " ", " ")
|
||||
if err != nil {
|
||||
//log.Printf("error: %v\n", err.Error())
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -205,7 +275,7 @@ func (dev device) CallNonAuthorizedMethod(endpoint string, method interface{}) (
|
||||
soap, err := buildMethodSOAP(string(output))
|
||||
if err != nil {
|
||||
//log.Printf("error: %v\n", err)
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
soap.AddRootNamespaces(Xlmns)
|
||||
@@ -217,14 +287,14 @@ func (dev device) CallNonAuthorizedMethod(endpoint string, method interface{}) (
|
||||
}
|
||||
|
||||
//CallMethod functions call an method, defined <method> struct with authentication data
|
||||
func (dev device) CallAuthorizedMethod(endpoint string, method interface{}) (string, error) {
|
||||
func (dev device) callAuthorizedMethod(endpoint string, method interface{}) (*http.Response, error) {
|
||||
/*
|
||||
Converting <method> struct to xml string representation
|
||||
*/
|
||||
output, err := xml.MarshalIndent(method, " ", " ")
|
||||
if err != nil {
|
||||
//log.Printf("error: %v\n", err.Error())
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -233,7 +303,7 @@ func (dev device) CallAuthorizedMethod(endpoint string, method interface{}) (str
|
||||
soap, err := buildMethodSOAP(string(output))
|
||||
if err != nil {
|
||||
//log.Printf("error: %v\n", err.Error())
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -252,7 +322,7 @@ func (dev device) CallAuthorizedMethod(endpoint string, method interface{}) (str
|
||||
soapReq, err := xml.MarshalIndent(auth, "", " ")
|
||||
if err != nil {
|
||||
//log.Printf("error: %v\n", err.Error())
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
172
README.md
172
README.md
@@ -1 +1,171 @@
|
||||
# goonvif
|
||||
# Goonvif
|
||||
Библиотека **Goonvif** создана для упрощения взаимодействия с ONVIF устройствами. На данный момент в библиотеке реализована поддержка NVT(Network Video Transmitter) устройств, а именно следующих ONVIF сервисов:
|
||||
- Core или DeviceManagement
|
||||
- Media
|
||||
- Imaging
|
||||
- PTZ
|
||||
- Analytics
|
||||
# Dependencies
|
||||
[etree](https://github.com/beevik/etree)
|
||||
# Установка
|
||||
Для установки библиотеки необходимо воспользоваться утилитой go get:
|
||||
```
|
||||
go get github.com/yakovlevdmv/goonvif
|
||||
```
|
||||
|
||||
# Начало работы
|
||||
Чтобы начать работать с камерой, необходимо создать объект `device`.
|
||||
Для этого необходимо воспользоваться функцией `func NewDevice(xaddr string) (*device, error)`,
|
||||
которая принимает IP адрес ONVIF устройства и возвращает указатель на созданный объект либо ошибку.
|
||||
Если камера недоступна, указан неверный адрес для ONVIF сервиса камеры (возможно находится по другому порту) или же камера вообще не поддерживает ONVIF
|
||||
функция вернет error не являющимся `nil`, а в качестве указателя на объект устройства вернет `nil`.
|
||||
### Пример подключения к камере
|
||||
Пусть камера в сети находится по адресу 192.168.13.42, а ее ONVIF сервисы расположены на порте 1234. Тогда,
|
||||
```
|
||||
dev, err := goonvif.NewDevice("192.168.13.42:1234")
|
||||
```
|
||||
сработает успешно, а
|
||||
```
|
||||
dev, err := goonvif.NewDevice("192.168.13.42:80")
|
||||
```
|
||||
вернет нулевой объект камеры и ошибку:
|
||||
> camera is not available at 192.168.13.42:80 or it does not support ONVIF services
|
||||
|
||||
Модернизируем код, добавив обработку ошибки, и получим:
|
||||
```
|
||||
dev, err := goonvif.NewDevice("192.168.13.42:80")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
///Работа с камерой
|
||||
```
|
||||
### Поддерживаемые ONVIF сервисы
|
||||
Теперь, когда камера доступна, можно приступать к работе с ней. Однако стандарт ONVIF имеет множество сервисов, а также точку доступа (endpoint) которая не определена стандартом (кроме DeviceManagment: http://onvif_host/onvif/device_service).
|
||||
Поэтому дальше встает вопрос о поддерживаемых камерой сервисах и определении их endpoint'ов.
|
||||
Для получения поддерживаемых камерой сервисов необходимо вызвать метод GetCapabilities сервиса DeviceManagement.
|
||||
Однако эта библиотека автоматизирует данный процесс, поэтому при создании объекта device при помощи `func NewDevice(xaddr string) (*device, error)`
|
||||
библиотека одновременно обрабатывает поддерживаемые камерой сервисы. Таким образом есть два способа получения поддерживаемых устройством сервисов:
|
||||
1. Вызвать метод GetCapabilities сервиса DeviceManagement(как это сделать будет рассмотрено дальше) и обработать ответ.
|
||||
2. Довериться библиотеке и вызвать функцию `func (dev *device)GetServices() map[string]string`, которая вернет map[string]string, ключом которой является название сервиса, а значением - endpoint данного сервиса
|
||||
### Работа с камерой
|
||||
Для работы с различными сервисами камерами необходимо отправить корректный SOAP запрос, в теле которого находится вызываемый метод и принимаемые им функции.
|
||||
**Goonvif** берет на себя работу по созданию корректного SOAP запроса и его отправке. В **Goonvif** определены структуры, для каждой функции каждого (поддерживаемого данной бибилиотекой) сервиса ONVIF:
|
||||
- [DeviceManagement Service](Device/types.go)
|
||||
|
||||
- [Media Service](Media/types.go)
|
||||
|
||||
- [Imaging Service](Imaging/types.go)
|
||||
|
||||
- [PTZ Service](PTZ/types.go)
|
||||
|
||||
- [Analytics Service](Analytics/types.go)
|
||||
|
||||
[Список всех сервисов стандарта (и документация к ним)](https://www.onvif.org/profiles/specifications/)
|
||||
|
||||
Рассмторим, как организована отправка запросов в **Goonvif** на нескольких примерах.
|
||||
1. Метод GetCapabilities сервиса DeviceManagement
|
||||
|
||||
Все необходимые типы данных определены в пакете [Device](Device/types.go).
|
||||
В файле (https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl) можно увидеть:
|
||||

|
||||
|
||||
Таким образом, Функция GetCapabilities принимает в качестве аргумента перечисление:
|
||||
`enum { 'All', 'Analytics', 'Device', 'Events', 'Imaging', 'Media', 'PTZ' }`
|
||||
Чтобы вызвать данный метод создадим объект `Device.GetCapabilities`:
|
||||
```
|
||||
capabilities := Device.GetCapabilities{Category:"All"}
|
||||
```
|
||||
Для вызова данной функции воспользуемся методом `func (dev device) CallMethod(method interface{}) (string, error)`:
|
||||
```
|
||||
resp, err := dev.CallMethod(capab)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fmt.Println(resp)
|
||||
}
|
||||
```
|
||||
2. Создание пользователя методом CreateUsers сервиса DeviceManagement
|
||||
|
||||
Все необходимые типы данных определены в пакете [Device](Device/types.go).
|
||||
В файле (https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl) можно увидеть структуру запроса:
|
||||

|
||||
|
||||
Создадим объект `Device.CreateUsers`:
|
||||
```
|
||||
createUsers := Device.CreateUsers{User: onvif.User{Username:"korolev", Password:"qwerty", UserLevel:"User"}}
|
||||
resp, err := dev.CallMethod(createUsers)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fmt.Println(resp)
|
||||
}
|
||||
```
|
||||
|
||||
В данном примере можно наблюдать использование пакета onvif, в котором определено большинство типов, используемых в поддерживаемых библиотекой сервисах, поэтому при создании структур запросов необходимо это учитывать.
|
||||
|
||||
##### ВАЖНО
|
||||
Некоторые камеры работают специфично. Это означает, что в зависимости от модели камеры можно не получить ошибки при неправильном запросе. Поэтому советую проверять, точно ли выполнилась операция. Например, для метода CreateUsers надо вывести список всех пользователей и проверить добавился ли пользователь.
|
||||
|
||||
3. Метод ContinuousMove сервиса PTZ
|
||||
|
||||
Все необходимые типы данных определены в пакете [PTZ](PTZ/types.go).
|
||||
В файле (https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl) можно увидеть структуру запроса:
|
||||

|
||||
|
||||
Так как данная команда определяется сервисом PTZ, необходимый тип находится в пакете [PTZ](PTZ/types.go).
|
||||
Из файла [PTZ](PTZ/types.go) можно заметить, что :
|
||||
> ProfileToken [ReferenceToken]
|
||||
> A reference to the **MediaProfile** that indicate what should be stopped.
|
||||
|
||||
Таким образом, для того, чтобы начать работать с PTZ сервисом для начала необходимо получить **ProfileToken** сервиса **Media**. Как это сделать смотрите в примере 4. Сейчас же предположим, что нам известен нужный токен. Пусть ProfileToken = "Profile_1".
|
||||
|
||||
Создадим объект `PTZ.ContinuousMove`:
|
||||
```
|
||||
move := PTZ.ContinuousMove{
|
||||
ProfileToken:"Profile_1",
|
||||
Velocity:onvif.PTZSpeed{
|
||||
PanTilt:onvif.Vector2D{
|
||||
X: 1,
|
||||
Y: 1,
|
||||
},
|
||||
Zoom:onvif.Vector1D{X:0.5},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Заметим**, что объекты Velocity, PanTilt и Zoom определены в пакете onvif. Такое применение свойственно для большинства встроенных в структуру типов.
|
||||
|
||||
Для вызова данной функции воспользуемся методом `func (dev device) CallMethod(method interface{}) (string, error)`:
|
||||
```
|
||||
resp, err := dev.CallMethod(capab)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
fmt.Println(resp)
|
||||
}
|
||||
```
|
||||
|
||||
4. Получение списков Media профилей
|
||||
|
||||
Все необходимые типы данных определены в пакете [Media](Media/types.go).
|
||||
В файле (https://www.onvif.org/ver10/media/wsdl/media.wsdl) можно увидеть структуру запроса:
|
||||

|
||||
|
||||
Вот пример создания и вызова запроса GetProfiles
|
||||
```
|
||||
resp, err := dev.CallMethod(Media.GetProfiles{})
|
||||
if err != nil {
|
||||
panic (err)
|
||||
} else {
|
||||
fmt.Println(readResponse(resp))
|
||||
}
|
||||
```
|
||||
|
||||
**Важно** Обработка response'ов камеры пока не реализована, поэтому данная задача ложится на **Ваши** плечи. Вы можете упростить обработку response'ов при помощи библиотеки [etree](https://github.com/beevik/etree) либо же воспользоваться сервисом (http://www.webtoolkitonline.com/xml-formatter.html)
|
||||
|
||||
**Важно** Многие запросы требуют авторизованного доступа и для того, чтобы добавить аторизацию к конкретной камере, необходимо воспользоваться функцией `func (dev *device) Authenticate(username, password string)`. После применения данной функции все отправляемые камерой запросы будут авторизованными.
|
||||
```
|
||||
device := onvif.NewDevice("192.168.13.42:1234")
|
||||
device.Authenticate("username", "password")
|
||||
```
|
||||
|
||||
BIN
img/exmp_ContinuousMove.png
Normal file
BIN
img/exmp_ContinuousMove.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
img/exmp_CreateUsers.png
Normal file
BIN
img/exmp_CreateUsers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
img/exmp_GetCapabilities.png
Normal file
BIN
img/exmp_GetCapabilities.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
img/exmp_GetProfiles.png
Normal file
BIN
img/exmp_GetProfiles.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -3,20 +3,26 @@ package networking
|
||||
import (
|
||||
"net/http"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func SendSoap(endpoint, message string) (string, error) {
|
||||
func SendSoap(endpoint, message string) (*http.Response, error) {
|
||||
httpClient := new(http.Client)
|
||||
|
||||
resp, err := httpClient.Post(endpoint, "application/soap+xml; charset=utf-8", bytes.NewBufferString(message))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return string(b),nil
|
||||
/*if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest {
|
||||
return "", errors.New("error: got HTTP response status " + strconv.Itoa(resp.StatusCode))
|
||||
}*/
|
||||
//b, err := ioutil.ReadAll(resp.Body)
|
||||
//if err != nil {
|
||||
// return resp, err
|
||||
//}
|
||||
//fmt.Println(endpoint)
|
||||
//fmt.Println(string(b))
|
||||
//log.Println(resp.StatusCode)
|
||||
|
||||
return resp,nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user