mirror of
				https://github.com/Warky-Devs/nvr-notify-api.git
				synced 2025-11-04 10:23:54 +00:00 
			
		
		
		
	Added /hikvision/alarm
This commit is contained in:
		
							parent
							
								
									9f02e73dc3
								
							
						
					
					
						commit
						b81d6afecb
					
				@ -3,12 +3,14 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"encoding/xml"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,6 +24,9 @@ type Config struct {
 | 
				
			|||||||
	TelegramEnabled bool   `json:"telegram_enabled"`
 | 
						TelegramEnabled bool   `json:"telegram_enabled"`
 | 
				
			||||||
	TelegramToken   string `json:"telegram_token"`
 | 
						TelegramToken   string `json:"telegram_token"`
 | 
				
			||||||
	TelegramChatID  string `json:"telegram_chat_id"`
 | 
						TelegramChatID  string `json:"telegram_chat_id"`
 | 
				
			||||||
 | 
						HikEnabled      bool   `json:"hik_enabled"`
 | 
				
			||||||
 | 
						HikUsername     string `json:"hik_username"`
 | 
				
			||||||
 | 
						HikPassword     string `json:"hik_password"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// VivotekEvent represents the event data structure from Vivotek NVR
 | 
					// VivotekEvent represents the event data structure from Vivotek NVR
 | 
				
			||||||
@ -34,6 +39,34 @@ type VivotekEvent struct {
 | 
				
			|||||||
	// Add more fields as needed based on Vivotek's event structure
 | 
						// Add more fields as needed based on Vivotek's event structure
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HikVisionEvent represents the alarm data structure from HIKVision
 | 
				
			||||||
 | 
					type HikVisionEvent struct {
 | 
				
			||||||
 | 
						EventType    string                 `json:"eventType"`
 | 
				
			||||||
 | 
						EventTime    time.Time              `json:"eventTime"`
 | 
				
			||||||
 | 
						DeviceID     string                 `json:"deviceId"`
 | 
				
			||||||
 | 
						ChannelID    string                 `json:"channelId"`
 | 
				
			||||||
 | 
						EventDetails map[string]interface{} `json:"eventDetails"`
 | 
				
			||||||
 | 
						// Raw XML data for debugging/logging
 | 
				
			||||||
 | 
						RawXML string `json:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HIKVisionAlarm represents the XML structure of a HIKVision alarm event
 | 
				
			||||||
 | 
					type HIKVisionAlarm struct {
 | 
				
			||||||
 | 
						XMLName          xml.Name `xml:"EventNotificationAlert"`
 | 
				
			||||||
 | 
						IPAddress        string   `xml:"ipAddress"`
 | 
				
			||||||
 | 
						PortNo           int      `xml:"portNo"`
 | 
				
			||||||
 | 
						ProtocolType     string   `xml:"protocolType"`
 | 
				
			||||||
 | 
						MacAddress       string   `xml:"macAddress"`
 | 
				
			||||||
 | 
						ChannelID        int      `xml:"channelID"`
 | 
				
			||||||
 | 
						DateTime         string   `xml:"dateTime"`
 | 
				
			||||||
 | 
						ActivePostCount  int      `xml:"activePostCount"`
 | 
				
			||||||
 | 
						EventType        string   `xml:"eventType"`
 | 
				
			||||||
 | 
						EventState       string   `xml:"eventState"`
 | 
				
			||||||
 | 
						EventDescription string   `xml:"eventDescription"`
 | 
				
			||||||
 | 
						// Optional fields that may be present in some events
 | 
				
			||||||
 | 
						DetectionRegionID int `xml:"detectionRegionID,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GlobalState maintains the application state
 | 
					// GlobalState maintains the application state
 | 
				
			||||||
type GlobalState struct {
 | 
					type GlobalState struct {
 | 
				
			||||||
	Config     Config
 | 
						Config     Config
 | 
				
			||||||
@ -42,13 +75,14 @@ type GlobalState struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var state GlobalState
 | 
					var state GlobalState
 | 
				
			||||||
 | 
					var startTime time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// initConfig loads configuration from a JSON file
 | 
					// initConfig loads configuration from a JSON file
 | 
				
			||||||
func initConfig() error {
 | 
					func initConfig() error {
 | 
				
			||||||
	// Default configuration
 | 
						// Default configuration
 | 
				
			||||||
	state.Config = Config{
 | 
						state.Config = Config{
 | 
				
			||||||
		ServerPort: "8080",
 | 
							ServerPort: "8080",
 | 
				
			||||||
		LogFile:    "vivotek_events.log",
 | 
							LogFile:    "nvr_events.log",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Try to load from config file if it exists
 | 
						// Try to load from config file if it exists
 | 
				
			||||||
@ -74,9 +108,7 @@ func initConfig() error {
 | 
				
			|||||||
		logOutput = file
 | 
							logOutput = file
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state.Logger = log.New(logOutput, "VIVOTEK-API: ", log.LstdFlags)
 | 
						state.Logger = log.New(logOutput, "NVR-API: ", log.LstdFlags)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	fmt.Printf("Config loaded, handing off logs to %s...\n", state.Config.LogFile)
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,7 +123,7 @@ func basicAuth(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		username, password, ok := r.BasicAuth()
 | 
							username, password, ok := r.BasicAuth()
 | 
				
			||||||
		if !ok || username != state.Config.AuthUsername || password != state.Config.AuthPassword {
 | 
							if !ok || username != state.Config.AuthUsername || password != state.Config.AuthPassword {
 | 
				
			||||||
			w.Header().Set("WWW-Authenticate", `Basic realm="Vivotek NVR API"`)
 | 
								w.Header().Set("WWW-Authenticate", `Basic realm="NVR API"`)
 | 
				
			||||||
			w.WriteHeader(http.StatusUnauthorized)
 | 
								w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
			w.Write([]byte("Unauthorized"))
 | 
								w.Write([]byte("Unauthorized"))
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -145,22 +177,187 @@ func handleEvent(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	json.NewEncoder(w).Encode(response)
 | 
						json.NewEncoder(w).Encode(response)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// processEvent handles different event types
 | 
					// handleHikVisionAlarm processes alarm events from HIKVision devices
 | 
				
			||||||
func processEvent(event *VivotekEvent) {
 | 
					func handleHikVisionAlarm(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	switch event.EventType {
 | 
						if r.Method != http.MethodPost && r.Method != http.MethodGet {
 | 
				
			||||||
	case "MotionDetection":
 | 
							w.WriteHeader(http.StatusMethodNotAllowed)
 | 
				
			||||||
		handleMotionEvent(event)
 | 
							w.Write([]byte("Only POST and GET methods are supported"))
 | 
				
			||||||
	case "VideoLoss":
 | 
							return
 | 
				
			||||||
		handleVideoLossEvent(event)
 | 
					 | 
				
			||||||
	case "DeviceConnection":
 | 
					 | 
				
			||||||
		handleConnectionEvent(event)
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		state.Logger.Printf("Unhandled event type: %s", event.EventType)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Forward to notification URL if configured
 | 
						// Check for specific HIK authentication if enabled
 | 
				
			||||||
	if state.Config.NotifyURL != "" {
 | 
						if state.Config.HikEnabled && state.Config.HikUsername != "" {
 | 
				
			||||||
		forwardEvent(event)
 | 
							username, password, ok := r.BasicAuth()
 | 
				
			||||||
 | 
							if !ok || username != state.Config.HikUsername || password != state.Config.HikPassword {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
								w.Write([]byte("Unauthorized for HIKVision integration"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read the request body
 | 
				
			||||||
 | 
						body, err := io.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							state.Logger.Printf("Error reading HIKVision request body: %v", err)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the XML alarm data
 | 
				
			||||||
 | 
						var hikAlarm HIKVisionAlarm
 | 
				
			||||||
 | 
						err = xml.Unmarshal(body, &hikAlarm)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							state.Logger.Printf("Error parsing HIKVision XML: %v", err)
 | 
				
			||||||
 | 
							state.Logger.Printf("Raw payload: %s", string(body))
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Convert to our standard event format
 | 
				
			||||||
 | 
						event := convertHikVisionAlarm(hikAlarm, string(body))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Log the event
 | 
				
			||||||
 | 
						state.EventCount++
 | 
				
			||||||
 | 
						state.Logger.Printf("Received HIKVision alarm #%d: Type=%s, Device=%s, Channel=%s",
 | 
				
			||||||
 | 
							state.EventCount, event.EventType, event.DeviceID, event.ChannelID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Process the event based on type
 | 
				
			||||||
 | 
						processEvent(&event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Respond with success
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
						response := map[string]interface{}{
 | 
				
			||||||
 | 
							"status":  "success",
 | 
				
			||||||
 | 
							"message": "HIKVision alarm processed successfully",
 | 
				
			||||||
 | 
							"eventId": state.EventCount,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HIKVision may expect XML response, but most implementations work fine with JSON
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						json.NewEncoder(w).Encode(response)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// convertHikVisionAlarm converts HIKVision alarm format to our standard event format
 | 
				
			||||||
 | 
					func convertHikVisionAlarm(hikAlarm HIKVisionAlarm, rawXML string) HikVisionEvent {
 | 
				
			||||||
 | 
						// Parse the datetime from HIKVision format
 | 
				
			||||||
 | 
						eventTime, err := time.Parse("2006-01-02T15:04:05-07:00", hikAlarm.DateTime)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// If standard format fails, try alternative formats
 | 
				
			||||||
 | 
							eventTime, err = time.Parse("2006-01-02T15:04:05Z", hikAlarm.DateTime)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// If all parsing fails, use current time
 | 
				
			||||||
 | 
								eventTime = time.Now()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Map HIKVision event types to standardized types
 | 
				
			||||||
 | 
						eventType := mapHikEventType(hikAlarm.EventType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create device ID from IP if available
 | 
				
			||||||
 | 
						deviceID := fmt.Sprintf("HIK_%s", hikAlarm.IPAddress)
 | 
				
			||||||
 | 
						if hikAlarm.MacAddress != "" {
 | 
				
			||||||
 | 
							deviceID = fmt.Sprintf("HIK_%s", strings.ReplaceAll(hikAlarm.MacAddress, ":", ""))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create channel ID
 | 
				
			||||||
 | 
						channelID := fmt.Sprintf("Channel%d", hikAlarm.ChannelID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create event details map
 | 
				
			||||||
 | 
						eventDetails := map[string]interface{}{
 | 
				
			||||||
 | 
							"source":       "HIKVision",
 | 
				
			||||||
 | 
							"ipAddress":    hikAlarm.IPAddress,
 | 
				
			||||||
 | 
							"description":  hikAlarm.EventDescription,
 | 
				
			||||||
 | 
							"state":        hikAlarm.EventState,
 | 
				
			||||||
 | 
							"macAddress":   hikAlarm.MacAddress,
 | 
				
			||||||
 | 
							"originalType": hikAlarm.EventType,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add optional fields if present
 | 
				
			||||||
 | 
						if hikAlarm.DetectionRegionID > 0 {
 | 
				
			||||||
 | 
							eventDetails["regionId"] = hikAlarm.DetectionRegionID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return HikVisionEvent{
 | 
				
			||||||
 | 
							EventType:    eventType,
 | 
				
			||||||
 | 
							EventTime:    eventTime,
 | 
				
			||||||
 | 
							DeviceID:     deviceID,
 | 
				
			||||||
 | 
							ChannelID:    channelID,
 | 
				
			||||||
 | 
							EventDetails: eventDetails,
 | 
				
			||||||
 | 
							RawXML:       rawXML,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// mapHikEventType converts HIKVision event types to our standardized types
 | 
				
			||||||
 | 
					func mapHikEventType(hikType string) string {
 | 
				
			||||||
 | 
						// Map HIKVision event types to standardized types
 | 
				
			||||||
 | 
						// HIKVision has many event types, this is a simplified mapping
 | 
				
			||||||
 | 
						hikType = strings.ToLower(hikType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "motion"):
 | 
				
			||||||
 | 
							return "MotionDetection"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "videoloss"):
 | 
				
			||||||
 | 
							return "VideoLoss"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "tamper") || strings.Contains(hikType, "shelteralarm"):
 | 
				
			||||||
 | 
							return "TamperDetection"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "disk"):
 | 
				
			||||||
 | 
							return "StorageFailure"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "line") || strings.Contains(hikType, "crossing"):
 | 
				
			||||||
 | 
							return "LineCrossing"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "intrusion"):
 | 
				
			||||||
 | 
							return "IntrusionDetection"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "face"):
 | 
				
			||||||
 | 
							return "FaceDetection"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "io") || strings.Contains(hikType, "alarm"):
 | 
				
			||||||
 | 
							return "IOAlarm"
 | 
				
			||||||
 | 
						case strings.Contains(hikType, "connection"):
 | 
				
			||||||
 | 
							return "DeviceConnection"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return "UnknownEvent_" + hikType
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// processEvent handles different event types
 | 
				
			||||||
 | 
					func processEvent(event interface{}) {
 | 
				
			||||||
 | 
						// Process based on event type
 | 
				
			||||||
 | 
						switch e := event.(type) {
 | 
				
			||||||
 | 
						case *VivotekEvent:
 | 
				
			||||||
 | 
							switch e.EventType {
 | 
				
			||||||
 | 
							case "MotionDetection":
 | 
				
			||||||
 | 
								handleMotionEvent(e)
 | 
				
			||||||
 | 
							case "VideoLoss":
 | 
				
			||||||
 | 
								handleVideoLossEvent(e)
 | 
				
			||||||
 | 
							case "DeviceConnection":
 | 
				
			||||||
 | 
								handleConnectionEvent(e)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								state.Logger.Printf("Unhandled Vivotek event type: %s", e.EventType)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Forward to notification URL if configured
 | 
				
			||||||
 | 
							if state.Config.NotifyURL != "" {
 | 
				
			||||||
 | 
								forwardEvent(e)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case *HikVisionEvent:
 | 
				
			||||||
 | 
							switch e.EventType {
 | 
				
			||||||
 | 
							case "MotionDetection":
 | 
				
			||||||
 | 
								handleHikMotionEvent(e)
 | 
				
			||||||
 | 
							case "VideoLoss":
 | 
				
			||||||
 | 
								handleHikVideoLossEvent(e)
 | 
				
			||||||
 | 
							case "LineCrossing", "IntrusionDetection":
 | 
				
			||||||
 | 
								handleHikSmartEvent(e)
 | 
				
			||||||
 | 
							case "IOAlarm":
 | 
				
			||||||
 | 
								handleHikIOAlarmEvent(e)
 | 
				
			||||||
 | 
							case "DeviceConnection":
 | 
				
			||||||
 | 
								handleHikConnectionEvent(e)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								state.Logger.Printf("Unhandled HIKVision event type: %s", e.EventType)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Forward to notification URL if configured
 | 
				
			||||||
 | 
							if state.Config.NotifyURL != "" {
 | 
				
			||||||
 | 
								forwardHikEvent(e)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Send to Telegram if enabled
 | 
						// Send to Telegram if enabled
 | 
				
			||||||
@ -187,6 +384,37 @@ func handleConnectionEvent(event *VivotekEvent) {
 | 
				
			|||||||
	// Add custom processing for connection events
 | 
						// Add custom processing for connection events
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// handleHikMotionEvent processes HIKVision motion detection events
 | 
				
			||||||
 | 
					func handleHikMotionEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						state.Logger.Printf("HIKVision motion detected on device %s, channel %s", event.DeviceID, event.ChannelID)
 | 
				
			||||||
 | 
						// Add custom processing for HIKVision motion events
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// handleHikVideoLossEvent processes HIKVision video loss events
 | 
				
			||||||
 | 
					func handleHikVideoLossEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						state.Logger.Printf("HIKVision video lost on device %s, channel %s", event.DeviceID, event.ChannelID)
 | 
				
			||||||
 | 
						// Add custom processing for HIKVision video loss events
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// handleHikSmartEvent processes HIKVision smart events (line crossing, intrusion)
 | 
				
			||||||
 | 
					func handleHikSmartEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						state.Logger.Printf("HIKVision smart event %s on device %s, channel %s",
 | 
				
			||||||
 | 
							event.EventType, event.DeviceID, event.ChannelID)
 | 
				
			||||||
 | 
						// Add custom processing for HIKVision smart events
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// handleHikIOAlarmEvent processes HIKVision IO alarm events
 | 
				
			||||||
 | 
					func handleHikIOAlarmEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						state.Logger.Printf("HIKVision IO alarm on device %s, channel %s", event.DeviceID, event.ChannelID)
 | 
				
			||||||
 | 
						// Add custom processing for HIKVision IO events
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// handleHikConnectionEvent processes HIKVision device connection events
 | 
				
			||||||
 | 
					func handleHikConnectionEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						state.Logger.Printf("HIKVision connection event for device %s", event.DeviceID)
 | 
				
			||||||
 | 
						// Add custom processing for HIKVision connection events
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// forwardEvent sends the event to a configured notification URL
 | 
					// forwardEvent sends the event to a configured notification URL
 | 
				
			||||||
func forwardEvent(event *VivotekEvent) {
 | 
					func forwardEvent(event *VivotekEvent) {
 | 
				
			||||||
	eventJSON, err := json.Marshal(event)
 | 
						eventJSON, err := json.Marshal(event)
 | 
				
			||||||
@ -207,9 +435,29 @@ func forwardEvent(event *VivotekEvent) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// forwardHikEvent forwards HIKVision events to notification URL
 | 
				
			||||||
 | 
					func forwardHikEvent(event *HikVisionEvent) {
 | 
				
			||||||
 | 
						eventJSON, err := json.Marshal(event)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							state.Logger.Printf("Error serializing HIKVision event for forwarding: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := http.Post(state.Config.NotifyURL, "application/json", bytes.NewBuffer(eventJSON))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							state.Logger.Printf("Error forwarding HIKVision event: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if resp.StatusCode >= 400 {
 | 
				
			||||||
 | 
							state.Logger.Printf("Error response from notification URL for HIKVision event: %d", resp.StatusCode)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// sendTelegramNotification sends event information to a Telegram chat/bot
 | 
					// sendTelegramNotification sends event information to a Telegram chat/bot
 | 
				
			||||||
func sendTelegramNotification(event *VivotekEvent) {
 | 
					func sendTelegramNotification(event interface{}) {
 | 
				
			||||||
	// Format the message
 | 
						// Format the message based on event type
 | 
				
			||||||
	message := formatTelegramMessage(event)
 | 
						message := formatTelegramMessage(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Construct the Telegram Bot API URL
 | 
						// Construct the Telegram Bot API URL
 | 
				
			||||||
@ -234,48 +482,111 @@ func sendTelegramNotification(event *VivotekEvent) {
 | 
				
			|||||||
		body, _ := io.ReadAll(resp.Body)
 | 
							body, _ := io.ReadAll(resp.Body)
 | 
				
			||||||
		state.Logger.Printf("Telegram API error: status=%d, response=%s", resp.StatusCode, string(body))
 | 
							state.Logger.Printf("Telegram API error: status=%d, response=%s", resp.StatusCode, string(body))
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		state.Logger.Printf("Telegram notification sent successfully for event type %s", event.EventType)
 | 
							// Log success based on event type
 | 
				
			||||||
 | 
							switch e := event.(type) {
 | 
				
			||||||
 | 
							case *VivotekEvent:
 | 
				
			||||||
 | 
								state.Logger.Printf("Telegram notification sent successfully for Vivotek event type %s", e.EventType)
 | 
				
			||||||
 | 
							case *HikVisionEvent:
 | 
				
			||||||
 | 
								state.Logger.Printf("Telegram notification sent successfully for HIKVision event type %s", e.EventType)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								state.Logger.Printf("Telegram notification sent successfully for unknown event type")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// formatTelegramMessage creates a human-readable message for Telegram
 | 
					// formatTelegramMessage creates a human-readable message for Telegram
 | 
				
			||||||
func formatTelegramMessage(event *VivotekEvent) string {
 | 
					func formatTelegramMessage(event interface{}) string {
 | 
				
			||||||
	// Basic message with event details
 | 
						var message string
 | 
				
			||||||
	message := fmt.Sprintf("<b>🚨 Vivotek NVR Alert</b>\n\n"+
 | 
					 | 
				
			||||||
		"<b>Event:</b> %s\n"+
 | 
					 | 
				
			||||||
		"<b>Time:</b> %s\n"+
 | 
					 | 
				
			||||||
		"<b>Device:</b> %s\n"+
 | 
					 | 
				
			||||||
		"<b>Channel:</b> %s\n",
 | 
					 | 
				
			||||||
		event.EventType,
 | 
					 | 
				
			||||||
		event.EventTime.Format("2006-01-02 15:04:05"),
 | 
					 | 
				
			||||||
		event.DeviceID,
 | 
					 | 
				
			||||||
		event.ChannelID)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Add custom message based on event type
 | 
						switch e := event.(type) {
 | 
				
			||||||
	switch event.EventType {
 | 
						case *VivotekEvent:
 | 
				
			||||||
	case "MotionDetection":
 | 
							// Basic message with event details
 | 
				
			||||||
		message += "📹 <b>Motion detected!</b>"
 | 
							message = fmt.Sprintf("<b>🚨 NVR Alert</b>\n\n"+
 | 
				
			||||||
 | 
								"<b>Event:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Time:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Device:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Channel:</b> %s\n",
 | 
				
			||||||
 | 
								e.EventType,
 | 
				
			||||||
 | 
								e.EventTime.Format("2006-01-02 15:04:05"),
 | 
				
			||||||
 | 
								e.DeviceID,
 | 
				
			||||||
 | 
								e.ChannelID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Add zone info if available
 | 
							// Add custom message based on event type
 | 
				
			||||||
		if zone, ok := event.EventDetails["zoneId"].(string); ok {
 | 
							switch e.EventType {
 | 
				
			||||||
			message += fmt.Sprintf(" (Zone: %s)", zone)
 | 
							case "MotionDetection":
 | 
				
			||||||
 | 
								message += "📹 <b>Motion detected!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add zone info if available
 | 
				
			||||||
 | 
								if zone, ok := e.EventDetails["zoneId"].(string); ok {
 | 
				
			||||||
 | 
									message += fmt.Sprintf(" (Zone: %s)", zone)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "VideoLoss":
 | 
				
			||||||
 | 
								message += "⚠️ <b>Video signal lost!</b> Please check camera connection."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "DeviceConnection":
 | 
				
			||||||
 | 
								if status, ok := e.EventDetails["status"].(string); ok && status == "disconnected" {
 | 
				
			||||||
 | 
									message += "❌ <b>Device disconnected!</b> Network issue possible."
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									message += "✅ <b>Device connected</b> and operating normally."
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								// Add any available details for unknown event types
 | 
				
			||||||
 | 
								detailsJSON, _ := json.Marshal(e.EventDetails)
 | 
				
			||||||
 | 
								if len(detailsJSON) > 0 {
 | 
				
			||||||
 | 
									message += fmt.Sprintf("\n<pre>%s</pre>", string(detailsJSON))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case "VideoLoss":
 | 
						case *HikVisionEvent:
 | 
				
			||||||
		message += "⚠️ <b>Video signal lost!</b> Please check camera connection."
 | 
							// HIKVision specific formatting
 | 
				
			||||||
 | 
							message = fmt.Sprintf("<b>🔔 HIKVision Alarm</b>\n\n"+
 | 
				
			||||||
 | 
								"<b>Event:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Time:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Device:</b> %s\n"+
 | 
				
			||||||
 | 
								"<b>Channel:</b> %s\n",
 | 
				
			||||||
 | 
								e.EventType,
 | 
				
			||||||
 | 
								e.EventTime.Format("2006-01-02 15:04:05"),
 | 
				
			||||||
 | 
								e.DeviceID,
 | 
				
			||||||
 | 
								e.ChannelID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case "DeviceConnection":
 | 
							// Add description if available
 | 
				
			||||||
		if status, ok := event.EventDetails["status"].(string); ok && status == "disconnected" {
 | 
							if desc, ok := e.EventDetails["description"].(string); ok && desc != "" {
 | 
				
			||||||
			message += "❌ <b>Device disconnected!</b> Network issue possible."
 | 
								message += fmt.Sprintf("<b>Description:</b> %s\n", desc)
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			message += "✅ <b>Device connected</b> and operating normally."
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
							// Add custom message based on event type
 | 
				
			||||||
		// Add any available details for unknown event types
 | 
							switch e.EventType {
 | 
				
			||||||
		detailsJSON, _ := json.Marshal(event.EventDetails)
 | 
							case "MotionDetection":
 | 
				
			||||||
		if len(detailsJSON) > 0 {
 | 
								message += "📹 <b>Motion detected!</b>"
 | 
				
			||||||
			message += fmt.Sprintf("\n<pre>%s</pre>", string(detailsJSON))
 | 
					
 | 
				
			||||||
 | 
							case "LineCrossing":
 | 
				
			||||||
 | 
								message += "🚷 <b>Line crossing detected!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "IntrusionDetection":
 | 
				
			||||||
 | 
								message += "🚨 <b>Intrusion detected!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "FaceDetection":
 | 
				
			||||||
 | 
								message += "👤 <b>Face detected!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "IOAlarm":
 | 
				
			||||||
 | 
								message += "🔌 <b>I/O Alarm triggered!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "TamperDetection":
 | 
				
			||||||
 | 
								message += "⚠️ <b>Camera tampering detected!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "VideoLoss":
 | 
				
			||||||
 | 
								message += "⚠️ <b>Video signal lost!</b>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case "StorageFailure":
 | 
				
			||||||
 | 
								message += "💾 <b>Storage failure!</b> Check NVR hard drive."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								// For unknown events, include available details
 | 
				
			||||||
 | 
								if state, ok := e.EventDetails["state"].(string); ok {
 | 
				
			||||||
 | 
									message += fmt.Sprintf("\n<b>State:</b> %s", state)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -294,12 +605,9 @@ func healthCheck(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	json.NewEncoder(w).Encode(response)
 | 
						json.NewEncoder(w).Encode(response)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var startTime time.Time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	startTime = time.Now()
 | 
						startTime = time.Now()
 | 
				
			||||||
	fmt.Print("Starting NVR API...\n")
 | 
						fmt.Print("Starting NVR API...\n")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Initialize configuration
 | 
						// Initialize configuration
 | 
				
			||||||
	if err := initConfig(); err != nil {
 | 
						if err := initConfig(); err != nil {
 | 
				
			||||||
		log.Fatalf("Failed to initialize configuration: %v", err)
 | 
							log.Fatalf("Failed to initialize configuration: %v", err)
 | 
				
			||||||
@ -310,6 +618,9 @@ func main() {
 | 
				
			|||||||
	http.HandleFunc("/event", basicAuth(handleEvent))
 | 
						http.HandleFunc("/event", basicAuth(handleEvent))
 | 
				
			||||||
	http.HandleFunc("/events", basicAuth(handleEvent)) // Alternative endpoint
 | 
						http.HandleFunc("/events", basicAuth(handleEvent)) // Alternative endpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add HIKVision alarm server endpoint
 | 
				
			||||||
 | 
						http.HandleFunc("/hikvision/alarm/hikvision/alarm", basicAuth(handleHikVisionAlarm))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start the HTTP server
 | 
						// Start the HTTP server
 | 
				
			||||||
	serverAddr := fmt.Sprintf(":%s", state.Config.ServerPort)
 | 
						serverAddr := fmt.Sprintf(":%s", state.Config.ServerPort)
 | 
				
			||||||
	state.Logger.Printf("Starting NVR Event Handler API on %s", serverAddr)
 | 
						state.Logger.Printf("Starting NVR Event Handler API on %s", serverAddr)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user