arlo-go/camera.go

287 lines
9.2 KiB
Go

package arlo
import (
"context"
"encoding/json"
"fmt"
"sync"
log "github.com/sirupsen/logrus"
)
// A Camera is a Device of type "camera".
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
type Camera struct {
arlo *Arlo
Device
CameraState
stateMutex sync.RWMutex
motionSubscribers []chan bool
motionSubscribersMutex sync.RWMutex
}
type CameraState struct {
InterfaceVersion int `json:"interfaceVersion"`
SerialNumber string `json:"serialNumber"`
BatteryLevel int `json:"batteryLevel"`
BatteryTech string `json:"batteryTech"`
ChargerTech string `json:"chargerTech"`
ChargingState string `json:"chargingState"`
ChargeOnly bool `json:"chargeOnly"`
ChargeNotificationLedEnable bool `json:"chargeNotificationLedEnable"`
AudioMicAGC int `json:"audioMicAGC"`
SignalStrength int `json:"signalStrength"`
Brightness int `json:"brightness"`
Mirror bool `json:"mirror"`
Flip bool `json:"flip"`
PowerSaveMode int `json:"powerSaveMode"`
Zoom struct {
Topleftx int `json:"topleftx"`
Toplefty int `json:"toplefty"`
Bottomrightx int `json:"bottomrightx"`
Bottomrighty int `json:"bottomrighty"`
} `json:"zoom"`
Mic struct {
Mute bool `json:"mute"`
Volume int `json:"volume"`
} `json:"mic"`
Speaker struct {
Mute bool `json:"mute"`
Volume int `json:"volume"`
} `json:"speaker"`
StreamingMode string `json:"streamingMode"`
ContinuousStreamState string `json:"continuousStreamState"`
Motion struct {
Sensitivity int `json:"sensitivity"`
Zones []interface{} `json:"zones"`
} `json:"motion"`
Resolution struct {
Width int `json:"width"`
Height int `json:"height"`
} `json:"resolution"`
IdleLedEnable bool `json:"idleLedEnable"`
PrivacyActive bool `json:"privacyActive"`
StandbyActive bool `json:"standbyActive"`
SetupActive bool `json:"setupActive"`
ConnectionState string `json:"connectionState"`
ActivityState string `json:"activityState"`
SwVersion string `json:"swVersion"`
HwVersion string `json:"hwVersion"`
ModelID string `json:"modelId"`
MotionSetupModeEnabled bool `json:"motionSetupModeEnabled"`
MotionSetupModeSensitivity int `json:"motionSetupModeSensitivity"`
MotionDetected bool `json:"motionDetected"`
AudioDetected bool `json:"audioDetected"`
HasStreamed bool `json:"hasStreamed"`
LocalRecordingActive bool `json:"localRecordingActive"`
OlsonTimeZone string `json:"olsonTimeZone"`
Name string `json:"name"`
NightVisionMode int `json:"nightVisionMode"`
VideoMode string `json:"videoMode"`
Hdr string `json:"hdr"`
UpdateAvailable interface{} `json:"updateAvailable"`
BlockNotifications struct {
Block bool `json:"block"`
Duration int `json:"duration"`
EndTime int `json:"endTime"`
} `json:"blockNotifications"`
BestLocalLiveStreaming string `json:"bestLocalLiveStreaming"`
}
func NewCamera(arlo *Arlo, device Device) *Camera {
return &Camera{
arlo: arlo,
Device: device,
}
}
func (c *Camera) On(ctx context.Context) error {
err := c.arlo.makeRequest(ctx, c.DeviceId, c.XCloudId, "set", fmt.Sprintf("cameras/%s", c.DeviceId), true, CameraProperties{
PrivacyActive: false,
}, nil)
if err != nil {
return err
}
return nil
}
// On turns a camera off; meaning it won't detect and record events.
func (c *Camera) Off(ctx context.Context) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: CameraProperties{
PrivacyActive: true,
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.DeviceId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
// SetBrightness sets the camera brightness.
// NOTE: Brightness is between -2 and 2 in increments of 1 (-2, -1, 0, 1, 2).
// Setting it to an invalid value has no effect.
func (c *Camera) SetBrightness(ctx context.Context, brightness int) (response *EventStreamResponse, err error) {
// Sanity check; if the values are above or below the allowed limits, set them to their limit.
if brightness < -2 {
brightness = -2
} else if brightness > 2 {
brightness = 2
}
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: CameraProperties{
Brightness: brightness,
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.DeviceId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
func (c *Camera) EnableMotionAlerts(ctx context.Context, sensitivity int, zones []string) error {
err := c.arlo.makeRequest(ctx, c.DeviceId, c.XCloudId, "set", fmt.Sprintf("cameras/%s", c.DeviceId), true, MotionDetectionProperties{
BaseDetectionProperties: BaseDetectionProperties{
Armed: true,
Sensitivity: sensitivity,
Zones: zones,
},
}, nil)
if err != nil {
return err
}
return nil
}
func (c *Camera) DisableMotionAlerts(ctx context.Context, sensitivity int, zones []string) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: MotionDetectionProperties{
BaseDetectionProperties: BaseDetectionProperties{
Armed: false,
Sensitivity: sensitivity,
Zones: zones,
},
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.DeviceId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
func (c *Camera) EnableAudioAlerts(ctx context.Context, sensitivity int) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: AudioDetectionProperties{
BaseDetectionProperties: BaseDetectionProperties{
Armed: true,
Sensitivity: sensitivity,
},
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
func (c *Camera) DisableAudioAlerts(ctx context.Context, sensitivity int) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: AudioDetectionProperties{
BaseDetectionProperties: BaseDetectionProperties{
Armed: false,
Sensitivity: sensitivity,
},
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.DeviceId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
// action: disabled OR recordSnapshot OR recordVideo
func (c *Camera) SetAlertNotificationMethods(ctx context.Context, action string, email, push bool) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: EventActionProperties{
BaseEventActionProperties: BaseEventActionProperties{
ActionType: action,
StopType: "timeout",
Timeout: 15,
EmailNotification: EmailNotification{
Enabled: email,
EmailList: []string{"__OWNER_EMAIL__"},
PushNotification: push,
},
},
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.DeviceId,
}
return c.arlo.makeEventStreamRequest(ctx, payload, c.XCloudId)
}
func (c *Camera) subscribeToStateUpdate() {
go func() {
respChan := c.arlo.eventStream.subscribeResource(fmt.Sprintf("cameras/%s", c.DeviceId))
for msg := range respChan {
c.stateMutex.Lock()
old := *c
err := json.Unmarshal(msg.RawProperties, &c)
if err != nil {
log.Errorf("camera state update > unmarshalling properties: %v", err)
continue
}
c.stateMutex.Unlock()
c.processStateChanges(old, *c)
}
}()
}
func (c *Camera) RefreshState(ctx context.Context) error {
old := *c
err := c.arlo.makeRequest(ctx, c.DeviceId, c.XCloudId, "get", fmt.Sprintf("cameras/%s", c.DeviceId), false, nil, &c)
if err != nil {
return err
}
c.processStateChanges(old, *c)
return nil
}
func (c *Camera) processStateChanges(old Camera, new Camera) {
if old.MotionDetected != new.MotionDetected {
c.motionSubscribersMutex.RLock()
for _, subscriber := range c.motionSubscribers {
subscriber <- new.MotionDetected
}
c.motionSubscribersMutex.RUnlock()
}
}
func (c *Camera) SubscribeToMotion() chan bool {
c.motionSubscribersMutex.Lock()
defer c.motionSubscribersMutex.Unlock()
out := make(chan bool)
c.motionSubscribers = append(c.motionSubscribers, out)
return out
}