Skip to content

Commit 407f0b9

Browse files
authored
Merge pull request #14 from toxuin/amcrest
Added Dahua/Amcrest support
2 parents 2b07e4f + b509747 commit 407f0b9

File tree

4 files changed

+256
-2
lines changed

4 files changed

+256
-2
lines changed

config/config.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"fmt"
55
"github.com/spf13/viper"
6+
"github.com/toxuin/alarmserver/servers/dahua"
67
"github.com/toxuin/alarmserver/servers/hikvision"
78
)
89

@@ -12,6 +13,7 @@ type Config struct {
1213
Webhooks WebhooksConfig `json:"webhooks"`
1314
Hisilicon HisiliconConfig `json:"hisilicon"`
1415
Hikvision HikvisionConfig `json:"hikvision"`
16+
Dahua DahuaConfig `json:"dahua"`
1517
Ftp FtpConfig `json:"ftp"`
1618
}
1719

@@ -46,6 +48,11 @@ type HikvisionConfig struct {
4648
Cams []hikvision.HikCamera `json:"cams"`
4749
}
4850

51+
type DahuaConfig struct {
52+
Enabled bool `json:"enabled"`
53+
Cams []dahua.DhCamera `json:"cams"`
54+
}
55+
4956
type FtpConfig struct {
5057
Enabled bool `json:"enabled"`
5158
Port int `json:"port"`
@@ -69,6 +76,7 @@ func (c *Config) SetDefaults() {
6976
viper.SetDefault("hisilicon.enabled", true)
7077
viper.SetDefault("hisilicon.port", 15002)
7178
viper.SetDefault("hikvision.enabled", false)
79+
viper.SetDefault("dahua.enabled", false)
7280
viper.SetDefault("ftp.enabled", false)
7381
viper.SetDefault("ftp.port", 21)
7482
viper.SetDefault("ftp.allowFiles", true)
@@ -84,7 +92,9 @@ func (c *Config) SetDefaults() {
8492
_ = viper.BindEnv("hisilicon.enabled", "HISILICON_ENABLED")
8593
_ = viper.BindEnv("hisilicon.port", "HISILICON_PORT", "TCP_PORT")
8694
_ = viper.BindEnv("hikvision.enabled", "HIKVISION_ENABLED")
87-
_ = viper.BindEnv("hikvision.cams", "HIKVISION_ENABLED")
95+
_ = viper.BindEnv("hikvision.cams", "HIKVISION_CAMS")
96+
_ = viper.BindEnv("dahua.enabled", "DAHUA_ENABLED")
97+
_ = viper.BindEnv("dahua.cams", "DAHUA_CAMS")
8898
_ = viper.BindEnv("ftp.enabled", "FTP_ENABLED")
8999
_ = viper.BindEnv("ftp.port", "FTP_PORT")
90100
_ = viper.BindEnv("ftp.allowFiles", "FTP_ALLOW_FILES")
@@ -114,6 +124,9 @@ func (c *Config) Load() *Config {
114124
Hikvision: HikvisionConfig{
115125
Enabled: viper.GetBool("hikvision.enabled"),
116126
},
127+
Dahua: DahuaConfig{
128+
Enabled: viper.GetBool("dahua.enabled"),
129+
},
117130
}
118131

119132
if viper.IsSet("mqtt") {
@@ -134,6 +147,12 @@ func (c *Config) Load() *Config {
134147
panic(fmt.Errorf("unable to decode hisilicon config, %v", err))
135148
}
136149
}
150+
if viper.IsSet("dahua") {
151+
err := viper.Sub("dahua").Unmarshal(&myConfig.Dahua)
152+
if err != nil {
153+
panic(fmt.Errorf("unable to decode dahua config, %v", err))
154+
}
155+
}
137156
if viper.IsSet("ftp") {
138157
err := viper.Sub("ftp").Unmarshal(&myConfig.Ftp)
139158
if err != nil {
@@ -145,7 +164,7 @@ func (c *Config) Load() *Config {
145164
panic("Both MQTT and Webhook buses are disabled. Nothing to do!")
146165
}
147166

148-
if !myConfig.Hisilicon.Enabled && !myConfig.Hikvision.Enabled && !myConfig.Ftp.Enabled {
167+
if !myConfig.Hisilicon.Enabled && !myConfig.Hikvision.Enabled && !myConfig.Dahua.Enabled && !myConfig.Ftp.Enabled {
149168
panic("No Servers are enabled. Nothing to do!")
150169
}
151170

@@ -203,6 +222,8 @@ func (c *Config) Printout() {
203222
" port: %s\n"+
204223
" SERVER: Hikvision - enabled: %t\n"+
205224
" camera count: %d\n"+
225+
" Dahua server enabled: %t\n"+
226+
" cams: %v\n"+
206227
" SERVER: FTP - enabled: %t\n"+
207228
" port: %d\n"+
208229
" files allowed: %t\n"+
@@ -220,6 +241,8 @@ func (c *Config) Printout() {
220241
c.Hisilicon.Port,
221242
c.Hikvision.Enabled,
222243
len(c.Hikvision.Cams),
244+
c.Dahua.Enabled,
245+
len(c.Dahua.Cams),
223246
c.Ftp.Enabled,
224247
c.Ftp.Port,
225248
c.Ftp.AllowFiles,

docs/config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ hisilicon:
2121
enabled: true
2222
port: 15002
2323

24+
dahua:
25+
enabled: true
26+
cams:
27+
myCam:
28+
address: 192.168.1.13
29+
https: false
30+
username: admin
31+
password: admin1234
32+
2433
ftp:
2534
enabled: true
2635
port: 21

main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/toxuin/alarmserver/buses/mqtt"
66
"github.com/toxuin/alarmserver/buses/webhooks"
77
conf "github.com/toxuin/alarmserver/config"
8+
"github.com/toxuin/alarmserver/servers/dahua"
89
"github.com/toxuin/alarmserver/servers/ftp"
910
"github.com/toxuin/alarmserver/servers/hikvision"
1011
"github.com/toxuin/alarmserver/servers/hisilicon"
@@ -83,6 +84,20 @@ func main() {
8384
}
8485
}
8586

87+
if config.Dahua.Enabled {
88+
// START DAHUA SERVER
89+
dhServer := dahua.Server{
90+
Debug: config.Debug,
91+
WaitGroup: &processesWaitGroup,
92+
Cameras: &config.Dahua.Cams,
93+
MessageHandler: messageHandler,
94+
}
95+
dhServer.Start()
96+
if config.Debug {
97+
fmt.Println("STARTED DAHUA SERVER")
98+
}
99+
}
100+
86101
if config.Ftp.Enabled {
87102
// START FTP SERVER
88103
ftpServer := ftp.Server{

servers/dahua/server.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package dahua
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
"mime"
8+
"mime/multipart"
9+
"net/http"
10+
"strconv"
11+
"strings"
12+
"sync"
13+
)
14+
15+
type DhCamera struct {
16+
Debug bool
17+
Name string `json:"name"`
18+
Url string `json:"url"`
19+
Username string `json:"username"`
20+
Password string `json:"password"`
21+
client *http.Client
22+
}
23+
24+
type Server struct {
25+
Debug bool
26+
WaitGroup *sync.WaitGroup
27+
Cameras *[]DhCamera
28+
MessageHandler func(topic string, data string)
29+
}
30+
31+
type DhEvent struct {
32+
Camera *DhCamera
33+
Type string
34+
Message string
35+
}
36+
37+
type Event struct {
38+
Code string
39+
Action string
40+
Index int
41+
Data string
42+
active bool
43+
}
44+
45+
func (camera *DhCamera) readEvents(channel chan<- DhEvent, callback func()) {
46+
request, err := http.NewRequest("GET", camera.Url+"/cgi-bin/eventManager.cgi?action=attach&codes=All", nil)
47+
if err != nil {
48+
fmt.Printf("DAHUA: Error: Could not connect to camera %s\n", camera.Name)
49+
fmt.Println("DAHUA: Error", err)
50+
callback()
51+
return
52+
}
53+
request.SetBasicAuth(camera.Username, camera.Password)
54+
55+
response, err := camera.client.Do(request)
56+
if err != nil {
57+
fmt.Printf("DAHUA: Error opening HTTP connection to camera %s\n", camera.Name)
58+
fmt.Println(err)
59+
return
60+
}
61+
62+
if response.StatusCode != 200 {
63+
fmt.Printf("DAHUA: Warning: Status Code was not 200, but %v\n", response.StatusCode)
64+
}
65+
66+
// FIGURE OUT MULTIPART BOUNDARY
67+
mediaType, params, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
68+
if camera.Debug {
69+
fmt.Printf("DAHUA: Media type is %s\n", mediaType)
70+
}
71+
72+
if params["boundary"] == "" {
73+
fmt.Println("DAHUA: ERROR: Camera " + camera.Name + " does not seem to support event streaming")
74+
callback()
75+
return
76+
}
77+
multipartBoundary := params["boundary"]
78+
79+
event := Event{}
80+
81+
// READ PART BY PART
82+
multipartReader := multipart.NewReader(response.Body, multipartBoundary)
83+
for {
84+
part, err := multipartReader.NextPart()
85+
if err == io.EOF {
86+
return
87+
}
88+
if err != nil {
89+
fmt.Println(err)
90+
continue
91+
}
92+
body, err := ioutil.ReadAll(part)
93+
if err != nil {
94+
fmt.Println(err)
95+
continue
96+
}
97+
98+
if camera.Debug {
99+
fmt.Printf("DAHUA: Read event body: %s\n", body)
100+
}
101+
102+
// EXAMPLE: "Code=VideoMotion; action=Start; index=0\r\n\r\n"
103+
line := strings.Trim(string(body), " \n\r")
104+
items := strings.Split(line, "; ")
105+
keyValues := make(map[string]string, len(items))
106+
for _, item := range items {
107+
parts := strings.Split(item, "=")
108+
if len(parts) > 0 {
109+
keyValues[parts[0]] = parts[1]
110+
}
111+
}
112+
// EXAMPLE: { Code: VideoMotion, action: Start, index: 0 }
113+
index := 0
114+
index, _ = strconv.Atoi(keyValues["index"])
115+
event.Code = keyValues["Code"]
116+
event.Action = keyValues["action"]
117+
event.Index = index
118+
event.Data = keyValues["data"]
119+
120+
switch event.Action {
121+
case "Start":
122+
if !event.active {
123+
if camera.Debug {
124+
fmt.Println("DAHUA: SENDING CAMERA EVENT!")
125+
}
126+
dahuaEvent := DhEvent{
127+
Camera: camera,
128+
Type: event.Code,
129+
Message: event.Data,
130+
}
131+
if dahuaEvent.Message == "" {
132+
dahuaEvent.Message = event.Action
133+
}
134+
channel <- dahuaEvent
135+
}
136+
event.active = true
137+
case "Stop":
138+
event.active = false
139+
}
140+
}
141+
}
142+
143+
func (server *Server) addCamera(waitGroup *sync.WaitGroup, cam *DhCamera, channel chan<- DhEvent) {
144+
waitGroup.Add(1)
145+
146+
if server.Debug {
147+
fmt.Printf("DAHUA: Adding camera %s: %s\n", cam.Name, cam.Url)
148+
}
149+
150+
if cam.client == nil {
151+
cam.client = &http.Client{}
152+
}
153+
154+
go func() {
155+
defer waitGroup.Done()
156+
done := false
157+
callback := func() {
158+
done = true
159+
}
160+
161+
for {
162+
if done {
163+
break
164+
}
165+
go cam.readEvents(channel, callback)
166+
}
167+
fmt.Printf("DAHUA: Closed connection to camera %s\n", cam.Name)
168+
}()
169+
}
170+
171+
func (server *Server) Start() {
172+
if server.Cameras == nil || len(*server.Cameras) == 0 {
173+
fmt.Println("DAHUA: Error: no cameras defined")
174+
return
175+
}
176+
177+
if server.MessageHandler == nil {
178+
fmt.Println("DAHUA: Message handler is not set for Dahua cams - that's probably not what you want")
179+
server.MessageHandler = func(topic string, data string) {
180+
fmt.Printf("DAHUA: Lost alarm: %s: %s\n", topic, data)
181+
}
182+
}
183+
184+
waitGroup := sync.WaitGroup{}
185+
eventChannel := make(chan DhEvent, 5)
186+
187+
for _, camera := range *server.Cameras {
188+
server.addCamera(&waitGroup, &camera, eventChannel)
189+
}
190+
191+
// START MESSAGE PROCESSOR
192+
go func(waitGroup *sync.WaitGroup, channel <-chan DhEvent) {
193+
// WAIT GROUP FOR INDIVIDUAL CAMERAS
194+
defer waitGroup.Done()
195+
196+
// EXTERNAL WAIT GROUP FOR PROCESSES
197+
defer server.WaitGroup.Done()
198+
server.WaitGroup.Add(1)
199+
200+
for {
201+
event := <-channel
202+
go server.MessageHandler(event.Camera.Name+"/"+event.Type, event.Message)
203+
}
204+
}(&waitGroup, eventChannel)
205+
206+
waitGroup.Wait()
207+
}

0 commit comments

Comments
 (0)