mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
640 lines
14 KiB
Go
640 lines
14 KiB
Go
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,
|
|
}
|
|
}
|