merge Device.go

This commit is contained in:
George Palanjyan
2018-04-10 00:18:53 +03:00
7 changed files with 292 additions and 46 deletions

144
Device.go
View File

@@ -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
View File

@@ -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](img/exmp_GetCapabilities.png)
Таким образом, Функция 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) можно увидеть структуру запроса:
![CreateUsers](img/exmp_CreateUsers.png)
Создадим объект `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) можно увидеть структуру запроса:
![ContinuousMove](img/exmp_ContinuousMove.png)
Так как данная команда определяется сервисом 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) можно увидеть структуру запроса:
![ContinuousMove](img/exmp_GetProfiles.png)
Вот пример создания и вызова запроса 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
img/exmp_CreateUsers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
img/exmp_GetProfiles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -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
}