More refactoring, internal API almost stable. Added a shitload of external Arlo APIs! The library is at about 60% parity with the Python version.
This commit is contained in:
parent
533202c8b9
commit
049f3c5652
48
arlo.go
48
arlo.go
@ -15,7 +15,8 @@ type Arlo struct {
|
||||
Cameras Cameras
|
||||
}
|
||||
|
||||
func newArlo(user string, pass string) *Arlo {
|
||||
func newArlo(user string, pass string) (arlo *Arlo) {
|
||||
|
||||
c, _ := request.NewClient(BaseUrl)
|
||||
|
||||
// Add important headers.
|
||||
@ -31,14 +32,15 @@ func newArlo(user string, pass string) *Arlo {
|
||||
}
|
||||
}
|
||||
|
||||
func Login(user string, pass string) (*Arlo, error) {
|
||||
a := newArlo(user, pass)
|
||||
func Login(user string, pass string) (arlo *Arlo, err error) {
|
||||
arlo = newArlo(user, pass)
|
||||
|
||||
body := map[string]string{"email": a.user, "password": a.pass}
|
||||
resp, err := a.post(LoginUri, "", body, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "login request failed")
|
||||
body := map[string]string{"email": arlo.user, "password": arlo.pass}
|
||||
resp, err := arlo.post(LoginUri, "", body, nil)
|
||||
if err := checkHttpRequest(resp, err, "login request failed"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var loginResponse LoginResponse
|
||||
if err := resp.Decode(&loginResponse); err != nil {
|
||||
@ -47,33 +49,33 @@ func Login(user string, pass string) (*Arlo, error) {
|
||||
|
||||
if loginResponse.Success {
|
||||
// Cache the auth token.
|
||||
a.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
||||
arlo.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
||||
|
||||
// Save the account info with the arlo struct.
|
||||
a.Account = loginResponse.Data
|
||||
arlo.Account = loginResponse.Data
|
||||
|
||||
// Get the devices, which also caches them on the arlo object.
|
||||
if _, err := a.GetDevices(); err != nil {
|
||||
if _, err := arlo.GetDevices(); err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to login")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("failed to login")
|
||||
}
|
||||
|
||||
return a, nil
|
||||
return arlo, nil
|
||||
}
|
||||
|
||||
func (a *Arlo) Logout() error {
|
||||
resp, err := a.put(LogoutUri, "", nil, nil)
|
||||
return checkRequest(*resp, err, "failed to logout")
|
||||
return checkRequest(resp, err, "failed to logout")
|
||||
}
|
||||
|
||||
// GetDevices returns an array of all devices.
|
||||
// When you call Login, this method is called and all devices are cached in the arlo object.
|
||||
func (a *Arlo) GetDevices() (Devices, error) {
|
||||
func (a *Arlo) GetDevices() (devices Devices, err error) {
|
||||
resp, err := a.get(DevicesUri, "", nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to get devices")
|
||||
if err := checkHttpRequest(resp, err, "failed to get devices"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@ -82,14 +84,14 @@ func (a *Arlo) GetDevices() (Devices, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(deviceResponse.Data) == 0 {
|
||||
return nil, errors.New("no devices found")
|
||||
}
|
||||
|
||||
if !deviceResponse.Success {
|
||||
return nil, errors.New("failed to get devices")
|
||||
}
|
||||
|
||||
if len(deviceResponse.Data) == 0 {
|
||||
return nil, errors.New("no devices found")
|
||||
}
|
||||
|
||||
for i := range deviceResponse.Data {
|
||||
deviceResponse.Data[i].arlo = a
|
||||
}
|
||||
@ -118,20 +120,20 @@ func (a *Arlo) GetDevices() (Devices, error) {
|
||||
// UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given.
|
||||
func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) error {
|
||||
resp, err := a.post(DeviceDisplayOrderUri, "", d, nil)
|
||||
return checkRequest(*resp, err, "failed to display order")
|
||||
return checkRequest(resp, err, "failed to display order")
|
||||
}
|
||||
|
||||
// UpdateProfile takes a first and last name, and updates the user profile with that information.
|
||||
func (a *Arlo) UpdateProfile(firstName, lastName string) error {
|
||||
body := map[string]string{"firstName": firstName, "lastName": lastName}
|
||||
resp, err := a.put(UserProfileUri, "", body, nil)
|
||||
return checkRequest(*resp, err, "failed to update profile")
|
||||
return checkRequest(resp, err, "failed to update profile")
|
||||
}
|
||||
|
||||
func (a *Arlo) UpdatePassword(pass string) error {
|
||||
body := map[string]string{"currentPassword": a.pass, "newPassword": pass}
|
||||
resp, err := a.post(UserChangePasswordUri, "", body, nil)
|
||||
if err := checkRequest(*resp, err, "failed to update password"); err != nil {
|
||||
if err := checkRequest(resp, err, "failed to update password"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -142,5 +144,5 @@ func (a *Arlo) UpdatePassword(pass string) error {
|
||||
|
||||
func (a *Arlo) UpdateFriends(f Friend) error {
|
||||
resp, err := a.put(UserFriendsUri, "", f, nil)
|
||||
return checkRequest(*resp, err, "failed to update friends")
|
||||
return checkRequest(resp, err, "failed to update friends")
|
||||
}
|
||||
|
506
arlobaby.go
Normal file
506
arlobaby.go
Normal file
@ -0,0 +1,506 @@
|
||||
package arlo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
/*
|
||||
The methods in this file are all related to Arlo Baby (afaik).
|
||||
They may apply to other camera types that have audio playback or nightlight capabilities.
|
||||
*/
|
||||
|
||||
/*
|
||||
The follow methods are all related to the audio features of Arlo Baby.
|
||||
*/
|
||||
|
||||
// SetVolume sets the volume of the audio playback to a level from 0-100.
|
||||
func (c *Camera) SetVolume(volume int) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: SpeakerProperties{
|
||||
Speaker: VolumeProperties{
|
||||
Mute: false,
|
||||
Volume: volume,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to set audio volume"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Mute mutes the audio playback.
|
||||
func (c *Camera) Mute() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: SpeakerProperties{
|
||||
Speaker: VolumeProperties{
|
||||
Mute: true,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to mute audio"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// UnMute un-mutes the audio playback.
|
||||
func (c *Camera) UnMute() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: SpeakerProperties{
|
||||
Speaker: VolumeProperties{
|
||||
Mute: false,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to un-mute audio"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Play plays an audio track, specified by the track ID, from a given position starting from 0 seconds.
|
||||
func (c *Camera) Play(trackId string, position int) error {
|
||||
|
||||
// Defaulting to 'hugh little baby', which is a supplied track. Hopefully, the ID is the same for everyone.
|
||||
if trackId == "" {
|
||||
trackId = "2391d620-e491-4412-99f6-e9a40d6046ed"
|
||||
}
|
||||
|
||||
if position < 0 {
|
||||
position = 0
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "playTrack",
|
||||
Resource: "audioPlayback/player",
|
||||
PublishResponse: false,
|
||||
Properties: PlayTrackProperties{trackId, position},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to play audio"
|
||||
|
||||
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 errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
if err := b.NotifyEventStream(payload, msg); err != nil {
|
||||
return errors.WithMessage(err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pause pauses audio playback.
|
||||
func (c *Camera) Pause() error {
|
||||
payload := EventStreamPayload{
|
||||
Action: "pause",
|
||||
Resource: "audioPlayback/player",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to pause audio"
|
||||
|
||||
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 errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
if err := b.NotifyEventStream(payload, msg); err != nil {
|
||||
return errors.WithMessage(err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next moves audio playback to the next track.
|
||||
func (c *Camera) Next() error {
|
||||
payload := EventStreamPayload{
|
||||
Action: "nextTrack",
|
||||
Resource: "audioPlayback/player",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to skip audio"
|
||||
|
||||
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 errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
if err := b.NotifyEventStream(payload, msg); err != nil {
|
||||
return errors.WithMessage(err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shuffle toggles the audio play back mode to shuffle or not.
|
||||
func (c *Camera) Shuffle(on bool) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "audioPlayback/config",
|
||||
PublishResponse: true,
|
||||
Properties: ShuffleProperties{
|
||||
Config: BaseShuffleProperties{
|
||||
ShuffleActive: on,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
var msg string
|
||||
if on {
|
||||
msg = "failed to enable shuffle"
|
||||
} else {
|
||||
msg = "failed to disable shuffle"
|
||||
}
|
||||
|
||||
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) Continuous() (response *EventStreamResponse, err error) {
|
||||
return c.SetLoopBackMode("continuous")
|
||||
}
|
||||
|
||||
func (c *Camera) SingleTrack() (response *EventStreamResponse, err error) {
|
||||
return c.SetLoopBackMode("singleTrack")
|
||||
}
|
||||
|
||||
func (c *Camera) SetLoopBackMode(loopbackMode string) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "audioPlayback/config",
|
||||
PublishResponse: true,
|
||||
Properties: LoopbackModeProperties{
|
||||
Config: BaseLoopbackModeProperties{
|
||||
LoopbackMode: loopbackMode,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to set loop back mode to %s"
|
||||
|
||||
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, fmt.Sprintf(msg, loopbackMode))
|
||||
}
|
||||
return b.makeEventStreamRequest(payload, msg)
|
||||
}
|
||||
|
||||
func (c *Camera) GetAudioPlayback() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "audioPlayback",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to get audio playback"
|
||||
|
||||
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) EnableSleepTimer(sleepTime int64 /* milliseconds */, sleepTimeRel int) (response *EventStreamResponse, err error) {
|
||||
if sleepTime == 0 {
|
||||
sleepTime = 300 + (time.Now().UnixNano() / 1000000) /* milliseconds */
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "audioPlayback/config",
|
||||
PublishResponse: true,
|
||||
Properties: SleepTimerProperties{
|
||||
Config: BaseSleepTimerProperties{
|
||||
SleepTime: sleepTime,
|
||||
SleepTimeRel: sleepTimeRel,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to enable sleep timer"
|
||||
|
||||
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) DisableSleepTimer(sleepTimeRel int) (response *EventStreamResponse, err error) {
|
||||
if sleepTimeRel == 0 {
|
||||
sleepTimeRel = 300
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "audioPlayback/config",
|
||||
PublishResponse: true,
|
||||
Properties: SleepTimerProperties{
|
||||
Config: BaseSleepTimerProperties{
|
||||
SleepTime: 0,
|
||||
SleepTimeRel: sleepTimeRel,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to disable sleep timer"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
The follow methods are all related to the nightlight features of Arlo Baby.
|
||||
|
||||
NOTE: The current state is in: cameras[0]["properties"][0]["nightLight"] returned from the basestation.GetAssociatedCamerasState() method.
|
||||
*/
|
||||
func (c *Camera) NightLight(on bool) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
Enabled: on,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
var msg string
|
||||
if on {
|
||||
msg = "failed to turn night light on"
|
||||
} else {
|
||||
msg = "failed to turn night light 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)
|
||||
}
|
||||
|
||||
func (c *Camera) SetNightLightBrightness(level int) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
Brightness: level,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to set night light 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)
|
||||
}
|
||||
|
||||
// SetNightLightMode set the night light mode. Valid values are: "rainbow" or "rgb".
|
||||
func (c *Camera) SetNightLightMode(mode string) (response *EventStreamResponse, err error) {
|
||||
msg := "failed to set night light brightness"
|
||||
|
||||
if mode != "rainbow" && mode != "rgb" {
|
||||
return nil, errors.WithMessage(errors.New("mode can only be \"rainbow\" or \"rgb\""), msg)
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
Mode: mode,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// SetNightLightColor sets the night light color to the RGB value specified by the three parameters, which have valid values from 0-255.
|
||||
func (c *Camera) SetNightLightColor(red, blue, green int) (response *EventStreamResponse, err error) {
|
||||
// Sanity check; if the values are above or below the allowed limits, set them to their limit.
|
||||
if red < 0 {
|
||||
red = 0
|
||||
} else if red > 255 {
|
||||
red = 255
|
||||
}
|
||||
if blue < 0 {
|
||||
blue = 0
|
||||
} else if blue > 255 {
|
||||
blue = 255
|
||||
}
|
||||
if green < 0 {
|
||||
green = 0
|
||||
} else if green > 255 {
|
||||
green = 255
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
RGB: NightLightRGBProperties{
|
||||
Red: red,
|
||||
Blue: blue,
|
||||
Green: green,
|
||||
},
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to set night light color"
|
||||
|
||||
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) EnableNightLightTimer(sleepTime int64 /* milliseconds */, sleepTimeRel int) (response *EventStreamResponse, err error) {
|
||||
if sleepTime == 0 {
|
||||
sleepTime = 300 + (time.Now().UnixNano() / 1000000) /* milliseconds */
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
SleepTime: sleepTime,
|
||||
SleepTimeRel: sleepTimeRel,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to enable night light timer"
|
||||
|
||||
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) DisableNightLightTimer(sleepTimeRel int) (response *EventStreamResponse, err error) {
|
||||
if sleepTimeRel == 0 {
|
||||
sleepTimeRel = 300
|
||||
}
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
Properties: NightLightProperties{
|
||||
NightLight: BaseNightLightProperties{
|
||||
SleepTime: 0,
|
||||
SleepTimeRel: sleepTimeRel,
|
||||
},
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
msg := "failed to disable night light timer"
|
||||
|
||||
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)
|
||||
}
|
380
basestation.go
380
basestation.go
@ -2,33 +2,12 @@ package arlo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type BaseStationMetadata struct {
|
||||
InterfaceVersion int `json:"interfaceVersion"`
|
||||
ApiVersion int `json:"apiVersion"`
|
||||
State string `json:"state"`
|
||||
SwVersion string `json:"swVersion"`
|
||||
HwVersion string `json:"hwVersion"`
|
||||
ModelId string `json:"modelId"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
McsEnabled bool `json:"mcsEnabled"`
|
||||
AutoUpdateEnabled bool `json:"autoUpdateEnabled"`
|
||||
TimeZone string `json:"timeZone"`
|
||||
OlsonTimeZone string `json:"olsonTimeZone"`
|
||||
UploadBandwidthSaturated bool `json:"uploadBandwidthSaturated"`
|
||||
AntiFlicker map[string]int `json:"antiFlicker"`
|
||||
LowBatteryAlert map[string]bool `json:"lowBatteryAlert"`
|
||||
LowSignalAlert map[string]bool `json:"lowSignalAlert"`
|
||||
Claimed bool `json:"claimed"`
|
||||
TimeSyncState string `json:"timeSyncState"`
|
||||
Connectivity []struct {
|
||||
Type string `json:"type"`
|
||||
Connected bool `json:"connected"`
|
||||
} `json:"connectivity"`
|
||||
}
|
||||
const eventStreamTimeout = 10 * time.Second
|
||||
|
||||
// A Basestation is a Device that's not type "camera" (basestation, arloq, arloqs, etc.).
|
||||
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
|
||||
@ -40,101 +19,7 @@ type Basestation struct {
|
||||
// Basestations is an array of Basestation objects.
|
||||
type Basestations []Basestation
|
||||
|
||||
func (b *Basestation) Subscribe() error {
|
||||
b.eventStream = NewEventStream(BaseUrl+fmt.Sprintf(SubscribeUri, b.arlo.Account.Token), b.arlo.client.HttpClient)
|
||||
connected := b.eventStream.Listen()
|
||||
|
||||
outoffor:
|
||||
for {
|
||||
// TODO: Need to add a timeout here.
|
||||
// We blocking here because we can't really do anything with the event stream until we're connected.
|
||||
// Once we have confirmation that we're connected to the event stream, we will "subscribe" to events.
|
||||
select {
|
||||
case b.eventStream.Connected = <-connected:
|
||||
if b.eventStream.Connected {
|
||||
break outoffor
|
||||
} else {
|
||||
// TODO: What do we do if Connected is false? Probably need retry logic here.
|
||||
break
|
||||
}
|
||||
case <-b.eventStream.Close:
|
||||
return errors.New("failed to subscribe to the event stream")
|
||||
}
|
||||
}
|
||||
|
||||
// This is a crude (temporary?) way to monitor the connection. It's late and I'm tired, so this will probably go away.
|
||||
go func() {
|
||||
outoffor:
|
||||
for {
|
||||
select {
|
||||
case b.eventStream.Connected = <-connected:
|
||||
// TODO: What do we do if Connected is false? Probably need retry logic here.
|
||||
break outoffor
|
||||
case <-b.eventStream.Close:
|
||||
// TODO: Figure out what to do here if the eventStream is closed. (Panic?)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
payload := Payload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("subscriptions/%s_%s", b.UserId, TransIdPrefix),
|
||||
PublishResponse: false,
|
||||
Properties: map[string][1]string{"devices": {b.DeviceId}},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
if _, err := b.makeEventStreamRequest(payload, "failed to subscribe to the event stream"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) Unsubscribe() error {
|
||||
// TODO: Close channel to stop EventStream.
|
||||
//return errors.New("not implemented")
|
||||
if b.eventStream != nil {
|
||||
close(b.eventStream.Close)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) IsConnected() error {
|
||||
if !b.eventStream.Connected {
|
||||
return errors.New("basestation not connected to event stream")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) GetState() (*EventStreamResponse, error) {
|
||||
|
||||
payload := Payload{
|
||||
Action: "get",
|
||||
Resource: "basestation",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get basestation state")
|
||||
}
|
||||
|
||||
func (b *Basestation) GetAssociatedCamerasState() (*EventStreamResponse, error) {
|
||||
payload := Payload{
|
||||
Action: "get",
|
||||
Resource: "cameras",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get associated cameras state")
|
||||
}
|
||||
|
||||
func (b *Basestation) makeEventStreamRequest(payload Payload, msg string) (*EventStreamResponse, error) {
|
||||
func (b *Basestation) makeEventStreamRequest(payload EventStreamPayload, msg string) (response *EventStreamResponse, err error) {
|
||||
transId := genTransId()
|
||||
payload.TransId = transId
|
||||
|
||||
@ -145,17 +30,264 @@ func (b *Basestation) makeEventStreamRequest(payload Payload, msg string) (*Even
|
||||
b.eventStream.Subscriptions[transId] = make(chan *EventStreamResponse)
|
||||
defer close(b.eventStream.Subscriptions[transId])
|
||||
|
||||
resp, err := b.arlo.post(fmt.Sprintf(NotifyUri, b.DeviceId), b.XCloudId, payload, nil)
|
||||
if err := checkRequest(*resp, err, msg); err != nil {
|
||||
if err := b.NotifyEventStream(payload, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timer := time.NewTimer(eventStreamTimeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case eventStreamResponse := <-b.eventStream.Subscriptions[transId]:
|
||||
return eventStreamResponse, nil
|
||||
case <-timer.C:
|
||||
err = fmt.Errorf("event stream response timed out after %.0f second", eventStreamTimeout.Seconds())
|
||||
return nil, errors.WithMessage(err, msg)
|
||||
case response := <-b.eventStream.Subscriptions[transId]:
|
||||
return response, nil
|
||||
case err = <-b.eventStream.Error:
|
||||
return nil, errors.Wrap(err, "failed to get basestation")
|
||||
return nil, errors.Wrap(err, msg)
|
||||
case <-b.eventStream.Close:
|
||||
return nil, errors.New("event stream was closed before response was read")
|
||||
err = errors.New("event stream was closed before response was read")
|
||||
return nil, errors.WithMessage(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Find returns a basestation with the device id passed in.
|
||||
func (bs *Basestations) Find(deviceId string) *Basestation {
|
||||
for _, b := range *bs {
|
||||
if b.DeviceId == deviceId {
|
||||
return &b
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) IsConnected() error {
|
||||
if !b.eventStream.Connected {
|
||||
return errors.New("basestation not connected to event stream")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) Subscribe() error {
|
||||
b.eventStream = NewEventStream(BaseUrl+fmt.Sprintf(SubscribeUri, b.arlo.Account.Token), b.arlo.client.HttpClient)
|
||||
connected := b.eventStream.Listen()
|
||||
|
||||
forLoop:
|
||||
for {
|
||||
// We blocking here because we can't really do anything with the event stream until we're connected.
|
||||
// Once we have confirmation that we're connected to the event stream, we will "subscribe" to events.
|
||||
select {
|
||||
case b.eventStream.Connected = <-connected:
|
||||
if b.eventStream.Connected {
|
||||
break forLoop
|
||||
} else {
|
||||
return errors.New("failed to subscribe to the event stream")
|
||||
}
|
||||
case <-b.eventStream.Close:
|
||||
return errors.New("failed to subscribe to the event stream")
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.Ping(); err != nil {
|
||||
return errors.WithMessage(err, "failed to subscribe to the event stream")
|
||||
}
|
||||
|
||||
// The Arlo event stream requires a "ping" every 30s.
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(30 * time.Second)
|
||||
if err := b.Ping(); err != nil {
|
||||
b.Unsubscribe()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) Unsubscribe() error {
|
||||
// Close channel to stop EventStream.
|
||||
if b.eventStream != nil {
|
||||
close(b.eventStream.Close)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping makes a call to the subscriptions endpoint. The Arlo event stream requires this message to be sent every 30s.
|
||||
func (b *Basestation) Ping() error {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("subscriptions/%s_%s", b.UserId, TransIdPrefix),
|
||||
PublishResponse: false,
|
||||
Properties: map[string][1]string{"devices": {b.DeviceId}},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
if _, err := b.makeEventStreamRequest(payload, "failed to ping the event stream"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) NotifyEventStream(payload EventStreamPayload, msg string) error {
|
||||
resp, err := b.arlo.post(fmt.Sprintf(NotifyUri, b.DeviceId), b.XCloudId, payload, nil)
|
||||
if err := checkRequest(resp, err, msg); err != nil {
|
||||
return errors.WithMessage(err, "failed to notify event stream")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basestation) GetState() (response *EventStreamResponse, err error) {
|
||||
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "basestation",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get basestation state")
|
||||
}
|
||||
|
||||
func (b *Basestation) GetAssociatedCamerasState() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "cameras",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get associated cameras state")
|
||||
}
|
||||
|
||||
func (b *Basestation) GetRules() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "rules",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get rules")
|
||||
}
|
||||
|
||||
func (b *Basestation) GetCalendarMode() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "schedule",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get schedule")
|
||||
}
|
||||
|
||||
// SetCalendarMode toggles calendar mode.
|
||||
// NOTE: The Arlo API seems to disable calendar mode when switching to other modes, if it's enabled.
|
||||
// You should probably do the same, although, the UI reflects the switch from calendar mode to say armed mode without explicitly setting calendar mode to inactive.
|
||||
func (b *Basestation) SetCalendarMode(active bool) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "schedule",
|
||||
PublishResponse: true,
|
||||
Properties: BasestationScheduleProperties{
|
||||
Active: active,
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to set schedule")
|
||||
}
|
||||
|
||||
func (b *Basestation) GetModes() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "get",
|
||||
Resource: "modes",
|
||||
PublishResponse: false,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get modes")
|
||||
}
|
||||
|
||||
func (b *Basestation) SetCustomMode(mode string) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "modes",
|
||||
PublishResponse: true,
|
||||
Properties: BasestationModeProperties{
|
||||
Active: mode,
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to set mode")
|
||||
}
|
||||
|
||||
func (b *Basestation) DeleteMode(mode string) (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "delete",
|
||||
Resource: fmt.Sprintf("modes/%s", mode),
|
||||
PublishResponse: true,
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to set mode")
|
||||
}
|
||||
|
||||
func (b *Basestation) Arm() (response *EventStreamResponse, err error) {
|
||||
return b.SetCustomMode("mode1")
|
||||
}
|
||||
|
||||
func (b *Basestation) Disarm() (response *EventStreamResponse, err error) {
|
||||
return b.SetCustomMode("mode0")
|
||||
}
|
||||
|
||||
func (b *Basestation) SirenOn() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "siren",
|
||||
PublishResponse: true,
|
||||
Properties: SirenProperties{
|
||||
SirenState: "on",
|
||||
Duration: 300,
|
||||
Volume: 8,
|
||||
Pattern: "alarm",
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get modes")
|
||||
}
|
||||
|
||||
func (b *Basestation) SirenOff() (response *EventStreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: "siren",
|
||||
PublishResponse: true,
|
||||
Properties: SirenProperties{
|
||||
SirenState: "off",
|
||||
Duration: 300,
|
||||
Volume: 8,
|
||||
Pattern: "alarm",
|
||||
},
|
||||
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||
To: b.DeviceId,
|
||||
}
|
||||
|
||||
return b.makeEventStreamRequest(payload, "failed to get modes")
|
||||
}
|
||||
|
274
camera.go
274
camera.go
@ -14,13 +14,102 @@ 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() (*StreamResponse, error) {
|
||||
body := Payload{
|
||||
func (c *Camera) StartStream() (response *StreamResponse, err error) {
|
||||
payload := EventStreamPayload{
|
||||
Action: "set",
|
||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||
PublishResponse: true,
|
||||
@ -33,55 +122,194 @@ func (c *Camera) StartStream() (*StreamResponse, error) {
|
||||
To: c.ParentId,
|
||||
}
|
||||
|
||||
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, body, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to start stream")
|
||||
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()
|
||||
|
||||
var streamResponse StreamResponse
|
||||
if err := resp.Decode(&streamResponse); err != nil {
|
||||
if err := resp.Decode(response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !streamResponse.Success {
|
||||
return nil, errors.WithMessage(errors.New("status was false"), "failed to start stream")
|
||||
if !response.Success {
|
||||
return nil, errors.WithMessage(errors.New("status was false"), msg)
|
||||
}
|
||||
|
||||
streamResponse.Data.Url = strings.Replace(streamResponse.Data.Url, "rtsp://", "rtsps://", 1)
|
||||
response.Data.Url = strings.Replace(response.Data.Url, "rtsp://", "rtsps://", 1)
|
||||
|
||||
return &streamResponse, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// TakeSnapshot causes the camera to record a snapshot.
|
||||
func (c *Camera) TakeSnapshot() (*StreamResponse, error) {
|
||||
streamResponse, err := c.StartStream()
|
||||
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, "failed to take snapshot")
|
||||
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, "failed to take snapshot")
|
||||
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
|
||||
return nil, errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
return streamResponse, nil
|
||||
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() (*StreamResponse, error) {
|
||||
streamResponse, err := c.StartStream()
|
||||
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, "failed to start recording")
|
||||
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, "failed to start recording")
|
||||
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
|
||||
return nil, errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
return streamResponse, nil
|
||||
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)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type Device struct {
|
||||
ArloMobilePlanThreshold int `json:"arloMobilePlanThreshold"`
|
||||
Connectivity Connectivity `json:"connectivity"`
|
||||
CriticalBatteryState bool `json:"criticalBatteryState"`
|
||||
DateCreated float64 `json:"dateCreated"`
|
||||
DateCreated int64 `json:"dateCreated"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DeviceType string `json:"deviceType"`
|
||||
@ -19,7 +19,7 @@ type Device struct {
|
||||
InterfaceVersion string `json:"interfaceVersion"`
|
||||
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
||||
LastImageUploaded string `json:"lastImageUploaded"`
|
||||
LastModified float64 `json:"lastModified"`
|
||||
LastModified int64 `json:"lastModified"`
|
||||
MigrateActivityZone bool `json:"migrateActivityZone"`
|
||||
MobileCarrier string `json:"mobileCarrier"`
|
||||
MobileTrialUsed bool `json:"mobileTrialUsed"`
|
||||
@ -122,5 +122,5 @@ func (d *Device) UpdateDeviceName(name string) error {
|
||||
|
||||
body := map[string]string{"deviceId": d.DeviceId, "deviceName": name, "parentId": d.ParentId}
|
||||
resp, err := d.arlo.put(DeviceRenameUri, d.XCloudId, body, nil)
|
||||
return checkRequest(*resp, err, "failed to update device name")
|
||||
return checkRequest(resp, err, "failed to update device name")
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
@ -80,6 +81,7 @@ func (c *Client) newRequest(method string, uri string, body interface{}, header
|
||||
return nil, errors.Wrap(err, "failed to create request object")
|
||||
}
|
||||
}
|
||||
log.Printf("\n\nBODY (%s): %s\n\n", uri, buf)
|
||||
|
||||
u := c.BaseURL.String() + uri
|
||||
req, err := http.NewRequest(method, u, buf)
|
||||
|
59
library.go
59
library.go
@ -2,8 +2,6 @@ package arlo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// LibraryMetaData is the library meta data.
|
||||
@ -36,37 +34,34 @@ type Recording struct {
|
||||
|
||||
type Library []Recording
|
||||
|
||||
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (*LibraryMetaDataResponse, error) {
|
||||
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (response *LibraryMetaDataResponse, err error) {
|
||||
|
||||
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||
resp, err := a.post(LibraryMetadataUri, "", body, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to get library metadata")
|
||||
}
|
||||
|
||||
var libraryMetaDataResponse LibraryMetaDataResponse
|
||||
if err := resp.Decode(&libraryMetaDataResponse); err != nil {
|
||||
if err := checkHttpRequest(resp, err, "failed to get library metadata"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &libraryMetaDataResponse, nil
|
||||
if err := resp.Decode(response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (*LibraryResponse, error) {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (response *LibraryResponse, err error) {
|
||||
|
||||
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||
resp, err := a.post(LibraryUri, "", body, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to get library")
|
||||
}
|
||||
|
||||
var libraryResponse LibraryResponse
|
||||
if err := resp.Decode(&libraryResponse); err != nil {
|
||||
if err := checkHttpRequest(resp, err, "failed to get library"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &libraryResponse, nil
|
||||
if err := resp.Decode(response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
/*
|
||||
@ -76,20 +71,11 @@ func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (*LibraryResponse, error)
|
||||
|
||||
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
|
||||
*/
|
||||
func (a *Arlo) DeleteRecording(r Recording) (*Error, error) {
|
||||
func (a *Arlo) DeleteRecording(r Recording) error {
|
||||
|
||||
body := map[string]Library{"data": {r}}
|
||||
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to delete recording")
|
||||
}
|
||||
|
||||
var status Error
|
||||
if err := resp.Decode(&status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &status, nil
|
||||
return checkRequest(resp, err, "failed to delete recording")
|
||||
}
|
||||
|
||||
/*
|
||||
@ -99,18 +85,9 @@ func (a *Arlo) DeleteRecording(r Recording) (*Error, error) {
|
||||
|
||||
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
|
||||
*/
|
||||
func (a *Arlo) BatchDeleteRecordings(l Library) (*Error, error) {
|
||||
func (a *Arlo) BatchDeleteRecordings(l Library) error {
|
||||
|
||||
body := map[string]Library{"data": l}
|
||||
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to delete recordings")
|
||||
}
|
||||
|
||||
var status Error
|
||||
if err := resp.Decode(&status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &status, nil
|
||||
return checkRequest(resp, err, "failed to delete recordings")
|
||||
}
|
||||
|
21
responses.go
21
responses.go
@ -3,42 +3,37 @@ package arlo
|
||||
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
|
||||
type LoginResponse struct {
|
||||
Data Account
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
||||
type DeviceResponse struct {
|
||||
Data Devices
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
||||
type LibraryMetaDataResponse struct {
|
||||
Data LibraryMetaData
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
type LibraryResponse struct {
|
||||
Data Library
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
type StreamResponse struct {
|
||||
Data StreamUrl
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
type RecordingResponse struct {
|
||||
Data StreamUrl
|
||||
Error
|
||||
Status
|
||||
}
|
||||
|
||||
type EventStreamResponse struct {
|
||||
Action string `json:"action,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Properties interface{} `json:"properties,omitempty"`
|
||||
TransId string `json:"transId"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Status string `json:"status"`
|
||||
EventStreamPayload
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
176
types.go
176
types.go
@ -40,38 +40,60 @@ type Account struct {
|
||||
PolicyUpdate bool `json:"policyUpdate"`
|
||||
ValidEmail bool `json:"validEmail"`
|
||||
Arlo bool `json:"arlo"`
|
||||
DateCreated float64 `json:"dateCreated"`
|
||||
DateCreated int64 `json:"dateCreated"`
|
||||
}
|
||||
|
||||
// Friend is the account data for non-primary account holders designated as friends.
|
||||
type Friend struct {
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Devices DeviceOrder `json:"devices"`
|
||||
LastModified float64 `json:"lastModified"`
|
||||
LastModified int64 `json:"lastModified"`
|
||||
AdminUser bool `json:"adminUser"`
|
||||
Email string `json:"email"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// Owner is part of the Device data.
|
||||
// Connectivity is part of the Device data.
|
||||
type Connectivity struct {
|
||||
ActiveNetwork string `json:"activeNetwork"`
|
||||
APN string `json:"apn"`
|
||||
CarrierFw string `json:"carrierFw"`
|
||||
ActiveNetwork string `json:"activeNetwork,omitempty"`
|
||||
APN string `json:"apn,omitempty"`
|
||||
CarrierFw string `json:"carrierFw,omitempty"`
|
||||
Connected bool `json:"connected"`
|
||||
FWVersion string `json:"fwVersion"`
|
||||
ICCID string `json:"iccid"`
|
||||
IMEI string `json:"imei"`
|
||||
MEPStatus string `json:"mepStatus"`
|
||||
MSISDN string `json:"msisdn"`
|
||||
NetworkMode string `json:"networkMode"`
|
||||
NetworkName string `json:"networkName"`
|
||||
RFBand int `json:"rfBand"`
|
||||
FWVersion string `json:"fwVersion,omitempty"`
|
||||
ICCID string `json:"iccid,omitempty"`
|
||||
IMEI string `json:"imei,omitempty"`
|
||||
MEPStatus string `json:"mepStatus,omitempty"`
|
||||
MSISDN string `json:"msisdn,omitempty"`
|
||||
NetworkMode string `json:"networkMode,omitempty"`
|
||||
NetworkName string `json:"networkName,omitempty"`
|
||||
RFBand int `json:"rfBand,omitempty"`
|
||||
Roaming bool `json:"roaming"`
|
||||
RoamingAllowed bool `json:"roamingAllowed"`
|
||||
SignalStrength string `json:"signalStrength"`
|
||||
Type string `json:"type"`
|
||||
WWANIPAddr string `json:"wwanIpAddr"`
|
||||
SignalStrength string `json:"signalStrength,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
WWANIPAddr string `json:"wwanIpAddr,omitempty"`
|
||||
}
|
||||
|
||||
type BaseStationMetadata struct {
|
||||
InterfaceVersion int `json:"interfaceVersion"`
|
||||
ApiVersion int `json:"apiVersion"`
|
||||
State string `json:"state"`
|
||||
SwVersion string `json:"swVersion"`
|
||||
HwVersion string `json:"hwVersion"`
|
||||
ModelId string `json:"modelId"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
McsEnabled bool `json:"mcsEnabled"`
|
||||
AutoUpdateEnabled bool `json:"autoUpdateEnabled"`
|
||||
TimeZone string `json:"timeZone"`
|
||||
OlsonTimeZone string `json:"olsonTimeZone"`
|
||||
UploadBandwidthSaturated bool `json:"uploadBandwidthSaturated"`
|
||||
AntiFlicker map[string]int `json:"antiFlicker"`
|
||||
LowBatteryAlert map[string]bool `json:"lowBatteryAlert"`
|
||||
LowSignalAlert map[string]bool `json:"lowSignalAlert"`
|
||||
Claimed bool `json:"claimed"`
|
||||
TimeSyncState string `json:"timeSyncState"`
|
||||
Connectivity Connectivity `json:"connectivity"`
|
||||
}
|
||||
|
||||
// Owner is the owner of a Device data.
|
||||
@ -97,8 +119,118 @@ type StreamUrl struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// Payload represents the message that will be sent to the arlo servers via the Notify API.
|
||||
type Payload struct {
|
||||
type BaseDetectionProperties struct {
|
||||
Armed bool `json:"armed"`
|
||||
Sensitivity int `json:"sensitivity"`
|
||||
Zones []string `json:"zones,omitempty"`
|
||||
}
|
||||
|
||||
// MotionDetectionProperties is the Properties struct for the EventStreamPayload type.
|
||||
type MotionDetectionProperties struct {
|
||||
BaseDetectionProperties `json:"motionDetection"`
|
||||
}
|
||||
|
||||
// AudioDetectionProperties is the Properties struct for the EventStreamPayload type.
|
||||
type AudioDetectionProperties struct {
|
||||
BaseDetectionProperties `json:"audioDetection"`
|
||||
}
|
||||
|
||||
type EmailNotification struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
EmailList []string `json:"emailList"`
|
||||
PushNotification bool `json:"pushNotification"`
|
||||
}
|
||||
|
||||
type PlayTrackProperties struct {
|
||||
TrackId string `json:"trackId"`
|
||||
Position int `json:"position"`
|
||||
}
|
||||
|
||||
type BaseLoopbackModeProperties struct {
|
||||
LoopbackMode string `json:"loopbackMode"`
|
||||
}
|
||||
|
||||
type LoopbackModeProperties struct {
|
||||
Config BaseLoopbackModeProperties `json:"config"`
|
||||
}
|
||||
|
||||
type BaseSleepTimerProperties struct {
|
||||
SleepTime int64 `json:"sleepTime"`
|
||||
SleepTimeRel int `json:"sleepTimeRel"`
|
||||
}
|
||||
|
||||
type SleepTimerProperties struct {
|
||||
Config BaseSleepTimerProperties `json:"config"`
|
||||
}
|
||||
type BaseEventActionProperties struct {
|
||||
ActionType string `json:"actionType"`
|
||||
StopType string `json:"stopType"`
|
||||
Timeout int `json:"timeout"`
|
||||
EmailNotification `json:"emailNotification"`
|
||||
}
|
||||
|
||||
type EventActionProperties struct {
|
||||
BaseEventActionProperties `json:"eventAction"`
|
||||
}
|
||||
|
||||
type BaseShuffleProperties struct {
|
||||
ShuffleActive bool `json:"shuffleActive"`
|
||||
}
|
||||
|
||||
type ShuffleProperties struct {
|
||||
Config BaseShuffleProperties `json:"config"`
|
||||
}
|
||||
|
||||
type VolumeProperties struct {
|
||||
Mute bool `json:"mute"`
|
||||
Volume int `json:"volume,omitempty"`
|
||||
}
|
||||
|
||||
type SpeakerProperties struct {
|
||||
Speaker VolumeProperties `json:"speaker"`
|
||||
}
|
||||
|
||||
type NightLightRGBProperties struct {
|
||||
Red int `json:"red"`
|
||||
Green int `json:"green"`
|
||||
Blue int `json:"blue"`
|
||||
}
|
||||
|
||||
type BaseNightLightProperties struct {
|
||||
Brightness int `json:"brightness,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
RGB NightLightRGBProperties `json:"mode,omitempty"`
|
||||
SleepTime int64 `json:"sleepTime,omitempty"`
|
||||
SleepTimeRel int `json:"sleepTimeRel,omitempty"`
|
||||
}
|
||||
|
||||
type NightLightProperties struct {
|
||||
NightLight BaseNightLightProperties `json:"nightLight"`
|
||||
}
|
||||
|
||||
type SirenProperties struct {
|
||||
SirenState string `json:"sirenState"`
|
||||
Duration int `json:"duration"`
|
||||
Volume int `json:"volume"`
|
||||
Pattern string `json:"pattern"`
|
||||
}
|
||||
|
||||
type BasestationModeProperties struct {
|
||||
Active string `json:"active"`
|
||||
}
|
||||
|
||||
type BasestationScheduleProperties struct {
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type CameraProperties struct {
|
||||
PrivacyActive bool `json:"privacyActive"`
|
||||
Brightness int `json:"brightness,omitempty"`
|
||||
}
|
||||
|
||||
// EventStreamPayload is the message that will be sent to the arlo servers via the /notify API.
|
||||
type EventStreamPayload struct {
|
||||
Action string `json:"action,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
PublishResponse bool `json:"publishResponse"`
|
||||
@ -108,14 +240,16 @@ type Payload struct {
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
// Data is part of the Status message fragment returned by most calls to the Arlo API.
|
||||
// Data is only populated when Success is false.
|
||||
type Data struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// map[data:map[message:The device does not exist. reason:No such device. error:2217] success:false]
|
||||
type Error struct {
|
||||
// Status is the message fragment returned from most http calls to the Arlo API.
|
||||
type Status struct {
|
||||
Data `json:"Data,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
19
util.go
19
util.go
@ -15,19 +15,28 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func checkRequest(resp request.Response, err error, msg string) error {
|
||||
func checkHttpRequest(resp *request.Response, err error, msg string) error {
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.WithMessage(errors.New(fmt.Sprintf("http request failed: %s (%d)", resp.Status, resp.StatusCode)), msg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequest(resp *request.Response, err error, msg string) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
var status Error
|
||||
if err := resp.Decode(&status); err != nil {
|
||||
if err := checkHttpRequest(resp, err, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.WithMessage(errors.New(fmt.Sprintf("http request failed: %s (%d)", resp.Status, resp.StatusCode)), msg)
|
||||
var status Status
|
||||
if err := resp.Decode(&status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status.Success == false {
|
||||
|
Loading…
Reference in New Issue
Block a user