287 lines
9.2 KiB
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
|
|
}
|