package plugin_onvif import ( "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "github.com/IOTechSystems/onvif/xsd/onvif" "github.com/jinzhu/copier" ) type Response struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } type DeviceAddReq struct { IP string `json:"ip"` Port string `json:"port"` User string `json:"user"` Password string `json:"passwd"` Path string `json:"path"` Channel int `json:"channel"` } type PtzMoveReq struct { IP string `json:"ip"` Mode int `json:"mode"` Pan float64 `json:"pan"` Tilt float64 `json:"tilt"` Zoom float64 `json:"zoom"` Speed float64 `json:"speed"` } type PtzPresetReq struct { IP string `json:"ip"` PresetToken string `json:"preset_token"` PresetName string `json:"preset_name"` } type ImagingReq struct { IP string `json:"ip"` Brightness float64 `json:"brightness"` ColorSaturation float64 `json:"color_saturation"` Contrast float64 `json:"contrast"` Sharpness float64 `json:"sharpness"` Force bool `json:"force"` } type DeviceParam struct { Ip string `json:"ip"` Port string `json:"port"` User string `json:"user"` Passwd string `json:"passwd"` Path string `json:"path"` IFace string `json:"iface"` Channel int `json:"channel"` } /* ip, user, passwd, port, path string, channel int */ func parseDeviceParam(r *http.Request) (*DeviceParam, error) { ip := r.URL.Query().Get("ip") port := r.URL.Query().Get("port") user := r.URL.Query().Get("user") passwd := r.URL.Query().Get("passwd") path := r.URL.Query().Get("path") channel := r.URL.Query().Get("channel") iface := r.URL.Query().Get("iface") if ip == "" { return nil, fmt.Errorf("param ip error") } if channel == "" { channel = "0" } channelInt, err := strconv.Atoi(channel) if err != nil { return nil, fmt.Errorf("param channel error") } if port == "" { port = "80" } if path == "" { path = "/onvif/device_service" } if iface == "" { iface = VIRTUAL_IFACE } return &DeviceParam{ Ip: ip, Port: port, User: user, Passwd: passwd, Path: path, IFace: iface, Channel: channelInt, }, nil } func getDevice(autoAdd bool, method string, resp *Response, w http.ResponseWriter, r *http.Request) (*DeviceStatus, error) { w.Header().Set("Content-Type", "application/json") if r.Method != method { w.WriteHeader(http.StatusMethodNotAllowed) resp.Code = http.StatusMethodNotAllowed resp.Msg = "method not allowed" json.NewEncoder(w).Encode(resp) return nil, fmt.Errorf("method not allowed") } devParam, err := parseDeviceParam(r) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return nil, err } devs, ok := deviceList.Data.Get(devParam.IFace) if !ok { resp.Code = http.StatusBadRequest resp.Msg = "iface not found" json.NewEncoder(w).Encode(resp) return nil, fmt.Errorf("iface not found") } dev, ok := devs.Get(devParam.Ip + ":" + devParam.Port) if !ok { if !autoAdd { resp.Code = http.StatusBadRequest resp.Msg = "device not found" json.NewEncoder(w).Encode(resp) return nil, fmt.Errorf("device not found") } //add device ds, code, err := deviceList.AddDevice(devParam) if err != nil { resp.Msg = err.Error() resp.Code = code json.NewEncoder(w).Encode(resp) return nil, err } dev = ds } if devParam.Channel > len(dev.Profiles) { resp.Code = http.StatusBadRequest resp.Msg = "channel out of range" json.NewEncoder(w).Encode(resp) return nil, fmt.Errorf("channel out of range") } dev.Channel = devParam.Channel return dev, nil } func (o *OnvifPlugin) closeStream(resp *Response, w http.ResponseWriter, r *http.Request) error { d, err := getDevice(o.AutoAdd, http.MethodPost, resp, w, r) if err != nil { return err } devParam, _ := parseDeviceParam(r) streamPath := GenStreamPath(d.Device, devParam.IFace) stream, _ := o.Server.Streams.Get(streamPath) if stream == nil { return nil } stream.Stop(errors.New("close manual")) return nil } func (o *OnvifPlugin) API_list(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resp := Response{ Code: 0, Msg: "ok", Data: nil, } byList := r.URL.Query().Get("bylist") if byList == "1" { list := make([]*DeviceStatus, 0) deviceList.Data.Range(func(ic *InterfaceCollection) bool { ic.Range(func(ds *DeviceStatus) bool { list = append(list, ds) return true }) return true }) resp.Data = list } else { data := make(map[string]map[string]*DeviceStatus) deviceList.Data.Range(func(ic *InterfaceCollection) bool { if _, ok := data[ic.iface]; !ok { data[ic.iface] = make(map[string]*DeviceStatus) } ic.Range(func(ds *DeviceStatus) bool { data[ic.iface][ds.GetKey()] = ds return true }) return true }) resp.Data = data } json.NewEncoder(w).Encode(resp) return } func (o *OnvifPlugin) API_adddevice(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) resp.Code = http.StatusMethodNotAllowed resp.Msg = "method not allowed" json.NewEncoder(w).Encode(resp) return } devParam, err := parseDeviceParam(r) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } //add device _, code, err := deviceList.AddDevice(devParam) if err != nil { resp.Msg = err.Error() resp.Code = code json.NewEncoder(w).Encode(resp) return } resp.Code = 0 json.NewEncoder(w).Encode(resp) return } func (o *OnvifPlugin) API_deldevice(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) resp.Code = http.StatusMethodNotAllowed resp.Msg = "method not allowed" json.NewEncoder(w).Encode(resp) return } devParam, err := parseDeviceParam(r) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } //先关闭流 err = o.closeStream(&resp, w, r) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } //add device deviceList.DelDevice(devParam) resp.Code = 0 json.NewEncoder(w).Encode(resp) return } func (o *OnvifPlugin) API_pull(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } d, err := getDevice(o.AutoAdd, http.MethodGet, &resp, w, r) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } devParam, _ := parseDeviceParam(r) err = d.PullStream(devParam.IFace, devParam.Channel) if err != nil { resp.Code = StatusInitError resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } resp.Data = map[string]any{ "stream": d.Stream, } json.NewEncoder(w).Encode(resp) } func (o *OnvifPlugin) API_close(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } o.closeStream(&resp, w, r) json.NewEncoder(w).Encode(resp) return } func (o *OnvifPlugin) API_status(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodGet, &resp, w, r) if err != nil { return } resp.Data = map[string]any{ "status": dev.Status, } json.NewEncoder(w).Encode(resp) } // 获取设备能力 func (o *OnvifPlugin) API_capability(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodGet, &resp, w, r) if err != nil { return } caps := dev.Device.GetServices() resp.Data = caps json.NewEncoder(w).Encode(resp) } // 获取图片能力信息 func (o *OnvifPlugin) API_imageProfile(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodGet, &resp, w, r) if err != nil { return } settings, err := dev.GetImaging() if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } resp.Data = settings json.NewEncoder(w).Encode(resp) } func (o *OnvifPlugin) API_setImageProfile(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodPost, &resp, w, r) if err != nil { return } var settingReq ImageSettingReq content, err := io.ReadAll(r.Body) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } err = json.Unmarshal(content, &settingReq) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } var settings onvif.ImagingSettings20 copier.Copy(&settings, settingReq.ImageSettings) err = dev.SetImaging(&settings, settingReq.ForcePersistence) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } json.NewEncoder(w).Encode(resp) } // 获取预置位 func (o *OnvifPlugin) API_ptzPreset(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodGet, &resp, w, r) if err != nil { return } settings, err := dev.GetPtzPreset() if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } resp.Data = settings json.NewEncoder(w).Encode(resp) } // 设置预置位 name 必填 preset_token 可选,如果提供则更新预置位 func (o *OnvifPlugin) API_setPtzPreset(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodPost, &resp, w, r) if err != nil { return } name := r.URL.Query().Get("name") if name == "" { resp.Code = http.StatusBadRequest resp.Msg = "name is empty" json.NewEncoder(w).Encode(resp) return } presetToken := r.URL.Query().Get("preset_token") token, err := dev.SetPtzPreset(name, presetToken) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } resp.Data = map[string]any{"Token": token} json.NewEncoder(w).Encode(resp) } // 预置位移动 func (o *OnvifPlugin) API_gotoPtzPreset(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodPost, &resp, w, r) if err != nil { return } presetToken := r.URL.Query().Get("preset_token") if presetToken == "" { resp.Code = http.StatusBadRequest resp.Msg = "preset_token is empty" json.NewEncoder(w).Encode(resp) return } content, err := io.ReadAll(r.Body) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } var speed *onvif.PTZSpeed if string(content) != "" { json.Unmarshal(content, &speed) } err = dev.GotoPtzPreset(presetToken, speed) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } json.NewEncoder(w).Encode(resp) } // 预置位删除 func (o *OnvifPlugin) API_removePtzPreset(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodPost, &resp, w, r) if err != nil { return } presetToken := r.URL.Query().Get("preset_token") if presetToken == "" { resp.Code = http.StatusBadRequest resp.Msg = "preset_token is empty" json.NewEncoder(w).Encode(resp) return } err = dev.RemovePtzPreset(presetToken) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } json.NewEncoder(w).Encode(resp) } // 移动摄像头 func (o *OnvifPlugin) API_ptz(w http.ResponseWriter, r *http.Request) { resp := Response{ Code: 0, Msg: "ok", Data: map[string]any{}, } dev, err := getDevice(o.AutoAdd, http.MethodPost, &resp, w, r) if err != nil { return } mode := r.URL.Query().Get("mode") if mode == "" { resp.Code = http.StatusBadRequest resp.Msg = "mode is empty" json.NewEncoder(w).Encode(resp) return } modeInt, err := strconv.Atoi(mode) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = "mode is error" json.NewEncoder(w).Encode(resp) return } content, err := io.ReadAll(r.Body) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } switch modeInt { case PtzMoveAbs, PtzMoveRelative: var ptzMove ptzMoveReq err = json.Unmarshal(content, &ptzMove) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } err = dev.PtzMove(modeInt, ptzMove.Move, ptzMove.Speed) case PtzMoveContinue: var ptzContinueMove ptzContinueMoveReq err = json.Unmarshal(content, &ptzContinueMove) if err != nil { resp.Code = http.StatusBadRequest resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } err = dev.PtzContinueMove(ptzContinueMove.Velocity, ptzContinueMove.Timeout) default: resp.Code = http.StatusBadRequest resp.Msg = "mode is error" json.NewEncoder(w).Encode(resp) return } if err != nil { resp.Code = StatusPtzMove resp.Msg = err.Error() json.NewEncoder(w).Encode(resp) return } json.NewEncoder(w).Encode(resp) } func (p *OnvifPlugin) RegisterHandler() map[string]http.HandlerFunc { return map[string]http.HandlerFunc{ "/list": p.API_list, "/add": p.API_adddevice, "/remove": p.API_deldevice, "/ptz": p.API_ptz, "/ptz/preset/get": p.API_ptzPreset, "/ptz/preset/set": p.API_setPtzPreset, "/ptz/preset/goto": p.API_gotoPtzPreset, "/imaging/get": p.API_imageProfile, "/imaging/set": p.API_setImageProfile, } }