2018-09-19 07:07:32 +00:00
package arlo
2018-09-17 04:44:41 +00:00
import (
"fmt"
2018-09-20 22:38:01 +00:00
"time"
2018-09-19 21:35:05 +00:00
"github.com/pkg/errors"
2018-09-17 04:44:41 +00:00
)
2018-09-20 22:38:01 +00:00
const eventStreamTimeout = 10 * time . Second
2018-09-22 14:15:22 +00:00
const pingTime = 30 * time . Second
2018-09-17 04:44:41 +00:00
// 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.
type Basestation struct {
Device
2018-09-22 14:15:22 +00:00
eventStream * eventStream
2018-09-17 04:44:41 +00:00
}
// Basestations is an array of Basestation objects.
type Basestations [ ] Basestation
2018-09-22 14:15:22 +00:00
// 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
}
// makeEventStreamRequest is a helper function sets up a response channel, sends a message to the event stream, and blocks waiting for the response.
2018-09-20 22:38:01 +00:00
func ( b * Basestation ) makeEventStreamRequest ( payload EventStreamPayload , msg string ) ( response * EventStreamResponse , err error ) {
transId := genTransId ( )
payload . TransId = transId
if err := b . IsConnected ( ) ; err != nil {
return nil , errors . WithMessage ( err , msg )
}
2018-09-22 14:15:22 +00:00
subscriber := make ( subscriber )
// Add the response channel to the event stream queue so the response can be written to it.
b . eventStream . subscribe ( transId , subscriber )
// Make sure we close and remove the response channel before returning.
defer b . eventStream . unsubscribe ( transId )
2018-09-20 22:38:01 +00:00
2018-09-22 14:15:22 +00:00
// Send the payload to the event stream.
2018-09-20 22:38:01 +00:00
if err := b . NotifyEventStream ( payload , msg ) ; err != nil {
return nil , err
}
timer := time . NewTimer ( eventStreamTimeout )
defer timer . Stop ( )
2018-09-22 14:15:22 +00:00
// Wait for the response to come back from the event stream on the response channel.
2018-09-20 22:38:01 +00:00
select {
2018-09-22 14:15:22 +00:00
// If we get a response, return it to the caller.
case response := <- subscriber :
2018-09-20 22:38:01 +00:00
return response , nil
case err = <- b . eventStream . Error :
return nil , errors . Wrap ( err , msg )
2018-09-22 14:15:22 +00:00
// If the event stream is closed, return an error about it.
case <- b . eventStream . Disconnected :
2018-09-20 22:38:01 +00:00
err = errors . New ( "event stream was closed before response was read" )
return nil , errors . WithMessage ( err , msg )
2018-09-22 14:15:22 +00:00
// If we timeout, return an error about it.
case <- timer . C :
err = fmt . Errorf ( "event stream response timed out after %.0f second" , eventStreamTimeout . Seconds ( ) )
return nil , errors . WithMessage ( err , msg )
2018-09-20 22:38:01 +00:00
}
}
func ( b * Basestation ) IsConnected ( ) error {
2018-09-22 14:15:22 +00:00
// If the event stream is closed, return an error about it.
select {
case <- b . eventStream . Disconnected :
2018-09-20 22:38:01 +00:00
return errors . New ( "basestation not connected to event stream" )
2018-09-22 14:15:22 +00:00
default :
return nil
2018-09-20 22:38:01 +00:00
}
}
2018-09-19 19:12:06 +00:00
func ( b * Basestation ) Subscribe ( ) error {
2018-09-22 14:15:22 +00:00
b . eventStream = newEventStream ( BaseUrl + fmt . Sprintf ( SubscribeUri , b . arlo . Account . Token ) , b . arlo . client . HttpClient )
2018-09-19 21:35:05 +00:00
2018-09-20 22:38:01 +00:00
forLoop :
2018-09-19 21:35:05 +00:00
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 {
2018-09-22 14:15:22 +00:00
case connected := <- b . eventStream . listen ( ) :
if connected {
2018-09-20 22:38:01 +00:00
break forLoop
2018-09-19 21:35:05 +00:00
} else {
2018-09-20 22:38:01 +00:00
return errors . New ( "failed to subscribe to the event stream" )
2018-09-19 21:35:05 +00:00
}
2018-09-22 14:15:22 +00:00
case <- b . eventStream . Disconnected :
err := errors . New ( "event stream was closed" )
return errors . WithMessage ( err , "failed to subscribe to the event stream" )
2018-09-19 21:35:05 +00:00
}
}
2018-09-18 18:02:21 +00:00
2018-09-20 22:38:01 +00:00
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.
2018-09-19 21:35:05 +00:00
go func ( ) {
for {
2018-09-22 14:15:22 +00:00
time . Sleep ( pingTime )
2018-09-20 22:38:01 +00:00
if err := b . Ping ( ) ; err != nil {
2018-09-22 14:15:22 +00:00
b . Disconnect ( )
2018-09-20 22:38:01 +00:00
break
2018-09-19 21:35:05 +00:00
}
}
} ( )
2018-09-20 22:38:01 +00:00
return nil
}
2018-09-22 14:15:22 +00:00
func ( b * Basestation ) Disconnect ( ) error {
// disconnect channel to stop event stream.
2018-09-20 22:38:01 +00:00
if b . eventStream != nil {
2018-09-22 14:15:22 +00:00
b . eventStream . disconnect ( )
2018-09-20 22:38:01 +00:00
}
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 {
2018-09-18 18:02:21 +00:00
Action : "set" ,
2018-09-19 19:12:06 +00:00
Resource : fmt . Sprintf ( "subscriptions/%s_%s" , b . UserId , TransIdPrefix ) ,
2018-09-18 18:02:21 +00:00
PublishResponse : false ,
2018-09-19 19:12:06 +00:00
Properties : map [ string ] [ 1 ] string { "devices" : { b . DeviceId } } ,
2018-09-18 18:02:21 +00:00
From : fmt . Sprintf ( "%s_%s" , b . UserId , TransIdPrefix ) ,
To : b . DeviceId ,
}
2018-09-19 21:35:05 +00:00
2018-09-20 22:38:01 +00:00
if _ , err := b . makeEventStreamRequest ( payload , "failed to ping the event stream" ) ; err != nil {
2018-09-19 21:35:05 +00:00
return err
}
return nil
2018-09-19 19:12:06 +00:00
}
2018-09-18 18:02:21 +00:00
2018-09-20 22:38:01 +00:00
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" )
2018-09-19 21:35:05 +00:00
}
2018-09-20 22:38:01 +00:00
defer resp . Body . Close ( )
2018-09-17 04:44:41 +00:00
2018-09-19 21:35:05 +00:00
return nil
}
2018-09-17 04:44:41 +00:00
2018-09-20 22:38:01 +00:00
func ( b * Basestation ) GetState ( ) ( response * EventStreamResponse , err error ) {
2018-09-17 04:44:41 +00:00
2018-09-20 22:38:01 +00:00
payload := EventStreamPayload {
2018-09-17 04:44:41 +00:00
Action : "get" ,
Resource : "basestation" ,
PublishResponse : false ,
From : fmt . Sprintf ( "%s_%s" , b . UserId , TransIdPrefix ) ,
To : b . DeviceId ,
}
2018-09-19 21:35:05 +00:00
return b . makeEventStreamRequest ( payload , "failed to get basestation state" )
2018-09-19 07:07:32 +00:00
}
2018-09-20 22:38:01 +00:00
func ( b * Basestation ) GetAssociatedCamerasState ( ) ( response * EventStreamResponse , err error ) {
payload := EventStreamPayload {
2018-09-19 07:07:32 +00:00
Action : "get" ,
Resource : "cameras" ,
PublishResponse : false ,
From : fmt . Sprintf ( "%s_%s" , b . UserId , TransIdPrefix ) ,
To : b . DeviceId ,
}
2018-09-17 04:44:41 +00:00
2018-09-19 21:35:05 +00:00
return b . makeEventStreamRequest ( payload , "failed to get associated cameras state" )
}
2018-09-20 22:38:01 +00:00
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 ,
}
2018-09-19 21:35:05 +00:00
2018-09-20 22:38:01 +00:00
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 ,
2018-09-19 21:35:05 +00:00
}
2018-09-20 22:38:01 +00:00
return b . makeEventStreamRequest ( payload , "failed to get schedule" )
}
2018-09-19 21:35:05 +00:00
2018-09-20 22:38:01 +00:00
// 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 ,
2018-09-17 04:44:41 +00:00
}
2018-09-20 22:38:01 +00:00
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 ,
2018-09-19 21:35:05 +00:00
}
2018-09-20 22:38:01 +00:00
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" )
2018-09-17 04:44:41 +00:00
}