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 }