package arlo import ( "context" "fmt" "time" ) const eventStreamTimeout = 30 * time.Second const pingTime = 30 * time.Second // A Basestation is a Device that's not type "camera" (basestation, arloq, arloqs, etc.). // This type is here just for semantics. Some methods explicitly require a device of a certain type. type Basestation struct { arlo *Arlo Device } type GetModesResponse struct { Active string `json:"active"` Modes []*Mode `json:"modes"` } type Mode struct { Name string `json:"name"` Type string `json:"type,omitempty"` RulesIds []string `json:"rules"` ID string `json:"id"` } type GetRulesResponse struct { Rules []Rule `json:"rules"` } type Rule struct { Name string `json:"name"` Protected bool `json:"protected"` Triggers []struct { Type string `json:"type"` DeviceID string `json:"deviceId"` Sensitivity int `json:"sensitivity"` } `json:"triggers"` Actions []struct { Type string `json:"type"` Recipients []string `json:"recipients,omitempty"` DeviceID string `json:"deviceId,omitempty"` StopCondition struct { Type string `json:"type"` DeviceID string `json:"deviceId"` } `json:"stopCondition,omitempty"` } `json:"actions"` ID string `json:"id"` } type CalendarMode struct { Active bool `json:"active"` Schedule []struct { ModeID string `json:"modeId"` StartTime int `json:"startTime"` } `json:"schedule"` } func NewBaseStation(arlo *Arlo, device Device) *Basestation { return &Basestation{ arlo: arlo, Device: device, } } // makeEventStreamRequest is a helper function sets up a response channel, sends a message to the event stream, and blocks waiting for the response. func (b *Basestation) GetState(ctx context.Context) (*BaseStationState, error) { var state BaseStationState err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "get", "basestation", false, nil, &state) if err != nil { return nil, fmt.Errorf("getting basestation %s state: %v", b.DeviceName, err) } return &state, nil } func (b *Basestation) GetAllCameraState(ctx context.Context) ([]CameraState, error) { var states []CameraState err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "get", "cameras", false, nil, &states) if err != nil { return nil, fmt.Errorf("getting associated cameras state: %v", err) } return states, nil } func (b *Basestation) GetRules(ctx context.Context) ([]Rule, error) { var resp GetRulesResponse err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "get", "rules", false, nil, &resp) if err != nil { return nil, fmt.Errorf("getting rules: %v", err) } return resp.Rules, nil } func (b *Basestation) GetCalendarMode(ctx context.Context) (*CalendarMode, error) { var calendarMode CalendarMode err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "get", "schedule", false, nil, &calendarMode) if err != nil { return nil, fmt.Errorf("getting calendar mode: %v", err) } return &calendarMode, nil } // 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(ctx context.Context, active bool) error { resp := make(map[string]bool) err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "set", "schedule", true, struct { Active bool `json:"active"` }{ Active: active, }, &resp) if err != nil { return fmt.Errorf("setting calendar mode %t: %v", active, err) } activemode, ok := resp["active"] if !ok { return fmt.Errorf("active mode not present in response") } if activemode != active { return fmt.Errorf("active mode is not the mode requested: requested %t, set %t", active, activemode) } return nil } func (b *Basestation) GetModes(ctx context.Context) (*GetModesResponse, error) { var resp GetModesResponse err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "get", "modes", false, nil, &resp) if err != nil { return nil, fmt.Errorf("getting modes: %v", err) } return &resp, nil } func (b *Basestation) SetCustomMode(ctx context.Context, mode string) error { resp := make(map[string]string) err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "set", "modes", true, struct { Active string `json:"active"` }{ Active: mode, }, &resp) if err != nil { return fmt.Errorf("setting custom mode %s: %v", mode, err) } activemode, ok := resp["active"] if !ok { return fmt.Errorf("active mode not present in response") } if activemode != mode { return fmt.Errorf("active mode is not the mode requested: requested %s, set %s", mode, activemode) } return nil } func (b *Basestation) DeleteMode(ctx context.Context, mode string) error { err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "delete", fmt.Sprintf("modes/%s", mode), true, nil, nil) if err != nil { return fmt.Errorf("deleting mode %s: %v", mode, err) } return nil } func (b *Basestation) Arm(ctx context.Context) error { err := b.SetCustomMode(ctx, "mode1") if err != nil { return fmt.Errorf("arming (mode1): %v", err) } return nil } func (b *Basestation) Disarm(ctx context.Context) error { err := b.SetCustomMode(ctx, "mode0") if err != nil { return fmt.Errorf("disarming (mode0): %v", err) } return nil } type SetSirenResponse struct { SirenState string `json:"sirenState"` SirenTrigger string `json:"sirenTrigger"` Duration int `json:"duration"` Timestamp int64 `json:"timestamp"` } func (b *Basestation) SirenOn(ctx context.Context) error { var response SetSirenResponse err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "set", "siren", true, SirenProperties{ SirenState: "on", Duration: 300, Volume: 8, Pattern: "alarm", }, &response) if err != nil { return fmt.Errorf("making request: %v", err) } if response.SirenState != "on" { return fmt.Errorf("siren not on in response") } return nil } func (b *Basestation) SirenOff(ctx context.Context) error { var response SetSirenResponse err := b.arlo.makeRequest(ctx, b.DeviceId, b.XCloudId, "set", "siren", true, SirenProperties{ SirenState: "off", Duration: 300, Volume: 8, Pattern: "alarm", }, &response) if err != nil { return fmt.Errorf("making request: %v", err) } if response.SirenState != "off" { return fmt.Errorf("siren not off in response") } return nil }