Implementing EventStream support (still a WIP).
This commit is contained in:
parent
75454f08b3
commit
ceb2bff6c8
6
.gitignore
vendored
6
.gitignore
vendored
@ -12,3 +12,9 @@
|
|||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
# Project-local files
|
||||||
|
*.idea
|
||||||
|
|
||||||
|
# Go vendor libraries
|
||||||
|
vendor/**/
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@ -1,21 +1,21 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/mitchellh/mapstructure"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
version = "v0.8.0"
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/r3labs/sse"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "ab73c814bbdece537f16e92302cd99d1618d0e0d"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "f9932cc0e893e6f71176657aa0d2f567ec244ca71e930f3d95235e1d8b349a1c"
|
inputs-digest = "559855ebed7f1c0bf0bea2b6f750822d2eb67595d23a2f9006a8302a22b74e47"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
39
account.go
39
account.go
@ -1,6 +1,15 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jeffreydwalter/arlo-golang/internal/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +40,18 @@ type Friend struct {
|
|||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenTransId() string {
|
||||||
|
|
||||||
|
source := rand.NewSource(time.Now().UnixNano())
|
||||||
|
random := rand.New(source)
|
||||||
|
|
||||||
|
e := random.Float64() * math.Pow(2, 32)
|
||||||
|
|
||||||
|
ms := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s!%s!%s", TransIdPrefix, strings.ToLower(util.FloatToHex(e)), strconv.Itoa(int(ms)))
|
||||||
|
}
|
||||||
|
|
||||||
func Login(user string, pass string) (*Arlo, error) {
|
func Login(user string, pass string) (*Arlo, error) {
|
||||||
|
|
||||||
a := newArlo(user, pass)
|
a := newArlo(user, pass)
|
||||||
@ -52,7 +73,7 @@ func Login(user string, pass string) (*Arlo, error) {
|
|||||||
a.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
a.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
|
a.Account = loginResponse.Data
|
||||||
|
|
||||||
if deviceResponse, err := a.GetDevices(); err != nil {
|
if deviceResponse, err := a.GetDevices(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -61,12 +82,18 @@ func Login(user string, pass string) (*Arlo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the devices as their respective types.
|
|
||||||
a.Basestations = deviceResponse.Data.Basestations()
|
|
||||||
a.Cameras = deviceResponse.Data.Cameras()
|
|
||||||
|
|
||||||
// Set the XCloudId header for future requests. You can override this on a per-request basis if needed.
|
// Set the XCloudId header for future requests. You can override this on a per-request basis if needed.
|
||||||
a.client.BaseHttpHeader.Add("xCloudId", deviceResponse.Data[0].XCloudId)
|
a.client.BaseHttpHeader.Add("xCloudId", deviceResponse.Data[0].XCloudId)
|
||||||
|
|
||||||
|
// Cache the devices as their respective types.
|
||||||
|
a.Cameras = deviceResponse.Data.GetCameras()
|
||||||
|
a.Basestations = deviceResponse.Data.GetBasestations()
|
||||||
|
// Connect each basestation to the EventStream.
|
||||||
|
for i := range a.Basestations {
|
||||||
|
a.Basestations[i].connect(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("HERE: %v", util.PrettyPrint(a.Basestations))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("failed to login")
|
return nil, errors.New("failed to login")
|
||||||
|
40
arlo.go
40
arlo.go
@ -1,16 +1,16 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jeffreydwalter/arlo/internal/request"
|
"github.com/jeffreydwalter/arlo-golang/internal/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Arlo struct {
|
type Arlo struct {
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
client *request.Client
|
client *request.Client
|
||||||
Account *Account
|
Account Account
|
||||||
Basestations *Basestations
|
Basestations Basestations
|
||||||
Cameras *Cameras
|
Cameras Cameras
|
||||||
}
|
}
|
||||||
|
|
||||||
func newArlo(user string, pass string) *Arlo {
|
func newArlo(user string, pass string) *Arlo {
|
||||||
@ -24,33 +24,3 @@ func newArlo(user string, pass string) *Arlo {
|
|||||||
|
|
||||||
return arlo
|
return arlo
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
##
|
|
||||||
# This function causes the camera to record a snapshot.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) TakeSnapshot(camera):
|
|
||||||
stream_url = self.StartStream(camera)
|
|
||||||
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/takeSnapshot', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
return stream_url;
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function causes the camera to start recording.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) StartRecording(camera):
|
|
||||||
stream_url = self.StartStream(camera)
|
|
||||||
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/startRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
return stream_url
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function causes the camera to stop recording.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) StopRecording(camera):
|
|
||||||
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/stopRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
*/
|
|
||||||
|
@ -1 +1 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
108
basestation.go
Normal file
108
basestation.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package arlo_golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jeffreydwalter/arlo-golang/internal/util"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseStationMetadata struct {
|
||||||
|
InterfaceVersion int `json:"interfaceVersion"`
|
||||||
|
ApiVersion int `json:"apiVersion"`
|
||||||
|
State string `json:"state"`
|
||||||
|
SwVersion string `json:"swVersion"`
|
||||||
|
HwVersion string `json:"hwVersion"`
|
||||||
|
ModelId string `json:"modelId"`
|
||||||
|
Capabilities []string `json:"capabilities"`
|
||||||
|
McsEnabled bool `json:"mcsEnabled"`
|
||||||
|
AutoUpdateEnabled bool `json:"autoUpdateEnabled"`
|
||||||
|
TimeZone string `json:"timeZone"`
|
||||||
|
OlsonTimeZone string `json:"olsonTimeZone"`
|
||||||
|
UploadBandwidthSaturated bool `json:"uploadBandwidthSaturated"`
|
||||||
|
AntiFlicker map[string]int `json:"antiFlicker"`
|
||||||
|
LowBatteryAlert map[string]bool `json:"lowBatteryAlert"`
|
||||||
|
LowSignalAlert map[string]bool `json:"lowSignalAlert"`
|
||||||
|
Claimed bool `json:"claimed"`
|
||||||
|
TimeSyncState string `json:"timeSyncState"`
|
||||||
|
Connectivity []struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Connected bool `json:"connected"`
|
||||||
|
} `json:"connectivity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
func (b *Basestation) connect(a *Arlo) {
|
||||||
|
b.eventStream = NewEventStream(BaseUrl+fmt.Sprintf(SubscribeUri, a.Account.Token), util.HeaderToMap(*a.client.BaseHttpHeader))
|
||||||
|
b.eventStream.Listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is an example of the json you would pass in the body to UpdateFriends():
|
||||||
|
{
|
||||||
|
"firstName":"Some",
|
||||||
|
"lastName":"Body",
|
||||||
|
"devices":{
|
||||||
|
"XXXXXXXXXXXXX":"Camera 1",
|
||||||
|
"XXXXXXXXXXXXX":"Camera 2 ",
|
||||||
|
"XXXXXXXXXXXXX":"Camera 3"
|
||||||
|
},
|
||||||
|
"lastModified":1463977440911,
|
||||||
|
"adminUser":true,
|
||||||
|
"email":"user@example.com",
|
||||||
|
"id":"XXX-XXXXXXX"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func (a *Arlo) GetBasestationState(b Basestation) (*NotifyResponse, error) {
|
||||||
|
|
||||||
|
transId := GenTransId()
|
||||||
|
|
||||||
|
body := NotifyPayload{
|
||||||
|
Action: "get",
|
||||||
|
Resource: "basestation",
|
||||||
|
PublishResponse: false,
|
||||||
|
Properties: map[string]string{},
|
||||||
|
TransId: transId,
|
||||||
|
From: fmt.Sprintf("%s_%s", b.UserId, TransIdPrefix),
|
||||||
|
To: b.DeviceId,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.eventStream.Subscriptions[transId] = new(Subscriber)
|
||||||
|
|
||||||
|
for b.eventStream.Connected == false {
|
||||||
|
fmt.Println("Not connected yet.")
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
fmt.Println("Connected now.")
|
||||||
|
|
||||||
|
resp, err := a.client.Post(fmt.Sprintf(NotifyUri, b.DeviceId), body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to start stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
ep := &NotifyResponse{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(ep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to decode body")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Println("Subscribing to the eventstream.")
|
||||||
|
select {
|
||||||
|
case notifyResponse := <-*b.eventStream.Subscriptions[transId]:
|
||||||
|
fmt.Println("Recieved a response from the subscription.")
|
||||||
|
return ¬ifyResponse, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
camera.go
Normal file
8
camera.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package arlo_golang
|
||||||
|
|
||||||
|
// A Camera is a Device of type "camera".
|
||||||
|
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
|
||||||
|
type Camera Device
|
||||||
|
|
||||||
|
// Cameras is an array of Camera objects.
|
||||||
|
type Cameras []Camera
|
8
const.go
8
const.go
@ -1,6 +1,8 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
TransIdPrefix = "web"
|
||||||
|
|
||||||
BaseUrl = "https://arlo.netgear.com/hmsweb"
|
BaseUrl = "https://arlo.netgear.com/hmsweb"
|
||||||
LoginUri = "/login/v2"
|
LoginUri = "/login/v2"
|
||||||
LogoutUri = "/logout"
|
LogoutUri = "/logout"
|
||||||
@ -26,4 +28,8 @@ const (
|
|||||||
DeviceStartRecordUri = "/users/devices/startRecord"
|
DeviceStartRecordUri = "/users/devices/startRecord"
|
||||||
DeviceStopRecordUri = "/users/devices/stopRecord"
|
DeviceStopRecordUri = "/users/devices/stopRecord"
|
||||||
DeviceStartStreamUri = "/users/devices/startStream"
|
DeviceStartStreamUri = "/users/devices/startStream"
|
||||||
|
|
||||||
|
DeviceTypeBasestation = "basestation"
|
||||||
|
DeviceTypeCamera = "camera"
|
||||||
|
DeviceTypeArloQ = "arloq"
|
||||||
)
|
)
|
||||||
|
158
devices.go
158
devices.go
@ -1,6 +1,9 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,25 +28,12 @@ type Device struct {
|
|||||||
UserRole string `json:"userRole"`
|
UserRole string `json:"userRole"`
|
||||||
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
||||||
DeviceId string `json:"deviceId"`
|
DeviceId string `json:"deviceId"`
|
||||||
|
Metadata interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Devices is an array of Device objects.
|
// Devices is an array of Device objects.
|
||||||
type Devices []Device
|
type Devices []Device
|
||||||
|
|
||||||
// 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 Device
|
|
||||||
|
|
||||||
// Basestations is an array of Basestation objects.
|
|
||||||
type Basestations []Basestation
|
|
||||||
|
|
||||||
// A Camera is a Device of type "camera".
|
|
||||||
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
|
|
||||||
type Camera Device
|
|
||||||
|
|
||||||
// Cameras is an array of Camera objects.
|
|
||||||
type Cameras []Camera
|
|
||||||
|
|
||||||
// A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order.
|
// A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order.
|
||||||
// Device order is mainly used by the UI to determine which order to show the devices.
|
// Device order is mainly used by the UI to determine which order to show the devices.
|
||||||
/*
|
/*
|
||||||
@ -69,32 +59,51 @@ func (ds *Devices) Find(deviceId string) *Device {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basestations returns a Basestations object containing all devices that are NOT type "camera".
|
func (ds *Devices) FindCameras(basestationId string) *Cameras {
|
||||||
// I did this because some device types, like arloq, don't have a basestation.
|
cs := new(Cameras)
|
||||||
// So, when interacting with them you must treat them like a basestation and a camera.
|
|
||||||
// Cameras also includes decvices of this type, so you can get the same data there or cast.
|
|
||||||
func (ds *Devices) Basestations() *Basestations {
|
|
||||||
var basestations Basestations
|
|
||||||
for _, d := range *ds {
|
for _, d := range *ds {
|
||||||
if d.DeviceType != "camera" {
|
if d.ParentId == basestationId {
|
||||||
basestations = append(basestations, Basestation(d))
|
*cs = append(*cs, Camera(d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &basestations
|
|
||||||
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cameras returns a Cameras object containing all devices that are of type "camera".
|
func (d Device) IsBasestation() bool {
|
||||||
|
return d.DeviceType == DeviceTypeBasestation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Device) IsCamera() bool {
|
||||||
|
return d.DeviceType == DeviceTypeCamera
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBasestations returns a Basestations object containing all devices that are NOT type "camera".
|
||||||
|
// I did this because some device types, like arloq, don't have a basestation.
|
||||||
|
// So, when interacting with them you must treat them like a basestation and a camera.
|
||||||
|
// Cameras also includes devices of this type, so you can get the same data there or cast.
|
||||||
|
func (ds *Devices) GetBasestations() Basestations {
|
||||||
|
var basestations Basestations
|
||||||
|
for _, d := range *ds {
|
||||||
|
if !d.IsCamera() {
|
||||||
|
basestations = append(basestations, Basestation{Device: d})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return basestations
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCameras returns a Cameras object containing all devices that are of type "camera".
|
||||||
// I did this because some device types, like arloq, don't have a basestation.
|
// I did this because some device types, like arloq, don't have a basestation.
|
||||||
// So, when interacting with them you must treat them like a basestation and a camera.
|
// So, when interacting with them you must treat them like a basestation and a camera.
|
||||||
// Basestations also includes decvices of this type, so you can get the same data there or cast.
|
// Basestations also includes decvices of this type, so you can get the same data there or cast.
|
||||||
func (ds *Devices) Cameras() *Cameras {
|
func (ds *Devices) GetCameras() Cameras {
|
||||||
var cameras Cameras
|
var cameras Cameras
|
||||||
for _, d := range *ds {
|
for _, d := range *ds {
|
||||||
if d.DeviceType != "basestation" {
|
if !d.IsBasestation() {
|
||||||
cameras = append(cameras, Camera(d))
|
cameras = append(cameras, Camera(d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &cameras
|
return cameras
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDevices returns an array of all devices.
|
// GetDevices returns an array of all devices.
|
||||||
@ -159,30 +168,91 @@ func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) (*Status, error) {
|
|||||||
// 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":"rtmps://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 (a *Arlo) StartStream(c Camera) (*StartStreamResponse, error) {
|
func (a *Arlo) StartStream(c Camera) (*StreamResponse, error) {
|
||||||
|
|
||||||
var n Notification
|
body := map[string]interface{}{
|
||||||
n.To = c.ParentId
|
"to": c.ParentId,
|
||||||
n.From = c.UserId
|
"from": fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
|
||||||
n.Resource = "cameras/" + c.DeviceId
|
"resource": fmt.Sprintf("cameras/%s", c.DeviceId),
|
||||||
n.Action = "set"
|
"action": "set",
|
||||||
n.PublishResponse = true
|
"publishResponse": true,
|
||||||
n.TransId = ""
|
"transId": GenTransId(),
|
||||||
n.Properties.ActivityState = "startUserStream"
|
"properties": map[string]string{
|
||||||
n.Properties.CameraId = c.DeviceId
|
"activityState": "startUserStream",
|
||||||
|
"cameraId": c.DeviceId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// {"to":camera.get('parentId'),"from":self.user_id+"_web","resource":"cameras/"+camera.get('deviceId'),"action":"set","publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"startUserStream","cameraId":camera.get('deviceId')}}, headers={"xcloudId":camera.get('xCloudId')}
|
resp, err := a.client.Post(DeviceStartStreamUri, body, nil)
|
||||||
resp, err := a.client.Post(DeviceStartStreamUri, n, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed to start stream")
|
return nil, errors.WithMessage(err, "failed to start stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
var startStreamResponse StartStreamResponse
|
var streamResponse StreamResponse
|
||||||
if err := resp.Decode(&startStreamResponse); err != nil {
|
if err := resp.Decode(&streamResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &startStreamResponse, nil
|
streamResponse.Data.Url = strings.Replace(streamResponse.Data.Url, "rtsp://", "rtsps://", 1)
|
||||||
|
|
||||||
|
return &streamResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TakeSnapshot causes the camera to record a snapshot.
|
||||||
|
func (a *Arlo) TakeSnapshot(c Camera) (*StreamResponse, error) {
|
||||||
|
|
||||||
|
stream, err := a.StartStream(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to take snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
|
||||||
|
resp, err := a.client.Post(DeviceTakeSnapshotUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to take snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
streamResponse := StreamResponse{stream.Data, &status}
|
||||||
|
return &streamResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
|
||||||
|
// or something similar.
|
||||||
|
func (a *Arlo) StartRecording(c Camera) (*StreamResponse, error) {
|
||||||
|
|
||||||
|
stream, err := a.StartStream(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to start recording")
|
||||||
|
}
|
||||||
|
|
||||||
|
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
|
||||||
|
resp, err := a.client.Post(DeviceStartRecordUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to start recording")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
streamResponse := StreamResponse{stream.Data, &status}
|
||||||
|
return &streamResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
##
|
||||||
|
# This function causes the camera to stop recording.
|
||||||
|
#
|
||||||
|
# You can get the timezone from GetDevices().
|
||||||
|
##
|
||||||
|
func (a *Arlo) StopRecording(camera):
|
||||||
|
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/stopRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
||||||
|
*/
|
||||||
|
147
events_stream.go
Normal file
147
events_stream.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package arlo_golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/r3labs/sse"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FAILED_TO_PUBLISH = errors.New("Failed to publish")
|
||||||
|
|
||||||
|
var FAILED_TO_DECODE_JSON = errors.New("Failed to decode JSON")
|
||||||
|
|
||||||
|
var FAILED_TO_SUBSCRIBE = errors.New("Failed to subscribe to SSEClient")
|
||||||
|
|
||||||
|
type Subscriber chan NotifyResponse
|
||||||
|
|
||||||
|
type EventStream struct {
|
||||||
|
Registered bool
|
||||||
|
Connected bool
|
||||||
|
SSEClient *sse.Client
|
||||||
|
Events chan *sse.Event
|
||||||
|
Subscriptions map[string]*Subscriber
|
||||||
|
ErrorChan chan error
|
||||||
|
Responses map[string]NotifyResponse
|
||||||
|
Verbose bool
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventStream(url string, headers map[string]string) *EventStream {
|
||||||
|
|
||||||
|
SSEClient := sse.NewClient(url)
|
||||||
|
SSEClient.Headers = headers
|
||||||
|
|
||||||
|
return &EventStream{
|
||||||
|
SSEClient: SSEClient,
|
||||||
|
Events: make(chan *sse.Event),
|
||||||
|
Subscriptions: map[string]*Subscriber{},
|
||||||
|
ErrorChan: make(chan error, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventStream) Listen() {
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := e.SSEClient.SubscribeChan("", e.Events)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(FAILED_TO_SUBSCRIBE)
|
||||||
|
e.ErrorChan <- FAILED_TO_SUBSCRIBE
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for event := range e.Events {
|
||||||
|
fmt.Println("Got event message here.")
|
||||||
|
fmt.Printf("EVENT: %s\n", event.Event)
|
||||||
|
fmt.Printf("DATA: %s\n", event.Data)
|
||||||
|
|
||||||
|
if event.Data != nil {
|
||||||
|
notifyResponse := &NotifyResponse{}
|
||||||
|
b := bytes.NewBuffer(event.Data)
|
||||||
|
err := json.NewDecoder(b).Decode(notifyResponse)
|
||||||
|
if err != nil {
|
||||||
|
e.ErrorChan <- errors.WithMessage(err, "failed to decode JSON")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if notifyResponse.Status == "connected" {
|
||||||
|
e.Connected = true
|
||||||
|
fmt.Println("Connected.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for event := range e.Events {
|
||||||
|
fmt.Println("Got event message.")
|
||||||
|
fmt.Printf("EVENT: %X\n", event.Event)
|
||||||
|
fmt.Printf("DATA: %X\n", event.Data)
|
||||||
|
|
||||||
|
if event.Data != nil {
|
||||||
|
notifyResponse := &NotifyResponse{}
|
||||||
|
b := bytes.NewBuffer(event.Data)
|
||||||
|
err := json.NewDecoder(b).Decode(notifyResponse)
|
||||||
|
if err != nil {
|
||||||
|
e.ErrorChan <- FAILED_TO_DECODE_JSON
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if notifyResponse.Status == "connected" {
|
||||||
|
fmt.Println("Connected.")
|
||||||
|
e.Connected = true
|
||||||
|
} else if notifyResponse.Status == "disconnected" {
|
||||||
|
fmt.Println("Disconnected.")
|
||||||
|
e.Connected = false
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Message for transId: %s", notifyResponse.TransId)
|
||||||
|
if subscriber, ok := e.Subscriptions[notifyResponse.TransId]; ok {
|
||||||
|
e.Lock()
|
||||||
|
*subscriber <- *notifyResponse
|
||||||
|
close(*subscriber)
|
||||||
|
delete(e.Subscriptions, notifyResponse.TransId)
|
||||||
|
e.Unlock()
|
||||||
|
} else {
|
||||||
|
// Throw away the message.
|
||||||
|
fmt.Println("Throwing away message.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
/*
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
fmt.Println("go func to recieve a subscription.")
|
||||||
|
for {
|
||||||
|
fmt.Println("go func for loop to recieve a subscription.")
|
||||||
|
select {
|
||||||
|
case s := <-e.Subscriptions:
|
||||||
|
if resp, ok := e.Responses[s.transId]; ok {
|
||||||
|
fmt.Println("Recieved a subscription, sending response.")
|
||||||
|
s.ResponseChan <- resp
|
||||||
|
e.Lock()
|
||||||
|
delete(e.Responses, s.transId)
|
||||||
|
e.Unlock()
|
||||||
|
} else {
|
||||||
|
fmt.Println("Recieved a subscription error, sending error response.")
|
||||||
|
e.ErrorChan <- FAILED_TO_PUBLISH
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EventStream) verbose(params ...interface{}) {
|
||||||
|
if e.Verbose {
|
||||||
|
log.Println(params...)
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -14,8 +13,8 @@ import (
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
BaseURL *url.URL
|
BaseURL *url.URL
|
||||||
BaseHttpHeader http.Header
|
BaseHttpHeader *http.Header
|
||||||
httpClient http.Client
|
HttpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(baseurl string) (*Client, error) {
|
func NewClient(baseurl string) (*Client, error) {
|
||||||
@ -40,8 +39,8 @@ func NewClient(baseurl string) (*Client, error) {
|
|||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
BaseURL: u,
|
BaseURL: u,
|
||||||
BaseHttpHeader: header,
|
BaseHttpHeader: &header,
|
||||||
httpClient: http.Client{Jar: jar},
|
HttpClient: &http.Client{Jar: jar},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,14 +78,14 @@ 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("JSON: %v", buf)
|
// log.Printf("JSON: %v", 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create request object")
|
return nil, errors.Wrap(err, "failed to create request object")
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range c.BaseHttpHeader {
|
for k, v := range *c.BaseHttpHeader {
|
||||||
for _, h := range v {
|
for _, h := range v {
|
||||||
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
||||||
req.Header.Add(k, h)
|
req.Header.Add(k, h)
|
||||||
@ -114,15 +113,15 @@ func (c *Client) newResponse(resp *http.Response) (*Response, error) {
|
|||||||
|
|
||||||
func (c *Client) do(req *Request) (*Response, error) {
|
func (c *Client) do(req *Request) (*Response, error) {
|
||||||
|
|
||||||
//fmt.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.httpClient.Jar.Cookies(req.URL))
|
//fmt.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.HttpClient.Jar.Cookies(req.URL))
|
||||||
//fmt.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
|
//fmt.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(&req.Request)
|
resp, err := c.HttpClient.Do(&req.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to execute http request")
|
return nil, errors.Wrap(err, "failed to execute http request")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
return nil, errors.New("http request failed with status: " + resp.Status)
|
return nil, errors.New("http request failed with status: " + resp.Status)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package util
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrettyPrint(data interface{}) string {
|
func PrettyPrint(data interface{}) string {
|
||||||
@ -13,13 +15,64 @@ func PrettyPrint(data interface{}) string {
|
|||||||
return fmt.Sprint(string(j))
|
return fmt.Sprint(string(j))
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransIdPrefix = "web"
|
func FloatToHex(x float64) string {
|
||||||
|
var result []byte
|
||||||
|
quotient := int(x)
|
||||||
|
fraction := x - float64(quotient)
|
||||||
|
|
||||||
func GenTransId(transType string) {
|
for quotient > 0 {
|
||||||
/*
|
quotient = int(x / 16)
|
||||||
func divmod(numerator, denominator int64) (quotient, remainder int64) {
|
remainder := int(x - (float64(quotient) * 16))
|
||||||
quotient = numerator / denominator // integer division, decimals are truncated
|
|
||||||
remainder = numerator % denominator
|
if remainder > 9 {
|
||||||
return
|
result = append([]byte{byte(remainder + 55)}, result...)
|
||||||
}*/
|
} else {
|
||||||
|
for _, c := range strconv.Itoa(int(remainder)) {
|
||||||
|
result = append([]byte{byte(c)}, result...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = float64(quotient)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fraction == 0 {
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, '.')
|
||||||
|
|
||||||
|
for fraction > 0 {
|
||||||
|
fraction = fraction * 16
|
||||||
|
integer := int(fraction)
|
||||||
|
fraction = fraction - float64(integer)
|
||||||
|
|
||||||
|
if integer > 9 {
|
||||||
|
result = append(result, byte(integer+55))
|
||||||
|
} else {
|
||||||
|
for _, c := range strconv.Itoa(int(integer)) {
|
||||||
|
result = append(result, byte(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HeaderToArray(header http.Header) (res []string) {
|
||||||
|
for name, values := range header {
|
||||||
|
for _, value := range values {
|
||||||
|
res = append(res, fmt.Sprintf("%s: %s", name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func HeaderToMap(header http.Header) map[string]string {
|
||||||
|
h := make(map[string]string)
|
||||||
|
for name, values := range header {
|
||||||
|
for _, value := range values {
|
||||||
|
h[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
// UpdateResponse is an intermediate struct used when parsing data from the UpdateProfile() call.
|
// UpdateResponse is an intermediate struct used when parsing data from the UpdateProfile() call.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
@ -28,7 +28,12 @@ type LibraryResponse struct {
|
|||||||
*Status
|
*Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type StartStreamResponse struct {
|
type StreamResponse struct {
|
||||||
|
Data StreamUrl
|
||||||
|
*Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingResponse struct {
|
||||||
Data StreamUrl
|
Data StreamUrl
|
||||||
*Status
|
*Status
|
||||||
}
|
}
|
||||||
|
80
types.go
80
types.go
@ -1,4 +1,4 @@
|
|||||||
package arlo
|
package arlo_golang
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Credentials is the login credential data.
|
// Credentials is the login credential data.
|
||||||
@ -45,45 +45,53 @@ type Favorite struct {
|
|||||||
Favorite uint8 `json:"Favorite"`
|
Favorite uint8 `json:"Favorite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
type Device struct {
|
|
||||||
DeviceType string `json:"deviceType"`
|
|
||||||
XCloudId string `json:"xCloudId"`
|
|
||||||
DisplayOrder uint8 `json:"displayOrder"`
|
|
||||||
State string `json:"state"`
|
|
||||||
ModelId string `json:"modelId"`
|
|
||||||
InterfaceVersion string `json:"interfaceVersion"`
|
|
||||||
ParentId string `json:"parentId"`
|
|
||||||
UserId string `json:"userId"`
|
|
||||||
DeviceName string `json:"deviceName"`
|
|
||||||
FirmwareVersion string `json:"firmwareVersion"`
|
|
||||||
MediaObjectCount uint8 `json:"mediaObjectCount"`
|
|
||||||
DateCreated float64 `json:"dateCreated"`
|
|
||||||
Owner Owner `json:"owner"`
|
|
||||||
Properties Properties `json:"properties"`
|
|
||||||
UniqueId string `json:"uniqueId"`
|
|
||||||
LastModified float64 `json:"lastModified"`
|
|
||||||
UserRole string `json:"userRole"`
|
|
||||||
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
|
||||||
DeviceId string `json:"deviceId"`
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
type StreamUrl struct {
|
type StreamUrl struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationProperties struct {
|
// NotifyPayload represents the message that will be sent to the Arlo servers via the Notify API.
|
||||||
ActivityState string `json:"activityState"`
|
type NotifyPayload struct {
|
||||||
CameraId string `json:"cameraId"`
|
Action string `json:"action,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
PublishResponse bool `json:"publishResponse,omitempty"`
|
||||||
|
Properties interface{} `json:"properties,omitempty"`
|
||||||
|
TransId string `json:"transId"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type NotifyResponse struct {
|
||||||
To string `json:"to"`
|
Action string `json:"action,omitempty"`
|
||||||
From string `json:"from"`
|
Resource string `json:"resource,omitempty"`
|
||||||
Resource string `json:"resource"`
|
Properties interface{} `json:"properties,omitempty"`
|
||||||
Action string `json:"action"`
|
TransId string `json:"transId"`
|
||||||
PublishResponse bool `json:"publishResourcec"`
|
From string `json:"from"`
|
||||||
TransId string `json:"transId"`
|
To string `json:"to"`
|
||||||
Properties NotificationProperties `json:"properties"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{"status":"connected"}
|
||||||
|
|
||||||
|
{"resource":"subscriptions/336-4764296_web","transId":"web!f94fbae4.46e6e!1520148142862","action":"is","from":"48935B7SA9847","to":"336-4764296_web","properties":{"devices":["48935B7SA9847"],"url":"https://vzweb05-prod.vz.netgear.com/hmsweb/publish/48935B7SA9847/336-4764296/c16ec5b2-f914-4140-aa5d-880feda292a0"}}
|
||||||
|
|
||||||
|
{"resource":"cameras/48B45974D8E54","properties":{"batteryLevel":15},"transId":"48935B7SA9847!cfa2b5ed!1520148143870","from":"48935B7SA9847","action":"is"}
|
||||||
|
|
||||||
|
{"resource":"basestation","properties":{"interfaceVersion":3,"apiVersion":1,"state":"idle","swVersion":"1.9.8.0_16666","hwVersion":"VMB3010r2","modelId":"VMB3010","capabilities":["gateway"],"mcsEnabled":true,"autoUpdateEnabled":true,"timeZone":"CST6CDT,M3.2.0,M11.1.0","olsonTimeZone":"America/Chicago","uploadBandwidthSaturated":false,"antiFlicker":{"mode":0,"autoDefault":1},"lowBatteryAlert":{"enabled":true},"lowSignalAlert":{"enabled":false},"claimed":true,"timeSyncState":"synchronized","connectivity":[{"type":"ethernet","connected":true}]},"action":"is","transId":"web!ffe75798.f6dca!1520148144127","to":"336-4764296_web","from":"48935B7SA9847"}
|
||||||
|
|
||||||
|
|
||||||
|
{"resource":"basestation","properties":{"interfaceVersion":3,"apiVersion":1,"state":"idle","swVersion":"1.9.8.0_16666","hwVersion":"VMB3010r2","modelId":"VMB3010","capabilities":["gateway"],"mcsEnabled":true,"autoUpdateEnabled":true,"timeZone":"CST6CDT,M3.2.0,M11.1.0","olsonTimeZone":"America/Chicago","uploadBandwidthSaturated":false,"antiFlicker":{"mode":0,"autoDefault":1},"lowBatteryAlert":{"enabled":true},"lowSignalAlert":{"enabled":false},"claimed":true,"timeSyncState":"synchronized","connectivity":[{"type":"ethernet","connected":true}]},"action":"is","transId":"web!ffe75798.f6dca!1520148144127","to":"336-4764296_web","from":"48935B7SA9847"}
|
||||||
|
|
||||||
|
{"resource":"cameras","properties":[{"interfaceVersion":3,"serialNumber":"48B45974D8E54","batteryLevel":15,"signalStrength":4,"brightness":0,"mirror":true,"flip":true,"powerSaveMode":3,"capabilities":["H.264Streaming","JPEGSnapshot","SignalStrength","Privacy","Standby",{"Resolutions":[{"text":"1080p","x":1920,"y":1088},{"text":"720p","x":1280,"y":720},{"text":"480p","x":848,"y":480},{"text":"360p","x":640,"y":352},{"text":"240p","x":416,"y":240}]},{"TimedStreamDuration":{"min":5,"max":120,"default":10}},{"TriggerEndStreamDuration":{"min":5,"max":300,"default":300}},{"Actions":[{"recordVideo":[{"StopActions":["timeout","triggerEndDetected"]}]},"sendEmailAlert","pushNotification"]},{"Triggers":[{"type":"pirMotionActive","sensitivity":{"type":"integer","min":1,"max":100,"step":1,"default":80}}]}],"zoom":{"topleftx":0,"toplefty":0,"bottomrightx":1280,"bottomrighty":720},"mic":{"mute":false,"volume":100},"speaker":{"mute":false,"volume":100},"streamingMode":"eventBased","continuousStreamState":"inactive","motion":{"sensitivity":5,"zones":[]},"resolution":{"width":1280,"height":720},"idleLedEnable":true,"privacyActive":false,"standbyActive":false,"connectionState":"available","activityState":"idle","swVersion":"1.2.16720","hwVersion":"H7","modelId":"VMC3030","motionSetupModeEnabled":false,"motionSetupModeSensitivity":80,"motionDetected":false,"audioDetected":false,"hasStreamed":true,"olsonTimeZone":"America/Chicago","name":"","nightVisionMode":1},{"interfaceVersion":3,"serialNumber":"48B4597FD9B8E","batteryLevel":0,"signalStrength":4,"brightness":0,"mirror":false,"flip":false,"powerSaveMode":3,"capabilities":["H.264Streaming","JPEGSnapshot","SignalStrength","Privacy","Standby",{"Resolutions":[{"text":"1080p","x":1920,"y":1088},{"text":"720p","x":1280,"y":720},{"text":"480p","x":848,"y":480},{"text":"360p","x":640,"y":352},{"text":"240p","x":416,"y":240}]},{"TimedStreamDuration":{"min":5,"max":120,"default":10}},{"TriggerEndStreamDuration":{"min":5,"max":300,"default":300}},{"Actions":[{"recordVideo":[{"StopActions":["timeout","triggerEndDetected"]}]},"sendEmailAlert","pushNotification"]},{"Triggers":[{"type":"pirMotionActive","sensitivity":{"type":"integer","min":1,"max":100,"step":1,"default":80}}]}],"zoom":{"topleftx":0,"toplefty":0,"bottomrightx":1280,"bottomrighty":720},"mic":{"mute":false,"volume":100},"speaker":{"mute":false,"volume":100},"streamingMode":"eventBased","continuousStreamState":"inactive","motion":{"sensitivity":5,"zones":[]},"resolution":{"width":1280,"height":720},"idleLedEnable":true,"privacyActive":false,"standbyActive":false,"connectionState":"batteryCritical","activityState":"idle","swVersion":"1.2.16720","hwVersion":"H7","modelId":"VMC3030","motionSetupModeEnabled":false,"motionSetupModeSensitivity":80,"motionDetected":false,"audioDetected":false,"hasStreamed":true,"olsonTimeZone":"America/Chicago","name":"","nightVisionMode":1},{"interfaceVersion":3,"serialNumber":"48B4597VD8FF5","batteryLevel":0,"signalStrength":4,"brightness":2,"mirror":true,"flip":true,"powerSaveMode":3,"capabilities":["H.264Streaming","JPEGSnapshot","SignalStrength","Privacy","Standby",{"Resolutions":[{"text":"1080p","x":1920,"y":1088},{"text":"720p","x":1280,"y":720},{"text":"480p","x":848,"y":480},{"text":"360p","x":640,"y":352},{"text":"240p","x":416,"y":240}]},{"TimedStreamDuration":{"min":5,"max":120,"default":10}},{"TriggerEndStreamDuration":{"min":5,"max":300,"default":300}},{"Actions":[{"recordVideo":[{"StopActions":["timeout","triggerEndDetected"]}]},"sendEmailAlert","pushNotification"]},{"Triggers":[{"type":"pirMotionActive","sensitivity":{"type":"integer","min":1,"max":100,"step":1,"default":80}}]}],"zoom":{"topleftx":0,"toplefty":0,"bottomrightx":1280,"bottomrighty":720},"mic":{"mute":false,"volume":100},"speaker":{"mute":false,"volume":100},"streamingMode":"eventBased","continuousStreamState":"inactive","motion":{"sensitivity":5,"zones":[]},"resolution":{"width":1280,"height":720},"idleLedEnable":true,"privacyActive":false,"standbyActive":false,"connectionState":"batteryCritical","activityState":"idle","swVersion":"1.2.16720","hwVersion":"H7","modelId":"VMC3030","motionSetupModeEnabled":false,"motionSetupModeSensitivity":80,"motionDetected":false,"audioDetected":false,"hasStreamed":true,"olsonTimeZone":"America/Chicago","name":"","nightVisionMode":1}],"action":"is","transId":"web!2dc849b8.9ffc2!1520148144127","to":"336-4764296_web","from":"48935B7SA9847"}
|
||||||
|
|
||||||
|
{"resource":"modes","properties":{"active":"mode1","modes":[{"name":"","type":"disarmed","rules":[],"id":"mode0"},{"name":"","type":"armed","rules":["rule5","rule3","rule0"],"id":"mode1"},{"name":"*****_DEFAULT_MODE_ARMED_*****","rules":["rule1"],"id":"mode2"},{"name":"Test Mode","rules":["rule6"],"id":"mode3"}]},"action":"is","transId":"web!bbb0ff1f.3c85f!1520148144127","to":"336-4764296_web","from":"48935B7SA9847"}
|
||||||
|
|
||||||
|
|
||||||
|
{"resource":"rules","properties":{"rules":[{"name":"Push notification if Front Door detects motion","protected":true,"triggers":[{"deviceId":"48B45974D8E54","sensitivity":80,"type":"pirMotionActive"}],"actions":[{"type":"recordVideo","deviceId":"48B45974D8E54","stopCondition":{"type":"timeout","timeout":120}},{"type":"pushNotification"}],"id":"rule0"},{"name":"Record camera (Back Patio) on motion.","protected":false,"triggers":[{"type":"pirMotionActive","deviceId":"48B4597VD8FF5","sensitivity":80}],"actions":[{"deviceId":"48B4597VD8FF5","type":"recordVideo","stopCondition":{"type":"timeout","timeout":10}},{"type":"pushNotification"}],"id":"rule1"},{"name":"Push notification if Inside detects motion","protected":true,"triggers":[{"deviceId":"48B4597FD9B8E","sensitivity":90,"type":"pirMotionActive"}],"actions":[{"deviceId":"48B4597FD9B8E","type":"recordVideo","stopCondition":{"type":"timeout","timeout":120}}],"id":"rule3"},{"name":"Push notification if Back Patio detects motion","protected":true,"triggers":[{"deviceId":"48B4597VD8FF5","sensitivity":100,"type":"pirMotionActive"}],"actions":[{"deviceId":"48B4597VD8FF5","type":"recordVideo","stopCondition":{"type":"timeout","timeout":30}},{"type":"pushNotification"}],"id":"rule5"},{"name":"Push notification & Email alert if Back Patio detects motion","protected":false,"triggers":[{"type":"pirMotionActive","deviceId":"48B4597VD8FF5","sensitivity":80}],"actions":[{"type":"sendEmailAlert","recipients":["__OWNER_EMAIL__"]},{"type":"pushNotification"}],"id":"rule6"}]},"action":"is","transId":"web!bff59099.cbd6d!1520148144127","to":"336-4764296_web","from":"48935B7SA9847"}
|
||||||
|
|
||||||
|
|
||||||
|
{"resource":"subscriptions/336-4764296_web","transId":"web!ddda6350.ba92c!1520148172685","action":"is","from":"48935B7SA9847","to":"336-4764296_web","properties":{"devices":["48935B7SA9847"],"url":"https://vzweb05-prod.vz.netgear.com/hmsweb/publish/48935B7SA9847/336-4764296/37da66eb-023f-4965-bb8b-480687881b65"}}
|
||||||
|
|
||||||
|
|
||||||
|
{"resource":"subscriptions/336-4764296_web","transId":"web!d5739e5.077af!1520148202738","action":"is","from":"48935B7SA9847","to":"336-4764296_web","properties":{"devices":["48935B7SA9847"],"url":"https://vzweb05-prod.vz.netgear.com/hmsweb/publish/48935B7SA9847/336-4764296/7d9cc5d7-a908-4f22-aaaa-dbb70c8616d6"}}
|
||||||
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user