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
|
Cameras Cameras
|
||||||
}
|
}
|
||||||
|
|
||||||
func newArlo(user string, pass string) *Arlo {
|
func newArlo(user string, pass string) (arlo *Arlo) {
|
||||||
|
|
||||||
c, _ := request.NewClient(BaseUrl)
|
c, _ := request.NewClient(BaseUrl)
|
||||||
|
|
||||||
// Add important headers.
|
// Add important headers.
|
||||||
@ -31,14 +32,15 @@ func newArlo(user string, pass string) *Arlo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(user string, pass string) (*Arlo, error) {
|
func Login(user string, pass string) (arlo *Arlo, err error) {
|
||||||
a := newArlo(user, pass)
|
arlo = newArlo(user, pass)
|
||||||
|
|
||||||
body := map[string]string{"email": a.user, "password": a.pass}
|
body := map[string]string{"email": arlo.user, "password": arlo.pass}
|
||||||
resp, err := a.post(LoginUri, "", body, nil)
|
resp, err := arlo.post(LoginUri, "", body, nil)
|
||||||
if err != nil {
|
if err := checkHttpRequest(resp, err, "login request failed"); err != nil {
|
||||||
return nil, errors.WithMessage(err, "login request failed")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var loginResponse LoginResponse
|
var loginResponse LoginResponse
|
||||||
if err := resp.Decode(&loginResponse); err != nil {
|
if err := resp.Decode(&loginResponse); err != nil {
|
||||||
@ -47,33 +49,33 @@ func Login(user string, pass string) (*Arlo, error) {
|
|||||||
|
|
||||||
if loginResponse.Success {
|
if loginResponse.Success {
|
||||||
// Cache the auth token.
|
// 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.
|
// 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.
|
// 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")
|
return nil, errors.WithMessage(err, "failed to login")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("failed to login")
|
return nil, errors.New("failed to login")
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return arlo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Arlo) Logout() error {
|
func (a *Arlo) Logout() error {
|
||||||
resp, err := a.put(LogoutUri, "", nil, nil)
|
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.
|
// GetDevices returns an array of all devices.
|
||||||
// When you call Login, this method is called and all devices are cached in the arlo object.
|
// 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)
|
resp, err := a.get(DevicesUri, "", nil)
|
||||||
if err != nil {
|
if err := checkHttpRequest(resp, err, "failed to get devices"); err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to get devices")
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@ -82,14 +84,14 @@ func (a *Arlo) GetDevices() (Devices, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deviceResponse.Data) == 0 {
|
|
||||||
return nil, errors.New("no devices found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deviceResponse.Success {
|
if !deviceResponse.Success {
|
||||||
return nil, errors.New("failed to get devices")
|
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 {
|
for i := range deviceResponse.Data {
|
||||||
deviceResponse.Data[i].arlo = a
|
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.
|
// UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given.
|
||||||
func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) error {
|
func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) error {
|
||||||
resp, err := a.post(DeviceDisplayOrderUri, "", d, nil)
|
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.
|
// UpdateProfile takes a first and last name, and updates the user profile with that information.
|
||||||
func (a *Arlo) UpdateProfile(firstName, lastName string) error {
|
func (a *Arlo) UpdateProfile(firstName, lastName string) error {
|
||||||
body := map[string]string{"firstName": firstName, "lastName": lastName}
|
body := map[string]string{"firstName": firstName, "lastName": lastName}
|
||||||
resp, err := a.put(UserProfileUri, "", body, nil)
|
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 {
|
func (a *Arlo) UpdatePassword(pass string) error {
|
||||||
body := map[string]string{"currentPassword": a.pass, "newPassword": pass}
|
body := map[string]string{"currentPassword": a.pass, "newPassword": pass}
|
||||||
resp, err := a.post(UserChangePasswordUri, "", body, nil)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,5 +144,5 @@ func (a *Arlo) UpdatePassword(pass string) error {
|
|||||||
|
|
||||||
func (a *Arlo) UpdateFriends(f Friend) error {
|
func (a *Arlo) UpdateFriends(f Friend) error {
|
||||||
resp, err := a.put(UserFriendsUri, "", f, nil)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseStationMetadata struct {
|
const eventStreamTimeout = 10 * time.Second
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Basestation is a Device that's not type "camera" (basestation, arloq, arloqs, etc.).
|
// 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.
|
// 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.
|
// Basestations is an array of Basestation objects.
|
||||||
type Basestations []Basestation
|
type Basestations []Basestation
|
||||||
|
|
||||||
func (b *Basestation) Subscribe() error {
|
func (b *Basestation) makeEventStreamRequest(payload EventStreamPayload, msg string) (response *EventStreamResponse, err 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) {
|
|
||||||
transId := genTransId()
|
transId := genTransId()
|
||||||
payload.TransId = transId
|
payload.TransId = transId
|
||||||
|
|
||||||
@ -145,17 +30,264 @@ func (b *Basestation) makeEventStreamRequest(payload Payload, msg string) (*Even
|
|||||||
b.eventStream.Subscriptions[transId] = make(chan *EventStreamResponse)
|
b.eventStream.Subscriptions[transId] = make(chan *EventStreamResponse)
|
||||||
defer close(b.eventStream.Subscriptions[transId])
|
defer close(b.eventStream.Subscriptions[transId])
|
||||||
|
|
||||||
resp, err := b.arlo.post(fmt.Sprintf(NotifyUri, b.DeviceId), b.XCloudId, payload, nil)
|
if err := b.NotifyEventStream(payload, msg); err != nil {
|
||||||
if err := checkRequest(*resp, err, msg); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(eventStreamTimeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case eventStreamResponse := <-b.eventStream.Subscriptions[transId]:
|
case <-timer.C:
|
||||||
return eventStreamResponse, nil
|
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:
|
case err = <-b.eventStream.Error:
|
||||||
return nil, errors.Wrap(err, "failed to get basestation")
|
return nil, errors.Wrap(err, msg)
|
||||||
case <-b.eventStream.Close:
|
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.
|
// Cameras is an array of Camera objects.
|
||||||
type Cameras []Camera
|
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.
|
// 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
|
// 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:
|
// 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" }
|
// 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) {
|
func (c *Camera) StartStream() (response *StreamResponse, err error) {
|
||||||
body := Payload{
|
payload := EventStreamPayload{
|
||||||
Action: "set",
|
Action: "set",
|
||||||
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||||
PublishResponse: true,
|
PublishResponse: true,
|
||||||
@ -33,55 +122,194 @@ func (c *Camera) StartStream() (*StreamResponse, error) {
|
|||||||
To: c.ParentId,
|
To: c.ParentId,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, body, nil)
|
msg := "failed to start stream"
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var streamResponse StreamResponse
|
if err := resp.Decode(response); err != nil {
|
||||||
if err := resp.Decode(&streamResponse); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !streamResponse.Success {
|
if !response.Success {
|
||||||
return nil, errors.WithMessage(errors.New("status was false"), "failed to start stream")
|
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.
|
// TakeSnapshot causes the camera to record a snapshot.
|
||||||
func (c *Camera) TakeSnapshot() (*StreamResponse, error) {
|
func (c *Camera) TakeSnapshot() (response *StreamResponse, err error) {
|
||||||
streamResponse, err := c.StartStream()
|
msg := "failed to take snapshot"
|
||||||
|
|
||||||
|
response, err = c.StartStream()
|
||||||
if err != nil {
|
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}
|
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)
|
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
|
||||||
if err := checkRequest(*resp, err, "failed to update device name"); err != nil {
|
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to take snapshot")
|
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
|
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
|
||||||
// or something similar.
|
// or something similar.
|
||||||
func (c *Camera) StartRecording() (*StreamResponse, error) {
|
func (c *Camera) StartRecording() (response *StreamResponse, err error) {
|
||||||
streamResponse, err := c.StartStream()
|
msg := "failed to start recording"
|
||||||
|
|
||||||
|
response, err = c.StartStream()
|
||||||
if err != nil {
|
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}
|
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)
|
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
|
||||||
if err := checkRequest(*resp, err, "failed to update device name"); err != nil {
|
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to start recording")
|
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"`
|
ArloMobilePlanThreshold int `json:"arloMobilePlanThreshold"`
|
||||||
Connectivity Connectivity `json:"connectivity"`
|
Connectivity Connectivity `json:"connectivity"`
|
||||||
CriticalBatteryState bool `json:"criticalBatteryState"`
|
CriticalBatteryState bool `json:"criticalBatteryState"`
|
||||||
DateCreated float64 `json:"dateCreated"`
|
DateCreated int64 `json:"dateCreated"`
|
||||||
DeviceId string `json:"deviceId"`
|
DeviceId string `json:"deviceId"`
|
||||||
DeviceName string `json:"deviceName"`
|
DeviceName string `json:"deviceName"`
|
||||||
DeviceType string `json:"deviceType"`
|
DeviceType string `json:"deviceType"`
|
||||||
@ -19,7 +19,7 @@ type Device struct {
|
|||||||
InterfaceVersion string `json:"interfaceVersion"`
|
InterfaceVersion string `json:"interfaceVersion"`
|
||||||
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
||||||
LastImageUploaded string `json:"lastImageUploaded"`
|
LastImageUploaded string `json:"lastImageUploaded"`
|
||||||
LastModified float64 `json:"lastModified"`
|
LastModified int64 `json:"lastModified"`
|
||||||
MigrateActivityZone bool `json:"migrateActivityZone"`
|
MigrateActivityZone bool `json:"migrateActivityZone"`
|
||||||
MobileCarrier string `json:"mobileCarrier"`
|
MobileCarrier string `json:"mobileCarrier"`
|
||||||
MobileTrialUsed bool `json:"mobileTrialUsed"`
|
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}
|
body := map[string]string{"deviceId": d.DeviceId, "deviceName": name, "parentId": d.ParentId}
|
||||||
resp, err := d.arlo.put(DeviceRenameUri, d.XCloudId, body, nil)
|
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"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"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")
|
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
|
u := c.BaseURL.String() + uri
|
||||||
req, err := http.NewRequest(method, u, buf)
|
req, err := http.NewRequest(method, u, buf)
|
||||||
|
59
library.go
59
library.go
@ -2,8 +2,6 @@ package arlo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LibraryMetaData is the library meta data.
|
// LibraryMetaData is the library meta data.
|
||||||
@ -36,37 +34,34 @@ type Recording struct {
|
|||||||
|
|
||||||
type Library []Recording
|
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")}
|
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||||
resp, err := a.post(LibraryMetadataUri, "", body, nil)
|
resp, err := a.post(LibraryMetadataUri, "", body, nil)
|
||||||
|
if err := checkHttpRequest(resp, err, "failed to get library metadata"); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to get library metadata")
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryMetaDataResponse LibraryMetaDataResponse
|
|
||||||
if err := resp.Decode(&libraryMetaDataResponse); err != nil {
|
|
||||||
return nil, err
|
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")}
|
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||||
resp, err := a.post(LibraryUri, "", body, nil)
|
resp, err := a.post(LibraryUri, "", body, nil)
|
||||||
if err != nil {
|
if err := checkHttpRequest(resp, err, "failed to get library"); err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to get library")
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryResponse LibraryResponse
|
|
||||||
if err := resp.Decode(&libraryResponse); err != nil {
|
|
||||||
return nil, err
|
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.
|
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}}
|
body := map[string]Library{"data": {r}}
|
||||||
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
||||||
if err != nil {
|
return checkRequest(resp, err, "failed to delete recording")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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.
|
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}
|
body := map[string]Library{"data": l}
|
||||||
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
resp, err := a.post(LibraryRecycleUri, "", body, nil)
|
||||||
if err != nil {
|
return checkRequest(resp, err, "failed to delete recordings")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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.
|
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
Data Account
|
Data Account
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
||||||
type DeviceResponse struct {
|
type DeviceResponse struct {
|
||||||
Data Devices
|
Data Devices
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
||||||
type LibraryMetaDataResponse struct {
|
type LibraryMetaDataResponse struct {
|
||||||
Data LibraryMetaData
|
Data LibraryMetaData
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type LibraryResponse struct {
|
type LibraryResponse struct {
|
||||||
Data Library
|
Data Library
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamResponse struct {
|
type StreamResponse struct {
|
||||||
Data StreamUrl
|
Data StreamUrl
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingResponse struct {
|
type RecordingResponse struct {
|
||||||
Data StreamUrl
|
Data StreamUrl
|
||||||
Error
|
Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventStreamResponse struct {
|
type EventStreamResponse struct {
|
||||||
Action string `json:"action,omitempty"`
|
EventStreamPayload
|
||||||
Resource string `json:"resource,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
Properties interface{} `json:"properties,omitempty"`
|
|
||||||
TransId string `json:"transId"`
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
}
|
||||||
|
176
types.go
176
types.go
@ -40,38 +40,60 @@ type Account struct {
|
|||||||
PolicyUpdate bool `json:"policyUpdate"`
|
PolicyUpdate bool `json:"policyUpdate"`
|
||||||
ValidEmail bool `json:"validEmail"`
|
ValidEmail bool `json:"validEmail"`
|
||||||
Arlo bool `json:"arlo"`
|
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 {
|
type Friend struct {
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
Devices DeviceOrder `json:"devices"`
|
Devices DeviceOrder `json:"devices"`
|
||||||
LastModified float64 `json:"lastModified"`
|
LastModified int64 `json:"lastModified"`
|
||||||
AdminUser bool `json:"adminUser"`
|
AdminUser bool `json:"adminUser"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Owner is part of the Device data.
|
// Connectivity is part of the Device data.
|
||||||
type Connectivity struct {
|
type Connectivity struct {
|
||||||
ActiveNetwork string `json:"activeNetwork"`
|
ActiveNetwork string `json:"activeNetwork,omitempty"`
|
||||||
APN string `json:"apn"`
|
APN string `json:"apn,omitempty"`
|
||||||
CarrierFw string `json:"carrierFw"`
|
CarrierFw string `json:"carrierFw,omitempty"`
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
FWVersion string `json:"fwVersion"`
|
FWVersion string `json:"fwVersion,omitempty"`
|
||||||
ICCID string `json:"iccid"`
|
ICCID string `json:"iccid,omitempty"`
|
||||||
IMEI string `json:"imei"`
|
IMEI string `json:"imei,omitempty"`
|
||||||
MEPStatus string `json:"mepStatus"`
|
MEPStatus string `json:"mepStatus,omitempty"`
|
||||||
MSISDN string `json:"msisdn"`
|
MSISDN string `json:"msisdn,omitempty"`
|
||||||
NetworkMode string `json:"networkMode"`
|
NetworkMode string `json:"networkMode,omitempty"`
|
||||||
NetworkName string `json:"networkName"`
|
NetworkName string `json:"networkName,omitempty"`
|
||||||
RFBand int `json:"rfBand"`
|
RFBand int `json:"rfBand,omitempty"`
|
||||||
Roaming bool `json:"roaming"`
|
Roaming bool `json:"roaming"`
|
||||||
RoamingAllowed bool `json:"roamingAllowed"`
|
RoamingAllowed bool `json:"roamingAllowed"`
|
||||||
SignalStrength string `json:"signalStrength"`
|
SignalStrength string `json:"signalStrength,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type,omitempty"`
|
||||||
WWANIPAddr string `json:"wwanIpAddr"`
|
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.
|
// Owner is the owner of a Device data.
|
||||||
@ -97,8 +119,118 @@ type StreamUrl struct {
|
|||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payload represents the message that will be sent to the arlo servers via the Notify API.
|
type BaseDetectionProperties struct {
|
||||||
type Payload 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"`
|
Action string `json:"action,omitempty"`
|
||||||
Resource string `json:"resource,omitempty"`
|
Resource string `json:"resource,omitempty"`
|
||||||
PublishResponse bool `json:"publishResponse"`
|
PublishResponse bool `json:"publishResponse"`
|
||||||
@ -108,14 +240,16 @@ type Payload struct {
|
|||||||
To string `json:"to"`
|
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 {
|
type Data struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// map[data:map[message:The device does not exist. reason:No such device. error:2217] success:false]
|
// Status is the message fragment returned from most http calls to the Arlo API.
|
||||||
type Error struct {
|
type Status struct {
|
||||||
Data `json:"Data,omitempty"`
|
Data `json:"Data,omitempty"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
19
util.go
19
util.go
@ -15,19 +15,28 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"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 {
|
if err != nil {
|
||||||
return errors.WithMessage(err, msg)
|
return errors.WithMessage(err, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequest(resp *request.Response, err error, msg string) error {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var status Error
|
if err := checkHttpRequest(resp, err, msg); err != nil {
|
||||||
if err := resp.Decode(&status); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
var status Status
|
||||||
return errors.WithMessage(errors.New(fmt.Sprintf("http request failed: %s (%d)", resp.Status, resp.StatusCode)), msg)
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Success == false {
|
if status.Success == false {
|
||||||
|
Loading…
Reference in New Issue
Block a user