Page 1 of 1

Hikvision Camera Linecrossing detection

Posted: Monday 25 March 2019 13:36
by Andyyyy
Hi all,
i take the script form ccontavalli (https://github.com/ccontavalli/hicknotify) and change it a little bit to get the linecrossing function from my hikvision to domoticz on my Rpi3.

The script is written in golang.

install golang:

Code: Select all

pi@Domoticz:~ $ sudo apt install golang
copy hiknotify.go and config.json to /domoticz/scripts/ipcamera/

hiknotify.go

Code: Select all

package main

import "fmt"
import "net/http"
import "bufio"
import "sync"
import "bytes"
import "regexp"
import "strconv"
import "time"
import "encoding/json"
import "os"

type Event struct {
  etype string
  estate string
  ecount int
  camera *Camera
}

func (e *Event) Complete() bool {
  if len(e.etype) > 0 && len(e.estate) > 0 && e.ecount > 0 {
    return true
  }
  return false
}
func (e *Event) Reset() {
  *e = Event{}
}

func GenerateTimeout(config Config, camera *Camera, lc chan bool, ec chan Event) {
  count := 1
  for {
    select {
      case <-lc:
        break;

      case <-time.After(time.Second * config.WatchdogTime):
        ec <- Event{"watchdog", "lost-signal", count, camera}
        count += 1
        break

    }
  }
}

func GenerateEvents(wg *sync.WaitGroup, config Config, camera Camera, ec chan Event) {
  var last_attempt time.Time

  lc := make(chan bool)
  event := Event{camera: &camera}
  defer wg.Done()

  go GenerateTimeout(config, &camera, lc, ec)

  for {
    if time.Now().Before(last_attempt.Add(time.Second * config.ErrorRetryTime)) {
      fmt.Println("SLEEPING FOR SECONDS", config.ErrorRetryTime)
      time.Sleep(config.ErrorRetryTime * time.Second)
    }
    last_attempt = time.Now()

    client := &http.Client{}
    // This will break the stream of responses, the response has
    // to complete within 10 seconds.
    // client.Timeout = time.Second * 10

    etyper := regexp.MustCompile("eventType>(.*)</eventType")
    estater := regexp.MustCompile("eventState>(.*)</eventState")
    ecountr := regexp.MustCompile("activePostCount>(.*)</activePostCount")
	
    request, err := http.NewRequest("GET", camera.Url, nil)
    if err != nil {
      fmt.Println("REQUEST ERROR", camera, err)
      continue
    }

    request.SetBasicAuth(camera.Username, camera.Password)

    resp, err := client.Do(request)
    if err != nil {
      fmt.Println("DO ERROR", camera, err)
      continue
    }

    reader := bufio.NewReader(resp.Body)
    for {
      line, err := reader.ReadBytes('\n')
      if err != nil {
        fmt.Println("READER ERROR", camera, err)
        break
      }
      // Send keepalive
      lc <- true
      //Uncomment this line to dump all notifications
      fmt.Println(string(line))

      line = bytes.TrimRight(line, "\n\r")
	  
      etypes := etyper.FindSubmatch(line)
      if len(etypes) > 0 {
        event.etype = string(etypes[1])
      }
      estates := estater.FindSubmatch(line)
      if len(estates) > 0 {
        event.estate = string(estates[1])
      }
      ecounts := ecountr.FindSubmatch(line)
      if len(ecounts) > 0 {
        count, _ := strconv.Atoi(string(ecounts[1]))
        event.ecount = count + 1
      }
      if event.Complete() {
        if event.etype != "videoloss" {
        //if event.etype == "videoloss" {
          ec <- event
		//fmt.Println("SENDING NOTIFICATION FOR", event.etype, event.estate, event.ecount)
        }
	        event.Reset()
        event.camera = &camera
      }
    }
  }
}


type Camera struct {
  Url string
  Name string
  Username string
  Password string

}



type Config struct {
  Cameras []Camera
  DomoticzHost string
  DomoticzPort string
  DomoticzBasicAuth bool
  DomoticzUsername string
  DomoticzPassword string
  LineCrossidx string

  DampeningTime time.Duration
  ErrorRetryTime time.Duration
  WatchdogTime time.Duration
}

func LoadConfig() (Config, error) {
  file, err := os.Open("/home/pi/domoticz/scripts/ipcamera/config.json")
  if err != nil {
    return Config{}, err
  }
  decoder := json.NewDecoder(file)
  configuration := Config{}
  err = decoder.Decode(&configuration)
  if err != nil {
    return Config{}, err
  }

  if configuration.DampeningTime <= 0 {
    configuration.DampeningTime = 10
  }
  if configuration.ErrorRetryTime <= 0 {
    configuration.ErrorRetryTime = 5
  }
  if configuration.WatchdogTime <= 0 {
    configuration.WatchdogTime = 5
  }
  return configuration, nil
}

func domoticz(config Config, event Event, now time.Time) {

  if event.etype == "linedetection" && event.estate == "active" {

    fmt.Println("SENDING NOTIFICATION FOR", event, event.camera, now) 
    client := &http.Client{}
    urlstr := fmt.Sprintf("http://%s:%s/json.htm?type=command&param=switchlight&idx=%s&switchcmd=On", config.DomoticzHost, config.DomoticzPort, config.LineCrossidx)
    request, err := http.NewRequest("GET", urlstr, nil)
    if config.DomoticzBasicAuth {
	  request.SetBasicAuth(config.DomoticzUsername, config.DomoticzPassword)
	}
    fmt.Println(urlstr)
    client.Do(request)

    if err != nil {
      fmt.Println(err)
    }
  }



}


func main() {

  config, err := LoadConfig()
  if err != nil {
    fmt.Println("COULD NOT READ CONFIG", err)
    return
  }
  
  ec := make(chan Event)
  wg := &sync.WaitGroup{}
  for _, camera := range config.Cameras {
    fmt.Println("Hiknotify starting... " )
    fmt.Println("Hiknotify listening to Camera : ", config.Cameras )
    fmt.Println("Hiknotify started ..." )
    wg.Add(1)
    go GenerateEvents(wg, config, camera, ec)
  }

  type DampKey struct {
    event string
    camera *Camera
  }

  dampener := make(map[DampKey]time.Time)
  for {
    select {

      case event := <-ec:
        key := DampKey{event.etype, event.camera}
        last, ok := dampener[key]
        now := time.Now()
        if !ok || now.After(last.Add(config.DampeningTime * time.Second)) {
          domoticz(config, event, now)
          dampener[key] = now
        } else {
          fmt.Println("TIME DAMPENED ", event, key)
          dampener[key] = time.Now()
        }
        break;
    }
  }

  wg.Wait()
}
config.json

Code: Select all

{
 "Cameras": [
  {"Url": "http://ipaddress_cam/ISAPI/Event/notification/alertStream", 
   "Name": "your CameraName", 
   "Username": "username_cam", 
   "Password": "password_cam"}
],


"DampeningTime": 10,
"WatchdogTime": 10,
"ErrorRetryTime": 5,
"DomoticzHost": "ipaddress_Rpi",
"DomoticzPort": "8080",
"DomoticzBasicAuth": true,
"DomoticzUsername": "username_domo",
"DomoticzPassword": "password_domo",
"LineCrossidx": "idx_domo"
}
next you can build the script:

Code: Select all

pi@Domoticz:~/domoticz/scripts/ipcamera $ go build -o hiknotify
its time to test:

Code: Select all

pi@Domoticz:~/domoticz/scripts/ipcamera $ ./hiknotify
at least the script for autostart:
hiknotify.sh

Code: Select all

#! /bin/sh
### BEGIN INIT INFO
# Provides:          hiknotify
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Service to connect to Hikvision Camera
# Description:       Service to connect to Hikvision Camera
### END INIT INFO

case "$1" in
  start)
    echo "Start Hiknotify Service"
    # run application you want to start
    ./home/pi/domoticz/scripts/ipcamera/hiknotify &
    ;;
  stop)
    echo "Stopping Hiknotify Service"
    # kill application you want to stop
    killall hiknotify
    ;;
  *)
    echo "Usage: /etc/init.d/lgac_server{start|stop}"
    exit 1
    ;;
esac
 
exit 0
and

Code: Select all

pi@Domoticz:~/domoticz/scripts/ipcamera $ sudo cp hiknotify.sh /etc/init.d
pi@Domoticz:~/domoticz/scripts/ipcamera $ sudo chmod +x /etc/init.d/hiknotify.sh
pi@Domoticz:~/domoticz/scripts/ipcamera $ sudo update-rc.d hiknotify.sh defaults

Re: Hikvision Camera Linecrossing detection

Posted: Sunday 12 December 2021 19:02
by acoustic
Hi Andyyyy,

Could you point me in the right direction for getting this script to notify Domoticz from multiple Hikvision cameras to their respective IDX's?

I've been enjoying your efforts with this for quite some time, so a thank is in its place!

Cheers,

acoustic