2018-09-19 07:07:32 +00:00
|
|
|
package arlo
|
2018-09-17 04:44:41 +00:00
|
|
|
|
2018-09-19 19:12:06 +00:00
|
|
|
import (
|
2020-05-27 19:03:26 +00:00
|
|
|
"context"
|
2020-06-08 16:19:08 +00:00
|
|
|
"encoding/json"
|
2018-09-19 19:12:06 +00:00
|
|
|
"fmt"
|
2020-06-08 16:19:08 +00:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2018-09-19 19:12:06 +00:00
|
|
|
)
|
|
|
|
|
2018-09-17 04:44:41 +00:00
|
|
|
// 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 Device
|
|
|
|
|
2020-05-27 08:49:40 +00:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2018-09-22 19:22:42 +00:00
|
|
|
// Cameras is a slice of Camera objects.
|
2020-05-27 08:49:40 +00:00
|
|
|
type Cameras []*Camera
|
2018-09-19 19:12:06 +00:00
|
|
|
|
2018-09-20 22:38:01 +00:00
|
|
|
// Find returns a camera with the device id passed in.
|
|
|
|
func (cs *Cameras) Find(deviceId string) *Camera {
|
|
|
|
for _, c := range *cs {
|
|
|
|
if c.DeviceId == deviceId {
|
2020-05-27 08:49:40 +00:00
|
|
|
return c
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) On(ctx context.Context) error {
|
2018-09-20 22:38:01 +00:00
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 19:03:26 +00:00
|
|
|
return fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
|
|
|
|
err := b.makeRequest(ctx, "set", fmt.Sprintf("cameras/%s", c.DeviceId), true, CameraProperties{
|
|
|
|
PrivacyActive: false,
|
|
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// On turns a camera off; meaning it won't detect and record events.
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) Off(ctx context.Context) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
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.ParentId,
|
|
|
|
}
|
|
|
|
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) SetBrightness(ctx context.Context, brightness int) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
// 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.ParentId,
|
|
|
|
}
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 12:23:15 +00:00
|
|
|
func (c *Camera) EnableMotionAlerts(ctx context.Context, sensitivity int, zones []string) error {
|
2018-09-20 22:38:01 +00:00
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-06-06 12:23:15 +00:00
|
|
|
return fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-06-06 12:23:15 +00:00
|
|
|
err := b.makeRequest(ctx, "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
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) DisableMotionAlerts(ctx context.Context, sensitivity int, zones []string) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
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.ParentId,
|
|
|
|
}
|
|
|
|
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) EnableAudioAlerts(ctx context.Context, sensitivity int) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) DisableAudioAlerts(ctx context.Context, sensitivity int) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
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.ParentId,
|
|
|
|
}
|
|
|
|
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-20 22:38:01 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-09-22 14:15:22 +00:00
|
|
|
}
|
|
|
|
|
2018-09-20 22:38:01 +00:00
|
|
|
// action: disabled OR recordSnapshot OR recordVideo
|
2020-05-27 19:03:26 +00:00
|
|
|
func (c *Camera) SetAlertNotificationMethods(ctx context.Context, action string, email, push bool) (response *EventStreamResponse, err error) {
|
2018-09-20 22:38:01 +00:00
|
|
|
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.ParentId,
|
|
|
|
}
|
|
|
|
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
2020-05-27 08:49:40 +00:00
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
2018-09-22 19:22:42 +00:00
|
|
|
}
|
2020-05-27 19:03:26 +00:00
|
|
|
return b.makeEventStreamRequest(ctx, payload)
|
2018-12-11 23:48:51 +00:00
|
|
|
}
|
2020-06-08 16:19:08 +00:00
|
|
|
|
|
|
|
func (c *Camera) SubscribeToMotionDetection(ctx context.Context) (chan bool, error) {
|
|
|
|
b := c.arlo.Basestations.Find(c.ParentId)
|
|
|
|
if b == nil {
|
|
|
|
return nil, fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
|
|
|
|
}
|
|
|
|
out := make(chan bool)
|
|
|
|
respChan := b.eventStream.subscribeResource(fmt.Sprintf("cameras/%s", c.DeviceId))
|
|
|
|
go func() {
|
|
|
|
for msg := range respChan {
|
|
|
|
var state CameraState
|
|
|
|
err := json.Unmarshal(msg.RawProperties, &state)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("unmarshalling properties: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
out <- state.MotionDetected
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|