Files
monibuca/plugin/gb28181/device.go
2024-07-26 10:21:10 +08:00

213 lines
6.1 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 plugin_gb28181
import (
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"log/slog"
"m7s.live/m7s/v5/pkg/util"
gb28181 "m7s.live/m7s/v5/plugin/gb28181/pkg"
"net/http"
"strings"
"time"
)
type DeviceStatus string
const (
DeviceRegisterStatus DeviceStatus = "REGISTER"
DeviceRecoverStatus DeviceStatus = "RECOVER"
DeviceOnlineStatus DeviceStatus = "ONLINE"
DeviceOfflineStatus DeviceStatus = "OFFLINE"
DeviceAlarmedStatus DeviceStatus = "ALARMED"
)
type Device struct {
ID string
Name string
Manufacturer string
Model string
Owner string
RegisterTime, UpdateTime time.Time
LastKeepaliveAt time.Time
Status DeviceStatus
SN int
Recipient sip.Uri
Transport string
channels util.Collection[string, *Channel]
mediaIp string
GpsTime time.Time //gps时间
Longitude, Latitude string //经度,纬度
*slog.Logger
eventChan chan any
dialogClient *sipgo.DialogClient
}
func (d *Device) GetKey() string {
return d.ID
}
func (d *Device) onMessage(req *sip.Request, tx sip.ServerTransaction, msg *gb28181.Message) (err error) {
var body []byte
if d.Status == DeviceRecoverStatus {
d.Status = DeviceOnlineStatus
}
switch msg.CmdType {
case "Keepalive":
d.LastKeepaliveAt = time.Now()
case "Catalog":
d.eventChan <- msg.DeviceList
case "RecordInfo":
if channel, ok := d.channels.Get(msg.DeviceID); ok {
if req, ok := channel.RecordReqs.Get(msg.SN); ok {
req.Resolve(msg.RecordList)
}
}
case "DeviceInfo":
// 主设备信息
d.Name = msg.DeviceName
d.Manufacturer = msg.Manufacturer
d.Model = msg.Model
case "Alarm":
d.Status = DeviceAlarmedStatus
body = []byte(gb28181.BuildAlarmResponseXML(d.ID))
case "Broadcast":
d.Info("broadcast message", "body", req.Body())
default:
d.Warn("Not supported CmdType", "CmdType", msg.CmdType, "body", req.Body())
err = tx.Respond(sip.NewResponseFromRequest(req, http.StatusBadRequest, "", nil))
return
}
err = tx.Respond(sip.NewResponseFromRequest(req, http.StatusOK, "OK", body))
return
}
func (d *Device) eventLoop(gb *GB28181Plugin) {
send := func(req *sip.Request) (*sip.Response, error) {
return gb.client.Do(gb, req)
}
defer func() {
d.Status = DeviceOfflineStatus
if gb.devices.RemoveByKey(d.ID) {
d.Info("Unregister")
}
}()
response, err := d.catalog(send)
if err != nil {
d.Error("catalog", "err", err)
} else {
d.Debug("catalog", "response", response)
}
response, err = d.queryDeviceInfo(send)
if err != nil {
d.Error("deviceInfo", "err", err)
} else {
d.Debug("deviceInfo", "response", response)
}
subTick := time.NewTicker(time.Second * 3600)
defer subTick.Stop()
catalogTick := time.NewTicker(time.Second * 60)
defer catalogTick.Stop()
for {
select {
case <-subTick.C:
response, err = d.subscribeCatalog(send)
if err != nil {
d.Error("subCatalog", "err", err)
} else {
d.Debug("subCatalog", "response", response)
}
response, err = d.subscribePosition(int(gb.Position.Interval/time.Second), send)
if err != nil {
d.Error("subPosition", "err", err)
} else {
d.Debug("subPosition", "response", response)
}
case <-catalogTick.C:
if time.Since(d.LastKeepaliveAt) > time.Second*3600 {
d.Error("keepalive timeout")
return
}
response, err = d.catalog(send)
if err != nil {
d.Error("catalog", "err", err)
} else {
d.Debug("catalog", "response", response)
}
case event, ok := <-d.eventChan:
if !ok {
return
}
switch v := event.(type) {
case []gb28181.ChannelInfo:
for _, c := range v {
//当父设备非空且存在时、父设备节点增加通道
if c.ParentID != "" {
path := strings.Split(c.ParentID, "/")
parentId := path[len(path)-1]
//如果父ID并非本身所属设备一般情况下这是因为下级设备上传了目录信息该信息通常不需要处理。
// 暂时不考虑级联目录的实现
if d.ID != parentId {
if parent, ok := gb.devices.Get(parentId); ok {
parent.addOrUpdateChannel(c)
continue
} else {
c.Model = "Directory " + c.Model
c.Status = "NoParent"
}
}
}
d.addOrUpdateChannel(c)
}
}
}
}
}
func (d *Device) createRequest(Method sip.RequestMethod) (req *sip.Request) {
req = sip.NewRequest(Method, d.Recipient)
contentType := sip.ContentTypeHeader("Application/MANSCDP+xml")
req.AppendHeader(&contentType)
return
}
func (d *Device) catalog(send func(*sip.Request) (*sip.Response, error)) (*sip.Response, error) {
request := d.createRequest(sip.MESSAGE)
//d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
request.AppendHeader(sip.NewHeader("Expires", "3600"))
request.SetBody([]byte(gb28181.BuildCatalogXML(d.SN, d.ID)))
return send(request)
}
func (d *Device) subscribeCatalog(send func(*sip.Request) (*sip.Response, error)) (*sip.Response, error) {
request := d.createRequest(sip.SUBSCRIBE)
request.AppendHeader(sip.NewHeader("Expires", "3600"))
request.SetBody([]byte(gb28181.BuildCatalogXML(d.SN, d.ID)))
return send(request)
}
func (d *Device) queryDeviceInfo(send func(*sip.Request) (*sip.Response, error)) (*sip.Response, error) {
request := d.createRequest(sip.MESSAGE)
request.SetBody([]byte(gb28181.BuildDeviceInfoXML(d.SN, d.ID)))
return send(request)
}
func (d *Device) subscribePosition(interval int, send func(*sip.Request) (*sip.Response, error)) (*sip.Response, error) {
request := d.createRequest(sip.SUBSCRIBE)
request.AppendHeader(sip.NewHeader("Expires", "3600"))
request.SetBody([]byte(gb28181.BuildDevicePositionXML(d.SN, d.ID, interval)))
return send(request)
}
func (d *Device) addOrUpdateChannel(c gb28181.ChannelInfo) {
if channel, ok := d.channels.Get(c.DeviceID); ok {
channel.ChannelInfo = c
} else {
channel = &Channel{
Device: d,
Logger: d.Logger.With("channel", c.DeviceID),
ChannelInfo: c,
}
d.channels.Add(channel)
}
}