mirror of
https://github.com/kerberos-io/agent.git
synced 2025-10-23 16:23:06 +08:00
cleanup comments + add ouputs
This commit is contained in:
25
README.md
25
README.md
@@ -28,8 +28,8 @@ Kerberos Agent is an isolated and scalable video (surveillance) management agent
|
|||||||
|
|
||||||
## :thinking: Prerequisites
|
## :thinking: Prerequisites
|
||||||
|
|
||||||
- An IP camera which supports a RTSP H264 encoded stream,
|
- An IP camera which supports a RTSP H264 or H265 encoded stream,
|
||||||
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can transform to a valid RTSP H264 stream](https://github.com/kerberos-io/camera-to-rtsp).
|
- (or) a USB camera, Raspberry Pi camera or other camera, that [you can transform to a valid RTSP H264 or H265 stream](https://github.com/kerberos-io/camera-to-rtsp).
|
||||||
- Any hardware (ARMv6, ARMv7, ARM64, AMD) that can run a binary or container, for example: a Raspberry Pi, NVidia Jetson, Intel NUC, a VM, Bare metal machine or a full blown Kubernetes cluster.
|
- Any hardware (ARMv6, ARMv7, ARM64, AMD) that can run a binary or container, for example: a Raspberry Pi, NVidia Jetson, Intel NUC, a VM, Bare metal machine or a full blown Kubernetes cluster.
|
||||||
|
|
||||||
## :video_camera: Is my camera working?
|
## :video_camera: Is my camera working?
|
||||||
@@ -104,19 +104,21 @@ This repository contains everything you'll need to know about our core product,
|
|||||||
|
|
||||||
- Low memory and CPU usage.
|
- Low memory and CPU usage.
|
||||||
- Simplified and modern user interface.
|
- Simplified and modern user interface.
|
||||||
- Multi architecture (ARMv7, ARMv8, amd64, etc).
|
- Multi architecture (ARMv7, ARMv8, amd64, etc).).
|
||||||
- Multi camera support: IP Cameras (H264), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp).
|
- Multi stream, for example recording in H265, live streaming and motion detection in H264.
|
||||||
|
- Multi camera support: IP Cameras (H264 and H265), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp
|
||||||
- Single camera per instance (e.g. one container per camera).
|
- Single camera per instance (e.g. one container per camera).
|
||||||
- Primary and secondary stream setup (record full-res, stream low-res).
|
- Low resolution streaming through MQTT and high resolution streaming through WebRTC (only supports H264/PCM).
|
||||||
- Low resolution streaming through MQTT and full resolution streaming through WebRTC.
|
- Backchannel audio from Kerberos Hub to IP camera (requires PCM ULAW codec)
|
||||||
- End-to-end encryption through MQTT using RSA and AES.
|
- Audio (AAC) and video (H264/H265) recording in MP4 container.
|
||||||
- Ability to specifiy conditions: offline mode, motion region, time table, continuous recording, etc.
|
- End-to-end encryption through MQTT using RSA and AES (livestreaming, ONVIF, remote configuration, etc)
|
||||||
- Post- and pre-recording on motion detection.
|
- Conditional recording: offline mode, motion region, time table, continuous recording, webhook condition etc.
|
||||||
|
- Post- and pre-recording for motion detection.
|
||||||
- Encryption at rest using AES-256-CBC.
|
- Encryption at rest using AES-256-CBC.
|
||||||
- Ability to create fragmented recordings, and streaming though HLS fMP4.
|
- Ability to create fragmented recordings, and streaming through HLS fMP4.
|
||||||
- [Deploy where you want](#how-to-run-and-deploy-a-kerberos-agent) with the tools you use: `docker`, `docker compose`, `ansible`, `terraform`, `kubernetes`, etc.
|
- [Deploy where you want](#how-to-run-and-deploy-a-kerberos-agent) with the tools you use: `docker`, `docker compose`, `ansible`, `terraform`, `kubernetes`, etc.
|
||||||
- Cloud storage/persistance: Kerberos Hub, Kerberos Vault and Dropbox. [(WIP: Minio, Storj, Google Drive, FTP etc.)](https://github.com/kerberos-io/agent/issues/95)
|
- Cloud storage/persistance: Kerberos Hub, Kerberos Vault and Dropbox. [(WIP: Minio, Storj, Google Drive, FTP etc.)](https://github.com/kerberos-io/agent/issues/95)
|
||||||
- WIP: Integrations (Webhooks, MQTT, Script, etc).
|
- Outputs: trigger an integration (Webhooks, MQTT, Script, etc) when a specific event (motion detection or start recording ) occurs
|
||||||
- REST API access and documentation through Swagger (trigger recording, update configuration, etc).
|
- REST API access and documentation through Swagger (trigger recording, update configuration, etc).
|
||||||
- MIT License
|
- MIT License
|
||||||
|
|
||||||
@@ -192,6 +194,7 @@ Next to attaching the configuration file, it is also possible to override the co
|
|||||||
|
|
||||||
| Name | Description | Default Value |
|
| Name | Description | Default Value |
|
||||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
|
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||||
|
| `LOG_LEVEL` | Level for logging, could be "info", "warning", "debug", "error" or "fatal". | "info" |
|
||||||
| `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" |
|
| `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" |
|
||||||
| `AGENT_TLS_INSECURE` | Specify if you want to use `InsecureSkipVerify` for the internal HTTP client. | "false" |
|
| `AGENT_TLS_INSECURE` | Specify if you want to use `InsecureSkipVerify` for the internal HTTP client. | "false" |
|
||||||
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
|
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
|
||||||
|
@@ -103,7 +103,7 @@ const docTemplate = `{
|
|||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.IPCamera"
|
"$ref": "#/definitions/models.OnvifPreset"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@@ -95,7 +95,7 @@
|
|||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/models.IPCamera"
|
"$ref": "#/definitions/models.OnvifPreset"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@@ -379,7 +379,7 @@ paths:
|
|||||||
name: cameraConfig
|
name: cameraConfig
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/models.IPCamera'
|
$ref: '#/definitions/models.OnvifPreset'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
@@ -70,32 +70,38 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
timezone, _ := time.LoadLocation("CET")
|
timezone, _ := time.LoadLocation("CET")
|
||||||
log.Log.Init(configDirectory, timezone)
|
|
||||||
|
// Specify the level of loggin: "info", "warning", "debug", "error" or "fatal."
|
||||||
|
logLevel := os.Getenv("LOG_LEVEL")
|
||||||
|
if logLevel == "" {
|
||||||
|
logLevel = "info"
|
||||||
|
}
|
||||||
|
log.Log.Init(logLevel, configDirectory, timezone)
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
|
|
||||||
case "version":
|
case "version":
|
||||||
log.Log.Info("Main(): You are currrently running Kerberos Agent " + VERSION)
|
log.Log.Info("main.Main(): You are currrently running Kerberos Agent " + VERSION)
|
||||||
|
|
||||||
case "discover":
|
case "discover":
|
||||||
// Convert duration to int
|
// Convert duration to int
|
||||||
timeout, err := time.ParseDuration(timeout + "ms")
|
timeout, err := time.ParseDuration(timeout + "ms")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Fatal("Main(): could not parse timeout: " + err.Error())
|
log.Log.Fatal("main.Main(): could not parse timeout: " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onvif.Discover(timeout)
|
onvif.Discover(timeout)
|
||||||
|
|
||||||
case "decrypt":
|
case "decrypt":
|
||||||
log.Log.Info("Main(): Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1))
|
log.Log.Info("main.Main(): Decrypting: " + flag.Arg(0) + " with key: " + flag.Arg(1))
|
||||||
symmetricKey := []byte(flag.Arg(1))
|
symmetricKey := []byte(flag.Arg(1))
|
||||||
|
|
||||||
if symmetricKey == nil || len(symmetricKey) == 0 {
|
if symmetricKey == nil || len(symmetricKey) == 0 {
|
||||||
log.Log.Fatal("Main(): symmetric key should not be empty")
|
log.Log.Fatal("main.Main(): symmetric key should not be empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(symmetricKey) != 32 {
|
if len(symmetricKey) != 32 {
|
||||||
log.Log.Fatal("Main(): symmetric key should be 32 bytes")
|
log.Log.Fatal("main.Main(): symmetric key should be 32 bytes")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +137,7 @@ func main() {
|
|||||||
|
|
||||||
// Set timezone
|
// Set timezone
|
||||||
timezone, _ := time.LoadLocation(configuration.Config.Timezone)
|
timezone, _ := time.LoadLocation(configuration.Config.Timezone)
|
||||||
log.Log.Init(configDirectory, timezone)
|
log.Log.Init(logLevel, configDirectory, timezone)
|
||||||
|
|
||||||
// Check if we have a device Key or not, if not
|
// Check if we have a device Key or not, if not
|
||||||
// we will generate one.
|
// we will generate one.
|
||||||
@@ -140,9 +146,9 @@ func main() {
|
|||||||
configuration.Config.Key = key
|
configuration.Config.Key = key
|
||||||
err := configService.StoreConfig(configDirectory, configuration.Config)
|
err := configService.StoreConfig(configDirectory, configuration.Config)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Log.Info("Main(): updated unique key for agent to: " + key)
|
log.Log.Info("main.Main(): updated unique key for agent to: " + key)
|
||||||
} else {
|
} else {
|
||||||
log.Log.Info("Main(): something went wrong while trying to store key: " + key)
|
log.Log.Info("main.Main(): something went wrong while trying to store key: " + key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +156,8 @@ func main() {
|
|||||||
// This is used to restart the agent when the configuration is updated.
|
// This is used to restart the agent when the configuration is updated.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// We create a capture object.
|
// We create a capture object, this will contain all the streaming clients.
|
||||||
|
// And allow us to extract media from within difference places in the agent.
|
||||||
capture := capture.Capture{
|
capture := capture.Capture{
|
||||||
RTSPClient: nil,
|
RTSPClient: nil,
|
||||||
RTSPSubClient: nil,
|
RTSPSubClient: nil,
|
||||||
@@ -169,6 +176,6 @@ func main() {
|
|||||||
routers.StartWebserver(configDirectory, &configuration, &communication, &capture)
|
routers.StartWebserver(configDirectory, &configuration, &communication, &capture)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Log.Error("Main(): Sorry I don't understand :(")
|
log.Log.Error("main.Main(): Sorry I don't understand :(")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,27 +14,15 @@ import (
|
|||||||
"hash"
|
"hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecryptWithPrivateKey decrypts data with private key
|
|
||||||
func DecryptWithPrivateKey(ciphertext string, privateKey *rsa.PrivateKey) ([]byte, error) {
|
func DecryptWithPrivateKey(ciphertext string, privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||||
|
|
||||||
// decode our encrypted string into cipher bytes
|
|
||||||
cipheredValue, _ := base64.StdEncoding.DecodeString(ciphertext)
|
cipheredValue, _ := base64.StdEncoding.DecodeString(ciphertext)
|
||||||
|
|
||||||
// decrypt the data
|
|
||||||
out, err := rsa.DecryptPKCS1v15(nil, privateKey, cipheredValue)
|
out, err := rsa.DecryptPKCS1v15(nil, privateKey, cipheredValue)
|
||||||
|
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignWithPrivateKey signs data with private key
|
|
||||||
func SignWithPrivateKey(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) {
|
func SignWithPrivateKey(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||||
|
|
||||||
// hash the data with sha256
|
|
||||||
hashed := sha256.Sum256(data)
|
hashed := sha256.Sum256(data)
|
||||||
|
|
||||||
// sign the data
|
|
||||||
signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hashed[:])
|
signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hashed[:])
|
||||||
|
|
||||||
return signature, err
|
return signature, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,16 +47,10 @@ func AesEncrypt(content []byte, password string) ([]byte, error) {
|
|||||||
copy(cipherText[:8], []byte("Salted__"))
|
copy(cipherText[:8], []byte("Salted__"))
|
||||||
copy(cipherText[8:16], salt)
|
copy(cipherText[8:16], salt)
|
||||||
copy(cipherText[16:], cipherBytes)
|
copy(cipherText[16:], cipherBytes)
|
||||||
|
|
||||||
//cipherText := base64.StdEncoding.EncodeToString(data)
|
|
||||||
return cipherText, nil
|
return cipherText, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AesDecrypt(cipherText []byte, password string) ([]byte, error) {
|
func AesDecrypt(cipherText []byte, password string) ([]byte, error) {
|
||||||
//data, err := base64.StdEncoding.DecodeString(cipherText)
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
//}
|
|
||||||
if string(cipherText[:8]) != "Salted__" {
|
if string(cipherText[:8]) != "Salted__" {
|
||||||
return nil, errors.New("invalid crypto js aes encryption")
|
return nil, errors.New("invalid crypto js aes encryption")
|
||||||
}
|
}
|
||||||
@@ -92,8 +74,6 @@ func AesDecrypt(cipherText []byte, password string) ([]byte, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/27677236/encryption-in-javascript-and-decryption-with-php/27678978#27678978
|
|
||||||
// https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/evpkdf.js#L55
|
|
||||||
func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgorithm string) ([]byte, error) {
|
func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgorithm string) ([]byte, error) {
|
||||||
var block []byte
|
var block []byte
|
||||||
var hasher hash.Hash
|
var hasher hash.Hash
|
||||||
@@ -124,7 +104,6 @@ func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgor
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err error) {
|
func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err error) {
|
||||||
// https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/cipher-core.js#L775
|
|
||||||
keySize := 256 / 32
|
keySize := 256 / 32
|
||||||
ivSize := 128 / 32
|
ivSize := 128 / 32
|
||||||
derivedKeyBytes, err := EvpKDF(password, salt, keySize+ivSize, 1, "md5")
|
derivedKeyBytes, err := EvpKDF(password, salt, keySize+ivSize, 1, "md5")
|
||||||
@@ -134,7 +113,6 @@ func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err err
|
|||||||
return derivedKeyBytes[:keySize*4], derivedKeyBytes[keySize*4:], nil
|
return derivedKeyBytes[:keySize*4], derivedKeyBytes[keySize*4:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/41579325/golang-how-do-i-decrypt-with-des-cbc-and-pkcs7
|
|
||||||
func PKCS5UnPadding(src []byte) []byte {
|
func PKCS5UnPadding(src []byte) []byte {
|
||||||
length := len(src)
|
length := len(src)
|
||||||
unpadding := int(src[length-1])
|
unpadding := int(src[length-1])
|
||||||
|
@@ -12,7 +12,6 @@ import (
|
|||||||
// The logging library being used everywhere.
|
// The logging library being used everywhere.
|
||||||
var Log = Logging{
|
var Log = Logging{
|
||||||
Logger: "logrus",
|
Logger: "logrus",
|
||||||
Level: "debug",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------
|
// -----------------
|
||||||
@@ -45,7 +44,7 @@ func ConfigureGoLogging(configDirectory string, timezone *time.Location) {
|
|||||||
// This a logrus
|
// This a logrus
|
||||||
// -> github.com/sirupsen/logrus
|
// -> github.com/sirupsen/logrus
|
||||||
|
|
||||||
func ConfigureLogrus(timezone *time.Location) {
|
func ConfigureLogrus(level string, timezone *time.Location) {
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
logrus.SetFormatter(LocalTimeZoneFormatter{
|
logrus.SetFormatter(LocalTimeZoneFormatter{
|
||||||
Timezone: timezone,
|
Timezone: timezone,
|
||||||
@@ -57,7 +56,17 @@ func ConfigureLogrus(timezone *time.Location) {
|
|||||||
logrus.SetOutput(os.Stdout)
|
logrus.SetOutput(os.Stdout)
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
// Only log the warning severity or above.
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
logLevel := logrus.InfoLevel
|
||||||
|
if level == "error" {
|
||||||
|
logLevel = logrus.ErrorLevel
|
||||||
|
} else if level == "debug" {
|
||||||
|
logLevel = logrus.DebugLevel
|
||||||
|
} else if level == "fatal" {
|
||||||
|
logLevel = logrus.FatalLevel
|
||||||
|
} else if level == "warning" {
|
||||||
|
logLevel = logrus.WarnLevel
|
||||||
|
}
|
||||||
|
logrus.SetLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalTimeZoneFormatter struct {
|
type LocalTimeZoneFormatter struct {
|
||||||
@@ -72,15 +81,14 @@ func (u LocalTimeZoneFormatter) Format(e *logrus.Entry) ([]byte, error) {
|
|||||||
|
|
||||||
type Logging struct {
|
type Logging struct {
|
||||||
Logger string
|
Logger string
|
||||||
Level string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Logging) Init(configDirectory string, timezone *time.Location) {
|
func (self *Logging) Init(level string, configDirectory string, timezone *time.Location) {
|
||||||
switch self.Logger {
|
switch self.Logger {
|
||||||
case "go-logging":
|
case "go-logging":
|
||||||
ConfigureGoLogging(configDirectory, timezone)
|
ConfigureGoLogging(configDirectory, timezone)
|
||||||
case "logrus":
|
case "logrus":
|
||||||
ConfigureLogrus(timezone)
|
ConfigureLogrus(level, timezone)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
machinery/src/models/output.go
Normal file
15
machinery/src/models/output.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// The OutputMessage contains the relevant information
|
||||||
|
// to specify the type of triggers we want to execute.
|
||||||
|
type OutputMessage struct {
|
||||||
|
Name string
|
||||||
|
Outputs []string
|
||||||
|
Trigger string
|
||||||
|
Timestamp time.Time
|
||||||
|
File string
|
||||||
|
CameraId string
|
||||||
|
SiteId string
|
||||||
|
}
|
@@ -14,9 +14,9 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/kerberos-io/agent/machinery/src/log"
|
"github.com/kerberos-io/agent/machinery/src/log"
|
||||||
"github.com/kerberos-io/agent/machinery/src/models"
|
"github.com/kerberos-io/agent/machinery/src/models"
|
||||||
"github.com/kerberos-io/onvif/media"
|
|
||||||
|
|
||||||
"github.com/kerberos-io/onvif"
|
"github.com/kerberos-io/onvif"
|
||||||
|
"github.com/kerberos-io/onvif/device"
|
||||||
|
"github.com/kerberos-io/onvif/media"
|
||||||
"github.com/kerberos-io/onvif/ptz"
|
"github.com/kerberos-io/onvif/ptz"
|
||||||
xsd "github.com/kerberos-io/onvif/xsd/onvif"
|
xsd "github.com/kerberos-io/onvif/xsd/onvif"
|
||||||
)
|
)
|
||||||
@@ -718,9 +718,9 @@ func ContinuousZoom(device *onvif.Device, configuration ptz.GetConfigurationsRes
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCapabilitiesFromDevice(device *onvif.Device) []string {
|
func GetCapabilitiesFromDevice(dev *onvif.Device) []string {
|
||||||
var capabilities []string
|
var capabilities []string
|
||||||
services := device.GetServices()
|
services := dev.GetServices()
|
||||||
for key, _ := range services {
|
for key, _ := range services {
|
||||||
log.Log.Debug("GetCapabilitiesFromDevice: has key: " + key)
|
log.Log.Debug("GetCapabilitiesFromDevice: has key: " + key)
|
||||||
if key != "" {
|
if key != "" {
|
||||||
@@ -731,6 +731,34 @@ func GetCapabilitiesFromDevice(device *onvif.Device) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getCapabilitiesResponse device.GetCapabilitiesResponse
|
||||||
|
resp, err := dev.CallMethod(device.GetCapabilities{
|
||||||
|
Category: "All",
|
||||||
|
})
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
if resp != nil {
|
||||||
|
b, err = io.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
stringBody := string(b)
|
||||||
|
decodedXML, et, err := getXMLNode(stringBody, "GetCapabilitiesResponse")
|
||||||
|
if err != nil {
|
||||||
|
log.Log.Error("GetCapabilitiesResponse: " + err.Error())
|
||||||
|
//return err
|
||||||
|
} else {
|
||||||
|
if err := decodedXML.DecodeElement(&getCapabilitiesResponse, et); err != nil {
|
||||||
|
log.Log.Error("GetCapabilitiesResponse: " + err.Error())
|
||||||
|
// return err
|
||||||
|
}
|
||||||
|
//return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Log.Error("GoToPresetFromDevice: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return capabilities
|
return capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -909,12 +937,12 @@ func VerifyOnvifConnection(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDigitalInputs(device *onvif.Device) (ptz.GetConfigurationsResponse, error) {
|
func GetRelayOutputs(dev *onvif.Device) (device.GetRelayOutputsResponse, error) {
|
||||||
// We'll try to receive the PTZ configurations from the server
|
// We'll try to receive the relay outputs from the server
|
||||||
var configurations ptz.GetConfigurationsResponse
|
var relayoutputs device.GetRelayOutputsResponse
|
||||||
|
|
||||||
// Get the PTZ configurations from the device
|
// Get the PTZ configurations from the device
|
||||||
resp, err := device.CallMethod(ptz.GetConfigurations{})
|
resp, err := dev.CallMethod(device.GetRelayOutputs{})
|
||||||
var b []byte
|
var b []byte
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
b, err = io.ReadAll(resp.Body)
|
b, err = io.ReadAll(resp.Body)
|
||||||
@@ -924,19 +952,19 @@ func GetDigitalInputs(device *onvif.Device) (ptz.GetConfigurationsResponse, erro
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
stringBody := string(b)
|
stringBody := string(b)
|
||||||
decodedXML, et, err := getXMLNode(stringBody, "GetConfigurationsResponse")
|
decodedXML, et, err := getXMLNode(stringBody, "GetRelayOutputsResponse")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error())
|
log.Log.Error("onvif.main.GetRelayOutputs(): " + err.Error())
|
||||||
return configurations, err
|
return relayoutputs, err
|
||||||
} else {
|
} else {
|
||||||
if err := decodedXML.DecodeElement(&configurations, et); err != nil {
|
if err := decodedXML.DecodeElement(&relayoutputs, et); err != nil {
|
||||||
log.Log.Debug("onvif.GetPTZConfigurationsFromDevice(): " + err.Error())
|
log.Log.Debug("onvif.main.GetRelayOutputs(): " + err.Error())
|
||||||
return configurations, err
|
return relayoutputs, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return configurations, err
|
return relayoutputs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) {
|
func getXMLNode(xmlBody string, nodeName string) (*xml.Decoder, *xml.StartElement, error) {
|
||||||
|
59
machinery/src/outputs/main.go
Normal file
59
machinery/src/outputs/main.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package outputs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kerberos-io/agent/machinery/src/log"
|
||||||
|
"github.com/kerberos-io/agent/machinery/src/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Output interface {
|
||||||
|
// Triggers the integration
|
||||||
|
Trigger(message models.OutputMessage) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute(message *models.OutputMessage) (err error) {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
outputs := message.Outputs
|
||||||
|
for _, output := range outputs {
|
||||||
|
switch output {
|
||||||
|
case "slack":
|
||||||
|
slack := &SlackOutput{}
|
||||||
|
err := slack.Trigger(message)
|
||||||
|
if err == nil {
|
||||||
|
log.Log.Debug("outputs.main.Execute(slack): message was processed by output.")
|
||||||
|
} else {
|
||||||
|
log.Log.Error("outputs.main.Execute(slack): " + err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "webhook":
|
||||||
|
webhook := &WebhookOutput{}
|
||||||
|
err := webhook.Trigger(message)
|
||||||
|
if err == nil {
|
||||||
|
log.Log.Debug("outputs.main.Execute(webhook): message was processed by output.")
|
||||||
|
} else {
|
||||||
|
log.Log.Error("outputs.main.Execute(webhook): " + err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "onvif_relay":
|
||||||
|
onvif := &OnvifRelayOutput{}
|
||||||
|
err := onvif.Trigger(message)
|
||||||
|
if err == nil {
|
||||||
|
log.Log.Debug("outputs.main.Execute(onvif): message was processed by output.")
|
||||||
|
} else {
|
||||||
|
log.Log.Error("outputs.main.Execute(onvif): " + err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "script":
|
||||||
|
script := &ScriptOutput{}
|
||||||
|
err := script.Trigger(message)
|
||||||
|
if err == nil {
|
||||||
|
log.Log.Debug("outputs.main.Execute(script): message was processed by output.")
|
||||||
|
} else {
|
||||||
|
log.Log.Error("outputs.main.Execute(script): " + err.Error())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
12
machinery/src/outputs/onvif_relay.go
Normal file
12
machinery/src/outputs/onvif_relay.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package outputs
|
||||||
|
|
||||||
|
import "github.com/kerberos-io/agent/machinery/src/models"
|
||||||
|
|
||||||
|
type OnvifRelayOutput struct {
|
||||||
|
Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnvifRelayOutput) Trigger(message *models.OutputMessage) (err error) {
|
||||||
|
err = nil
|
||||||
|
return err
|
||||||
|
}
|
12
machinery/src/outputs/script.go
Normal file
12
machinery/src/outputs/script.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package outputs
|
||||||
|
|
||||||
|
import "github.com/kerberos-io/agent/machinery/src/models"
|
||||||
|
|
||||||
|
type ScriptOutput struct {
|
||||||
|
Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scr *ScriptOutput) Trigger(message *models.OutputMessage) (err error) {
|
||||||
|
err = nil
|
||||||
|
return err
|
||||||
|
}
|
12
machinery/src/outputs/slack.go
Normal file
12
machinery/src/outputs/slack.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package outputs
|
||||||
|
|
||||||
|
import "github.com/kerberos-io/agent/machinery/src/models"
|
||||||
|
|
||||||
|
type SlackOutput struct {
|
||||||
|
Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackOutput) Trigger(message *models.OutputMessage) (err error) {
|
||||||
|
err = nil
|
||||||
|
return err
|
||||||
|
}
|
12
machinery/src/outputs/webhook.go
Normal file
12
machinery/src/outputs/webhook.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package outputs
|
||||||
|
|
||||||
|
import "github.com/kerberos-io/agent/machinery/src/models"
|
||||||
|
|
||||||
|
type WebhookOutput struct {
|
||||||
|
Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebhookOutput) Trigger(message *models.OutputMessage) (err error) {
|
||||||
|
err = nil
|
||||||
|
return err
|
||||||
|
}
|
@@ -8,10 +8,7 @@ import (
|
|||||||
|
|
||||||
// Packet represents an RTP Packet
|
// Packet represents an RTP Packet
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
// for Gortsplib library
|
Packet *rtp.Packet
|
||||||
Packet *rtp.Packet
|
|
||||||
|
|
||||||
// for JOY4 and Gortsplib library
|
|
||||||
IsAudio bool // packet is audio
|
IsAudio bool // packet is audio
|
||||||
IsVideo bool // packet is video
|
IsVideo bool // packet is video
|
||||||
IsKeyFrame bool // video packet is key frame
|
IsKeyFrame bool // video packet is key frame
|
||||||
|
@@ -361,7 +361,7 @@ func GoToOnvifPreset(c *gin.Context) {
|
|||||||
// @in header
|
// @in header
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @Tags camera
|
// @Tags camera
|
||||||
// @Param cameraConfig body models.IPCamera true "Camera Config"
|
// @Param cameraConfig body models.OnvifPreset true "Camera Config"
|
||||||
// @Summary Will get the digital inputs from the ONVIF device.
|
// @Summary Will get the digital inputs from the ONVIF device.
|
||||||
// @Description Will get the digital inputs from the ONVIF device.
|
// @Description Will get the digital inputs from the ONVIF device.
|
||||||
// @Success 200 {object} models.APIResponse
|
// @Success 200 {object} models.APIResponse
|
||||||
@@ -386,10 +386,10 @@ func DoGetDigitalInputs(c *gin.Context) {
|
|||||||
cameraConfiguration := configuration.Config.Capture.IPCamera
|
cameraConfiguration := configuration.Config.Capture.IPCamera
|
||||||
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
|
device, err := onvif.ConnectToOnvifDevice(&cameraConfiguration)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inputs, err := onvif.GetDigitalInputs(device)
|
outputs, err := onvif.GetRelayOutputs(device)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"data": inputs,
|
"data": outputs,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
Reference in New Issue
Block a user