goonvif/device.go

289 lines
7.9 KiB
Go
Raw 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"
"strings"
"time"
"git.pyer.club/kingecg/goonvif/onvif"
device "git.pyer.club/kingecg/goonvif/onvif/device"
"git.pyer.club/kingecg/goonvif/onvif/media"
sdk "git.pyer.club/kingecg/goonvif/onvif/sdk/device"
media_sdk "git.pyer.club/kingecg/goonvif/onvif/sdk/media"
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
}
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
}
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
}
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) 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 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
}