diff --git a/cmd/aver/main.go b/cmd/aver/main.go index f1283d7..6aeec42 100644 --- a/cmd/aver/main.go +++ b/cmd/aver/main.go @@ -144,6 +144,9 @@ func main() { handlers := handlers.NewCameraController(cs) handlers.Logger = log handlers.CreateCamera = func(ctx context.Context, addr string) (cameraservices.Camera, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + // TODO need to make this function better if New() does much of anything (see av-control-api/drivers) if cam, ok := cameras.Load(addr); ok { return cam.(*aver.Pro520), nil @@ -157,11 +160,31 @@ func main() { } } + username, password, err := cs.FindCameraAuthByAddress(ctx, addrNoPort) + if err != nil { + log.Warn("unable to get camera credentials from config service, falling back to flags", + zap.String("camera", addrNoPort), + zap.Error(err), + ) + username = camUsername + password = camPassword + } + + if username == "" { + log.Debug("no username provided for camera, using default") + username = camUsername + } + + if password == "" { + log.Debug("no password provided for camera, using default") + password = camPassword + } + cam := &aver.Pro520{ Camera: visca.New(addr, visca.WithLogger(log.Sugar().Named(addr))), Address: addrNoPort, - Username: camUsername, - Password: camPassword, + Username: username, + Password: password, } cameras.Store(addr, cam) diff --git a/control.go b/control.go index 0e4c147..3b5fd0f 100644 --- a/control.go +++ b/control.go @@ -29,7 +29,9 @@ type CameraConfig struct { Presets []CameraPreset `json:"presets"` // admin items - Reboot string `json:"reboot"` + Reboot string `json:"reboot"` + UserName string `json:"username,omitempty"` + Password string `json:"password,omitempty"` } type CameraPreset struct { diff --git a/couch/couch.go b/couch/couch.go index 4ee0a1f..1bfe8ef 100644 --- a/couch/couch.go +++ b/couch/couch.go @@ -3,6 +3,7 @@ package couch import ( "context" "fmt" + "net" "strings" cameraservices "github.com/byuoitav/camera-services" @@ -191,3 +192,86 @@ func (c *configService) ControlIP(ctx context.Context, room string) ([]string, e return IP, nil } + +// FindCameraByAddress searches the config database for a camera with the given address +// and returns its username and password. +func (c *configService) FindCameraAuthByAddress(ctx context.Context, addr string) (string, string, error) { + db := c.client.DB(ctx, c.uiConfigDB) + + query := map[string]interface{}{ + "selector": map[string]interface{}{ + "presets": map[string]interface{}{ + "$elemMatch": map[string]interface{}{ + "cameras": map[string]interface{}{ + "$elemMatch": map[string]interface{}{ + "$or": []map[string]interface{}{ + {"stream": map[string]interface{}{"$regex": addr}}, + {"panLeft": map[string]interface{}{"$regex": addr}}, + {"panRight": map[string]interface{}{"$regex": addr}}, + {"tiltUp": map[string]interface{}{"$regex": addr}}, + {"tiltDown": map[string]interface{}{"$regex": addr}}, + }, + }, + }, + }, + }, + }, + } + + rows, err := db.Find(ctx, query) + if err != nil { + return "", "", fmt.Errorf("unable to query config DB: %w", err) + } + for rows.Next() { + var config uiConfig + if err := rows.ScanDoc(&config); err != nil { + continue + } + for _, group := range config.ControlGroups { + for _, cam := range group.Cameras { + if strings.Contains(cam.Stream, addr) || strings.Contains(cam.PanLeft, addr) || + strings.Contains(cam.PanRight, addr) || strings.Contains(cam.TiltUp, addr) || + strings.Contains(cam.TiltDown, addr) { + return cam.UserName, cam.Password, nil + } + } + } + } + return "", "", fmt.Errorf("camera with address %s not found", addr) +} + +// GetCameraAuth retrieves the camera's username and password from the config database based on the provided address. +func (c *configService) GetCameraAuth(ctx context.Context, addr string) (string, string, error) { + isIP := net.ParseIP(addr) != nil + if !isIP { + // Tries to get room from hostname in address + parts := strings.SplitN(addr, "-", 3) + if len(parts) >= 2 { + roomID := parts[0] + "-" + parts[1] + var config uiConfig + + db := c.client.DB(ctx, c.uiConfigDB) + if err := db.Get(ctx, roomID).ScanDoc(&config); err == nil { + for _, group := range config.ControlGroups { + for _, cam := range group.Cameras { + if cameraMatchesAddress(cam, addr) { + return cam.UserName, cam.Password, nil + } + } + } + } + } + } + + // If it fails to get a room from the address, it will try to get the camera from the config database + // by searching all the docs for the address + return c.FindCameraAuthByAddress(ctx, addr) +} + +func cameraMatchesAddress(cam cameraservices.CameraConfig, addr string) bool { + return strings.Contains(cam.Stream, addr) || + strings.Contains(cam.PanLeft, addr) || + strings.Contains(cam.PanRight, addr) || + strings.Contains(cam.TiltUp, addr) || + strings.Contains(cam.TiltDown, addr) +}