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-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
eventStream * EventStream
}
// Basestations is an array of Basestation objects.
type Basestations [ ] Basestation
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 )
}
b . eventStream . Subscriptions [ transId ] = make ( chan * EventStreamResponse )
defer close ( b . eventStream . Subscriptions [ transId ] )
if err := b . NotifyEventStream ( payload , msg ) ; err != nil {
return nil , err
}
timer := time . NewTimer ( eventStreamTimeout )
defer timer . Stop ( )
select {
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 , msg )
case <- b . eventStream . Close :
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
}
2018-09-19 19:12:06 +00:00
func ( b * Basestation ) Subscribe ( ) error {
2018-09-19 07:07:32 +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
connected := b . eventStream . Listen ( )
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 {
case b . eventStream . Connected = <- connected :
if b . eventStream . 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
}
case <- b . eventStream . Close :
return errors . New ( "failed to subscribe to the event stream" )
}
}
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-20 22:38:01 +00:00
time . Sleep ( 30 * time . Second )
if err := b . Ping ( ) ; err != nil {
b . Unsubscribe ( )
break
2018-09-19 21:35:05 +00:00
}
}
} ( )
2018-09-20 22:38:01 +00:00
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 {
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
}