arlo-go/camera.go

316 lines
9.6 KiB
Go

package arlo
import (
"fmt"
"strings"
"github.com/pkg/errors"
)
// 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
// Cameras is an array of Camera objects.
type Cameras []Camera
// 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 {
return &c
}
}
return nil
}
// On turns a camera on; meaning it will detect and record events.
func (c *Camera) On() (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: CameraProperties{
PrivacyActive: false,
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to turn camera on"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
// On turns a camera off; meaning it won't detect and record events.
func (c *Camera) Off() (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.ParentId,
}
msg := "failed to turn camera off"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
// 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(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.ParentId,
}
msg := "failed to set camera brightness"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
// StartStream returns a json object containing the rtmps url to the requested video stream.
// You will need the to install a library to handle streaming of this protocol: https://pypi.python.org/pypi/python-librtmp
//
// The request to /users/devices/startStream returns:
// NOTE: { "url":"rtsp://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
func (c *Camera) StartStream() (response *StreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: map[string]string{
"activityState": "startUserStream",
"cameraId": c.DeviceId,
},
TransId: genTransId(),
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to start stream"
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, payload, nil)
if err := checkHttpRequest(resp, err, msg); err != nil {
return nil, err
}
defer resp.Body.Close()
if err := resp.Decode(response); err != nil {
return nil, err
}
if !response.Success {
return nil, errors.WithMessage(errors.New("status was false"), msg)
}
response.Data.Url = strings.Replace(response.Data.Url, "rtsp://", "rtsps://", 1)
return response, nil
}
// TakeSnapshot causes the camera to record a snapshot.
func (c *Camera) TakeSnapshot() (response *StreamResponse, err error) {
msg := "failed to take snapshot"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
// or something similar.
func (c *Camera) StartRecording() (response *StreamResponse, err error) {
msg := "failed to start recording"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
func (c *Camera) EnableMotionAlerts(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: true,
Sensitivity: sensitivity,
Zones: zones,
},
},
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to enable motion alerts"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
func (c *Camera) DisableMotionAlerts(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.ParentId,
}
msg := "failed to enable motion alerts"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
func (c *Camera) EnableAudioAlerts(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,
}
msg := "failed to enable audio alerts"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
func (c *Camera) DisableAudioAlerts(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.ParentId,
}
msg := "failed to disable audio alerts"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}
// action: disabled OR recordSnapshot OR recordVideo
func (c *Camera) SetAlertNotificationMethods(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.ParentId,
}
msg := "failed to set alert notification methods"
b := c.arlo.Basestations.Find(c.ParentId)
if b == nil {
err := fmt.Errorf("basestation (%s) not found for camera (%s)", c.ParentId, c.DeviceId)
return nil, errors.WithMessage(err, msg)
}
return b.makeEventStreamRequest(payload, msg)
}