diff --git a/config/config.go b/config/config.go index 48a053f..52d0d76 100644 --- a/config/config.go +++ b/config/config.go @@ -190,6 +190,12 @@ func (c *Config) Load() *Config { if camConfig.GetBool("rawTcp") { camera.BrokenHttp = true } + if camConfig.GetBool("publishImages") { + camera.PublishImages = true + } + if camConfig.GetBool("sendXML") { + camera.SendXML = true + } if myConfig.Debug { fmt.Printf("Added Hikvision camera:\n"+ " name: %s \n"+ diff --git a/docs/config.yaml b/docs/config.yaml index b02aeff..54157be 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -9,6 +9,7 @@ hikvision: username: admin password: admin1234 rawTcp: false + publishImages: true myDoorbell: address: 192.168.1.13 https: false @@ -16,6 +17,9 @@ hikvision: password: admin666 # USE RAW TCP IF HTTP STREAMING DOES NOT WORK rawTcp: true + publishImages: false + # SEND XML from device in message rather than just eventDescription + sendXML: true hisilicon: enabled: true diff --git a/main.go b/main.go index 0e373e3..5190bae 100644 --- a/main.go +++ b/main.go @@ -47,12 +47,18 @@ func main() { } } - messageHandler := func(cameraName string, eventType string, extra string) { + messageHandler := func(cameraName string, eventType string, extra interface{}) { if config.Mqtt.Enabled { mqttBus.SendMessage(config.Mqtt.TopicRoot+"/"+cameraName+"/"+eventType, extra) } - if config.Webhooks.Enabled { - webhookBus.SendMessage(cameraName, eventType, extra) + if config.Webhooks.Enabled { + switch v := extra.(type) { + case string: + webhookBus.SendMessage(cameraName, eventType, v) + default: + webhookBus.SendMessage(cameraName, eventType, "") + } + } } diff --git a/servers/dahua/server.go b/servers/dahua/server.go index 1f3b527..99010ed 100644 --- a/servers/dahua/server.go +++ b/servers/dahua/server.go @@ -28,7 +28,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Cameras *[]DhCamera - MessageHandler func(cameraName string, eventType string, extra string) + MessageHandler func(cameraName string, eventType string, extra interface{}) } type DhEvent struct { @@ -254,7 +254,7 @@ func (server *Server) Start() { if server.MessageHandler == nil { fmt.Println("DAHUA: Message handler is not set for Dahua cams - that's probably not what you want") - server.MessageHandler = func(cameraName string, eventType string, extra string) { + server.MessageHandler = func(cameraName string, eventType string, extra interface{}) { fmt.Printf("DAHUA: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } diff --git a/servers/ftp/server.go b/servers/ftp/server.go index 314d352..7604b27 100644 --- a/servers/ftp/server.go +++ b/servers/ftp/server.go @@ -13,7 +13,7 @@ type Server struct { AllowFiles bool RootPath string Password string - MessageHandler func(cameraName string, eventType string, extra string) + MessageHandler func(cameraName string, eventType string, extra interface{}) } type Event struct { @@ -25,7 +25,7 @@ type Event struct { func (serv *Server) Start() { if serv.MessageHandler == nil { fmt.Println("FTP: Message handler is not set for FTP server - that's probably not what you want") - serv.MessageHandler = func(cameraName string, eventType string, extra string) { + serv.MessageHandler = func(cameraName string, eventType string, extra interface{}) { fmt.Printf("FTP: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } diff --git a/servers/hikvision/httpEventReader.go b/servers/hikvision/httpEventReader.go index b17c6fd..1b83daf 100644 --- a/servers/hikvision/httpEventReader.go +++ b/servers/hikvision/httpEventReader.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/icholy/digest" "io" + "strings" + "bytes" "log" "mime" "mime/multipart" @@ -17,6 +19,57 @@ type HttpEventReader struct { client *http.Client } +func BoundaryFilter(r io.Reader, boundary string) io.Reader{ + b := make([]byte, len(boundary)+2) + copy(b, "--") + copy(b[2:], boundary) + return &boundaryFilter{r, b, true} +} + +type boundaryFilter struct { + r io.Reader + boundary []byte + hasEndBoundary bool +} + + +// This Hikvision camera has a bug in that it sends an extra boundary at the end of +// its linedetect alarm, after it has sent the image. This code detects that it has +// already sent the boundary and removes the boundary from the next buffer +func(t *boundaryFilter) Read(p []byte) (n int, err error) { + buf := make([]byte, len(p)) + n, err = t.r.Read(buf) + if n > 0 { + i := 0 + //fmt.Println(string(buf[:n])) + //fmt.Println("------------------------------------------------") + for { + pos1 := bytes.Index(buf[i:], t.boundary) + if pos1 == 0 && t.hasEndBoundary { + copy(buf[i:], buf[i+len(t.boundary)+2:]) + n -= len(t.boundary) + 2 + //fmt.Println("remove boundary from start, we sent it at the end of last buffer") + t.hasEndBoundary = false + continue + } else if pos1 >= 0 { + pos2 := bytes.Index(buf[i+pos1+len(t.boundary):], t.boundary) + //fmt.Printf("Found boundary at %d %d\n", pos1, pos2) + if pos2 >= 0 { + pos2 += i + pos1 + len(t.boundary) + //fmt.Printf("i:%d pos1:%d pos2: %d len:%d n-pos2-len:%d\n", i, pos1, pos2, len(t.boundary), n - pos2 - len(t.boundary)) + t.hasEndBoundary = n - pos2 - len(t.boundary) <= 2 + i = pos2 + continue + } + } + break + } + copy(p, buf[:n]) + } + return n, err +} + + func (eventReader *HttpEventReader) ReadEvents(camera *HikCamera, channel chan<- HikEvent, callback func()) { if eventReader.client == nil { eventReader.client = &http.Client{} @@ -59,56 +112,90 @@ func (eventReader *HttpEventReader) ReadEvents(camera *HikCamera, channel chan<- callback() return } - multipartBoundary := params["boundary"] + multipartBoundary := params["boundary"] xmlEvent := XmlEvent{} // READ PART BY PART - multipartReader := multipart.NewReader(response.Body, multipartBoundary) + multipartReader := multipart.NewReader(BoundaryFilter(response.Body, multipartBoundary), multipartBoundary) for { part, err := multipartReader.NextPart() if err == io.EOF { break } if err != nil { + if strings.Contains(err.Error(),"connection reset by peer") { + break + } fmt.Println(err) continue } + contentType := part.Header.Get("Content-Type") contentLength, _ := strconv.Atoi(part.Header.Get("Content-Length")) body := make([]byte, contentLength) - _, err = part.Read(body) - if err != nil { - fmt.Println(err) + pos := 0 + n := 0 + for pos < contentLength { + n, err = part.Read(body[pos:]) + pos += n + if err != nil { + break + } + } + if pos + 2 < contentLength { continue } - err = xml.Unmarshal(body, &xmlEvent) - if err != nil { - fmt.Println(err) + if strings.Contains(contentType, "image/jpeg") { + if camera.PublishImages { + _, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition")) + if err == nil { + filename, ok := params["filename"] + if ok { + if eventReader.Debug { + fmt.Println("HIK: Sending an image: " + filename) + } + event := HikEvent{Camera: camera} + event.Type = "image/" + filename + event.Message = body + channel <- event + } + } + } continue } - // FILL IN THE CAMERA INTO FRESHLY-UNMARSHALLED EVENT - xmlEvent.Camera = camera + if strings.Contains(contentType, "application/xml") { + err = xml.Unmarshal(body, &xmlEvent) + if err != nil { + fmt.Println(err) + continue + } - if eventReader.Debug { - log.Printf("%s event: %s (%s - %d)", xmlEvent.Camera.Name, xmlEvent.Type, xmlEvent.State, xmlEvent.Id) - } + // FILL IN THE CAMERA INTO FRESHLY-UNMARSHALLED EVENT + xmlEvent.Camera = camera - switch xmlEvent.State { - case "active": - if !xmlEvent.Active { + if eventReader.Debug { + log.Printf("%s event: %s (%s - %d)", xmlEvent.Camera.Name, xmlEvent.Type, xmlEvent.State, xmlEvent.Id) + } + + switch xmlEvent.State { + case "active": if eventReader.Debug { fmt.Println("HIK: SENDING CAMERA EVENT!") } event := HikEvent{Camera: camera} event.Type = xmlEvent.Type - event.Message = xmlEvent.Description + if camera.SendXML { + event.Message = string(body) + } else { + event.Message = xmlEvent.Description + } channel <- event + xmlEvent.Active = true + case "inactive": + xmlEvent.Active = false } - xmlEvent.Active = true - case "inactive": - xmlEvent.Active = false } } } diff --git a/servers/hikvision/server.go b/servers/hikvision/server.go index dd0ff48..c78af05 100644 --- a/servers/hikvision/server.go +++ b/servers/hikvision/server.go @@ -18,18 +18,20 @@ const ( ) type HikCamera struct { - Name string `json:"name"` - Url string `json:"url"` - Username string `json:"username"` - Password string `json:"password"` - EventReader HikEventReader - BrokenHttp bool - AuthMethod HttpAuthMethod + Name string `json:"name"` + Url string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` + EventReader HikEventReader + BrokenHttp bool + AuthMethod HttpAuthMethod + PublishImages bool + SendXML bool } type HikEvent struct { Type string - Message string + Message interface{} Camera *HikCamera } @@ -37,7 +39,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Cameras *[]HikCamera - MessageHandler func(cameraName string, eventType string, extra string) + MessageHandler func(cameraName string, eventType string, extra interface{}) } type XmlEvent struct { @@ -153,7 +155,7 @@ func (server *Server) Start() { if server.MessageHandler == nil { fmt.Println("HIK: Message handler is not set for Hikvision cams - that's probably not what you want") - server.MessageHandler = func(cameraName string, eventType string, extra string) { + server.MessageHandler = func(cameraName string, eventType string, extra interface{}) { fmt.Printf("HIK: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } diff --git a/servers/hisilicon/server.go b/servers/hisilicon/server.go index 39ee4a3..26712cb 100644 --- a/servers/hisilicon/server.go +++ b/servers/hisilicon/server.go @@ -41,7 +41,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Port string - MessageHandler func(cameraName string, eventType string, extra string) + MessageHandler func(cameraName string, eventType string, extra interface{}) } func (server *Server) handleTcpConnection(conn net.Conn) { @@ -102,7 +102,7 @@ func (server *Server) Start() { } if server.MessageHandler == nil { fmt.Println("HISI: Message handler is not set for HiSilicon cams - that's probably not what you want") - server.MessageHandler = func(cameraName string, eventType string, extra string) { + server.MessageHandler = func(cameraName string, eventType string, extra interface{}) { fmt.Printf("HISI: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } }