Files
monibuca/plugin/onvif/api.go

640 lines
14 KiB
Go

package plugin_onvif
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"github.com/jinzhu/copier"
"github.com/kerberos-io/onvif/xsd/onvif"
)
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,
// }
//}