goonvif/device.go

437 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package goonvif
import (
"context"
"errors"
"net"
"net/http"
"net/url"
"strings"
"time"
"git.pyer.club/kingecg/goonvif/onvif"
device "git.pyer.club/kingecg/goonvif/onvif/device"
"git.pyer.club/kingecg/goonvif/onvif/media"
ptz "git.pyer.club/kingecg/goonvif/onvif/ptz"
sdk "git.pyer.club/kingecg/goonvif/onvif/sdk/device"
media_sdk "git.pyer.club/kingecg/goonvif/onvif/sdk/media"
ptz_sdk "git.pyer.club/kingecg/goonvif/onvif/sdk/ptz"
wsdiscovery "git.pyer.club/kingecg/goonvif/onvif/ws-discovery"
"git.pyer.club/kingecg/goonvif/onvif/xsd"
onvifmodel "git.pyer.club/kingecg/goonvif/onvif/xsd/onvif"
"git.pyer.club/kingecg/goonvif/replay"
replay_sdk "git.pyer.club/kingecg/goonvif/sdk/replay"
search_sdk "git.pyer.club/kingecg/goonvif/sdk/search"
"git.pyer.club/kingecg/goonvif/search"
"github.com/beevik/etree"
)
type Device struct {
onvif.DeviceParams
device *onvif.Device
ctx context.Context
mediaCapablities *media.Capabilities
}
const (
panTiltSpace string = "http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace"
zoomSpace string = "http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace"
)
var ErrAuthRequired = errors.New("auth required")
func NewDevice(params onvif.DeviceParams) *Device {
return &Device{DeviceParams: params, ctx: context.Background()}
}
func (d *Device) check() error {
if d.device != nil {
return nil
}
if d.Username == "" || d.Password == "" {
return ErrAuthRequired
}
if d.HttpClient == nil {
d.HttpClient = new(http.Client)
}
device, err := onvif.NewDevice(d.DeviceParams)
if err != nil {
return err
}
d.device = device
return nil
}
func (d *Device) GetCapabilities() (interface{}, error) {
err := d.check()
if err != nil {
return nil, err
}
cap := device.GetCapabilities{Category: "All"}
getCapabilitiesResponse, err := sdk.Call_GetCapabilities(d.ctx, d.device, cap)
if err != nil {
return nil, err
}
r := getCapabilitiesResponse.Capabilities
return r, err
}
func (d *Device) GetRecordingSummary() (search.RecordingSummary, error) {
err := d.check()
if err != nil {
return search.RecordingSummary{}, err
}
resp, rerr := search_sdk.Call_GetRecordingSummary(d.ctx, d.device, search.GetRecordingSummary{})
if rerr != nil {
return search.RecordingSummary{}, rerr
}
return resp.Summary, nil
}
func (d *Device) GetEndpoints() (map[string]string, error) {
err := d.check()
if err != nil {
return nil, err
}
return d.device.GetServices(), nil
}
func (d *Device) FindRecording(start time.Time, end time.Time) ([]string, error) {
err := d.check()
if err != nil {
return nil, err
}
// recordFilterTemplate := "not (boolean(//EarliestRecording > %s) or boolean(//LatestRecording < %s) "
// filter := fmt.Sprintf(recordFilterTemplate, end.Format("2006-01-02T15:04:05Z"), start.Format("2006-01-02T15:04:05Z"))
fr, ferr := search_sdk.Call_FindRecordings(d.ctx, d.device, search.FindRecordings{})
if ferr != nil {
return nil, ferr
}
searchToken := fr.SearchToken
resultlist := make([]search.RecordingInformation, 0)
for {
gr, gerr := search_sdk.Call_GetRecordingSearchResults(d.ctx, d.device, search.GetRecordingSearchResults{SearchToken: searchToken})
if gerr != nil {
return nil, gerr
}
if len(gr.ResultList.RecordingInformation) > 0 {
resultlist = append(resultlist, gr.ResultList.RecordingInformation...)
}
if gr.ResultList.SearchState == "Completed" {
break
}
}
recordingTokes := make([]string, 0)
for _, r := range resultlist {
rstartStr := string(xsd.DateTime(r.EarliestRecording))
rendStr := string(xsd.DateTime(r.LatestRecording))
rstart, _ := time.Parse("2006-01-02T15:04:05Z", rstartStr)
rend, _ := time.Parse("2006-01-02T15:04:05Z", rendStr)
if rstart.After(end) || rend.Before(start) {
continue
}
recordingTokes = append(recordingTokes, string(r.RecordingToken))
}
return recordingTokes, nil
}
func (d *Device) GetReplayUriTcp(token string) (string, error) {
return d.GetReplayUri(token, TcpUnicastStreamSetup)
}
func (d *Device) GetReplayUriUdpUnicast(token string) (string, error) {
return d.GetReplayUri(token, UdpUnicastStreamSetup)
}
func (d *Device) GetReplayUriUdpMulticast(token string) (string, error) {
return d.GetReplayUri(token, RtspMulticastStreamSetup)
}
func (d *Device) GetMediaCapablities() (media.Capabilities, error) {
err := d.check()
if err != nil {
return media.Capabilities{}, err
}
if d.mediaCapablities != nil {
return *d.mediaCapablities, nil
}
resp, err := media_sdk.Call_GetServiceCapabilities(d.ctx, d.device, media.GetServiceCapabilities{})
if err != nil {
return media.Capabilities{}, err
}
d.mediaCapablities = &resp.Capabilities
return resp.Capabilities, nil
}
// GetReplayUri 获取设备回放的URI。
// 参数:
//
// token: 用于标识回放的令牌。
// transport: 传输协议UDP=RTP/UDP, RTSP=RTP/RTSP/TCP or HTTP=RTP/RTSP/HTTP/TCP。
// multiCast: 布尔值,指示是否使用多播。
//
// 返回值:
//
// string: 回放的URI。
// error: 错误信息,如果执行操作时发生错误。
func (d *Device) GetReplayUri(token string, stream onvifmodel.StreamSetup) (string, error) {
// 检查设备状态
err := d.check()
if err != nil {
return "", err
}
// 调用SDK方法获取回放URI
resp, err := replay_sdk.Call_GetReplayUri(d.ctx, d.device, replay.GetReplayUri{StreamSetup: stream, RecordingToken: onvifmodel.ReferenceToken(token)})
if err != nil {
return "", err
}
// 返回获取到的URI
// return string(resp.Uri), nil
if err != nil {
return "", err
}
urlobj, _ := url.Parse(string(resp.Uri))
urlobj.User = url.UserPassword(d.Username, d.Password)
return urlobj.String(), nil
}
func (d *Device) GetStreamUri(stream onvifmodel.StreamSetup, profileToken string) (string, error) {
err := d.check()
if err != nil {
return "", err
}
getStreamUri := media.GetStreamUri{StreamSetup: stream}
if profileToken != "" {
getStreamUri.ProfileToken = onvifmodel.ReferenceToken(profileToken)
}
resp, rerr := media_sdk.Call_GetStreamUri(d.ctx, d.device, getStreamUri)
// resp, rerr := media_sdk.Call_GetStreamUri(d.ctx, d.device, media.GetStreamUri{})
// return string(resp.MediaUri.Uri), rerr
if rerr != nil {
return "", rerr
}
urlobj, _ := url.Parse(string(resp.MediaUri.Uri))
urlobj.User = url.UserPassword(d.Username, d.Password)
return urlobj.String(), nil
}
func (d *Device) GetStreamUriTcp(profileToken string) (string, error) {
return d.GetStreamUri(TcpUnicastStreamSetup, profileToken)
}
func (d *Device) GetStreamUriUdp(profileToken string) (string, error) {
return d.GetStreamUri(UdpUnicastStreamSetup, profileToken)
}
func (d *Device) GetSnapshotUri(profileToken string) (string, error) {
return "", nil
}
func (d *Device) GetVideoSources() ([]onvifmodel.VideoSource, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := media_sdk.Call_GetVideoSources(d.ctx, d.device, media.GetVideoSources{})
if err != nil {
return nil, err
}
return resp.VideoSources, nil
}
func (d *Device) GetVideoSourceConfigurations() ([]onvifmodel.VideoSourceConfiguration, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := media_sdk.Call_GetVideoSourceConfigurations(d.ctx, d.device, media.GetVideoSourceConfigurations{})
if err != nil {
return nil, err
}
return resp.Configurations, nil
}
func (d *Device) RelateVideoSource(profileToken, videoSourceToken string) error {
err := d.check()
if err != nil {
return err
}
profileResp, err := media_sdk.Call_GetProfile(d.ctx, d.device, media.GetProfile{ProfileToken: onvifmodel.ReferenceToken(profileToken)})
if err != nil {
return err
}
profile := profileResp.Profile
profile.VideoSourceConfiguration.SourceToken = onvifmodel.ReferenceToken(videoSourceToken)
// profile.VideoSourceConfiguration.Name = onvifmodel.Name(videoSourceToken)
_, serr := media_sdk.Call_SetVideoSourceConfiguration(d.ctx, d.device, media.SetVideoSourceConfiguration{Configuration: profile.VideoSourceConfiguration, ForcePersistence: xsd.Boolean(true)})
if serr != nil {
return serr
}
_, aerr := media_sdk.Call_AddVideoSourceConfiguration(d.ctx, d.device, media.AddVideoSourceConfiguration{ProfileToken: onvifmodel.ReferenceToken(profileToken), ConfigurationToken: profile.VideoSourceConfiguration.Token})
return aerr
}
func (d *Device) GetProfiles() ([]onvifmodel.Profile, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := media_sdk.Call_GetProfiles(d.ctx, d.device, media.GetProfiles{})
if err != nil {
return nil, err
}
return resp.Profiles, nil
}
func (d *Device) GetVideoEncoderConfigurations() ([]onvifmodel.VideoEncoderConfiguration, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := media_sdk.Call_GetVideoEncoderConfigurations(d.ctx, d.device, media.GetVideoEncoderConfigurations{})
if err != nil {
return nil, err
}
return resp.Configurations, nil
}
func (d *Device) GetDeviceInformation() (interface{}, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := sdk.Call_GetDeviceInformation(d.ctx, d.device, device.GetDeviceInformation{})
if err != nil {
return nil, err
}
return resp, nil
}
func (d *Device) PTZNodes() ([]onvifmodel.PTZNode, error) {
err := d.check()
if err != nil {
return nil, err
}
resp, err := ptz_sdk.Call_GetNodes(d.ctx, d.device, ptz.GetNodes{})
if err != nil {
return nil, err
}
return resp.PTZNode, nil
}
func (d *Device) continueMove(panTilt onvifmodel.Vector2D, zoom float64) error {
err := d.check()
if err != nil {
return err
}
v := onvifmodel.PTZSpeed{}
panTilt.Space = xsd.AnyURI(panTiltSpace)
v.PanTilt = panTilt
v.Zoom = onvifmodel.Vector1D{
Space: xsd.AnyURI(zoomSpace),
X: zoom,
}
_, perr := ptz_sdk.Call_ContinuousMove(d.ctx, d.device, ptz.ContinuousMove{
ProfileToken: "Profile_1",
Velocity: v,
})
return perr
}
func (d *Device) StopMove() error {
err := d.check()
if err != nil {
return err
}
_, perr := ptz_sdk.Call_Stop(d.ctx, d.device, ptz.Stop{})
return perr
}
// Pan 执行设备的水平和垂直移动。
// 此函数通过指定的x和y坐标来控制设备的移动方向和距离。
// 参数:
//
// x - 水平移动的坐标值。range: -1 to 1
// y - 垂直移动的坐标值。range: -1 to 1
//
// 返回值:
//
// 如果移动操作成功返回nil否则返回错误。
func (d *Device) Pan(x, y float64) error {
// 创建一个二维向量来表示移动的方向和距离。
v := onvifmodel.Vector2D{X: x, Y: y}
// 调用continueMove函数来执行实际的移动操作移动速度设置为0。
return d.continueMove(v, 0)
}
func (d *Device) Zoom(zoom float64) error {
return d.continueMove(onvifmodel.Vector2D{}, zoom)
}
func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) ([]Device, error) {
// Call a ws-discovery Probe Message to Discover NVT type Devices
devices, err := wsdiscovery.SendProbe(interfaceName, nil, []string{"dn:" + onvif.NVT.String()}, map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl"})
if err != nil {
return nil, err
}
nvtDevicesSeen := make(map[string]bool)
nvtDevices := make([]Device, 0)
for _, j := range devices {
doc := etree.NewDocument()
if err := doc.ReadFromString(j); err != nil {
return nil, err
}
for _, xaddr := range doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch/XAddrs") {
xaddr := strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2]
if !nvtDevicesSeen[xaddr] {
dev := NewDevice(onvif.DeviceParams{Xaddr: strings.Split(xaddr, " ")[0]})
nvtDevicesSeen[xaddr] = true
nvtDevices = append(nvtDevices, *dev)
}
}
}
return nvtDevices, nil
}
func Discovery() ([]Device, error) {
ifaces, err := listLocalNetworkInterfaces()
if err != nil {
return nil, err
}
devices := make([]Device, 0)
for _, iface := range ifaces {
idevices, err := GetAvailableDevicesAtSpecificEthernetInterface(iface)
if err != nil {
continue
}
devices = append(devices, idevices...)
}
return devices, nil
}
func listLocalNetworkInterfaces() ([]string, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
var ifaceNames []string
for _, iface := range interfaces {
// 判断是否为局域网
if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagRunning) == 0 || (iface.Flags&net.FlagLoopback) != 0 {
continue
}
ifaceNames = append(ifaceNames, iface.Name)
}
return ifaceNames, nil
}