-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdevice_manager.go
300 lines (268 loc) · 10 KB
/
device_manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.
package ttnsdk
import (
"context"
"strings"
"time"
"github.com/TheThingsNetwork/api/handler"
"github.com/TheThingsNetwork/api/protocol/lorawan"
"github.com/TheThingsNetwork/go-utils/grpc/ttnctx"
"github.com/TheThingsNetwork/go-utils/log"
"github.com/TheThingsNetwork/go-utils/random"
"github.com/TheThingsNetwork/ttn/core/types"
)
// DeviceManager manages devices within an application
type DeviceManager interface {
// List devices in an application. Use the limit and offset for pagination. Requests that fetch many devices will be
// very slow, which is often not necessary. If you use this function too often, the response will be cached by the
// server, and you might receive outdated data.
List(limit, offset uint64) (DeviceList, error)
// Get details for a device
Get(devID string) (*Device, error)
// Create or Update a device.
Set(*Device) error
// Delete a device
Delete(devID string) error
}
// DeviceList is a slice of *SparseDevice.
type DeviceList []*SparseDevice
// AsDevices returns the DeviceList as a slice of *Device instead of *SparseDevice
func (d DeviceList) AsDevices() []*Device {
converted := make([]*Device, len(d))
for i, dev := range d {
converted[i] = dev.AsDevice()
}
return converted
}
func (c *client) ManageDevices() (DeviceManager, error) {
if err := c.connectHandler(); err != nil {
return nil, err
}
return &deviceManager{
logger: c.Logger,
client: handler.NewApplicationManagerClient(c.handler.conn),
devAddrClient: lorawan.NewDevAddrManagerClient(c.handler.conn),
getContext: c.getContext,
requestTimeout: c.RequestTimeout,
appID: c.appID,
}, nil
}
type deviceManager struct {
logger log.Interface
client handler.ApplicationManagerClient
devAddrClient lorawan.DevAddrManagerClient
getContext func(context.Context) context.Context
requestTimeout time.Duration
appID string
}
func (d *deviceManager) List(limit, offset uint64) (devices DeviceList, err error) {
ctx, cancel := context.WithTimeout(d.getContext(context.Background()), d.requestTimeout)
defer cancel()
ctx = ttnctx.OutgoingContextWithLimitAndOffset(ctx, limit, offset)
res, err := d.client.GetDevicesForApplication(ctx, &handler.ApplicationIdentifier{AppID: d.appID})
if err != nil {
return nil, err
}
for _, res := range res.Devices {
dev := new(SparseDevice)
dev.fromProto(res)
devices = append(devices, dev)
}
return devices, nil
}
func (d *deviceManager) Get(devID string) (*Device, error) {
ctx, cancel := context.WithTimeout(d.getContext(context.Background()), d.requestTimeout)
defer cancel()
res, err := d.client.GetDevice(ctx, &handler.DeviceIdentifier{AppID: d.appID, DevID: devID})
if err != nil {
return nil, err
}
dev := &Device{deviceManager: d}
dev.fromProto(res)
return dev, nil
}
func (d *deviceManager) Set(dev *Device) error {
if dev.AppID != d.appID {
dev.AppID = d.appID
}
req := new(handler.Device)
dev.toProto(req)
ctx, cancel := context.WithTimeout(d.getContext(context.Background()), d.requestTimeout)
defer cancel()
_, err := d.client.SetDevice(ctx, req) // TODO: fill dev from response and set deviceManager when the server actually returns the device
return err
}
func (d *deviceManager) Delete(devID string) error {
ctx, cancel := context.WithTimeout(d.getContext(context.Background()), d.requestTimeout)
defer cancel()
_, err := d.client.DeleteDevice(ctx, &handler.DeviceIdentifier{AppID: d.appID, DevID: devID})
return err
}
// SparseDevice contains most, but not all fields of the device. It's returned by List operations to save server resources
type SparseDevice struct {
AppID string `json:"app_id"`
DevID string `json:"dev_id"`
AppEUI types.AppEUI `json:"app_eui"`
DevEUI types.DevEUI `json:"dev_eui"`
Description string `json:"description,omitempty"`
DevAddr *types.DevAddr `json:"dev_addr,omitempty"`
NwkSKey *types.NwkSKey `json:"nwk_s_key,omitempty"`
AppSKey *types.AppSKey `json:"app_s_key,omitempty"`
AppKey *types.AppKey `json:"app_key,omitempty"`
Latitude float32 `json:"latitude,omitempty"`
Longitude float32 `json:"longitude,omitempty"`
Altitude int32 `json:"altitude,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
func (d *SparseDevice) fromProto(dev *handler.Device) {
d.AppID = dev.GetAppID()
d.DevID = dev.GetDevID()
d.Description = dev.Description
if lorawanDevice := dev.GetLoRaWANDevice(); lorawanDevice != nil {
d.AppEUI = lorawanDevice.GetAppEUI()
d.DevEUI = lorawanDevice.GetDevEUI()
d.DevAddr = lorawanDevice.DevAddr
d.NwkSKey = lorawanDevice.NwkSKey
d.AppSKey = lorawanDevice.AppSKey
d.AppKey = lorawanDevice.AppKey
}
d.Latitude = dev.Latitude
d.Longitude = dev.Longitude
d.Altitude = dev.Altitude
d.Attributes = dev.Attributes
}
func (d *SparseDevice) toProto(dev *handler.Device) {
dev.AppID = d.AppID
dev.DevID = d.DevID
dev.Description = d.Description
dev.Latitude = d.Latitude
dev.Longitude = d.Longitude
dev.Altitude = d.Altitude
dev.Attributes = d.Attributes
if dev.Device == nil {
dev.Device = &handler.Device_LoRaWANDevice{LoRaWANDevice: &lorawan.Device{}}
}
lorawanDevice := dev.GetLoRaWANDevice()
lorawanDevice.AppID = d.AppID
lorawanDevice.DevID = d.DevID
lorawanDevice.AppEUI = d.AppEUI
lorawanDevice.DevEUI = d.DevEUI
lorawanDevice.DevAddr = d.DevAddr
lorawanDevice.NwkSKey = d.NwkSKey
lorawanDevice.AppSKey = d.AppSKey
lorawanDevice.AppKey = d.AppKey
}
// AsDevice wraps the *SparseDevice and returns a *Device containing that sparse device
func (d *SparseDevice) AsDevice() *Device {
if d == nil {
return nil
}
return &Device{SparseDevice: *d}
}
// Device in an application
type Device struct {
deviceManager DeviceManager
SparseDevice
FCntUp uint32 `json:"f_cnt_up"`
FCntDown uint32 `json:"f_cnt_down"`
DisableFCntCheck bool `json:"disable_f_cnt_check"`
Uses32BitFCnt bool `json:"uses32_bit_f_cnt"`
ActivationConstraints string `json:"activation_constraints"`
LastSeen time.Time `json:"last_seen"`
}
func (d *Device) addActivationConstraint(c string) {
constraints := strings.Split(d.ActivationConstraints, ",")
for _, constraint := range constraints {
if constraint == c {
return
}
}
constraints = append(constraints, c)
d.ActivationConstraints = strings.Join(constraints, ",")
}
// IsNew indicates whether the device is new.
func (d *Device) IsNew() bool { return d.deviceManager == nil }
// SetManager sets the manager of the device. This function panics if this is not a new device.
func (d *Device) SetManager(manager DeviceManager) {
if d.deviceManager == manager {
return
}
if !d.IsNew() {
panic("ttn-sdk: you can not change the device manager")
}
d.deviceManager = manager
}
// Update the device. This function panics if this is a new device.
func (d *Device) Update() error {
if d.IsNew() {
panic("ttn-sdk: you can not update new devices")
}
return d.deviceManager.Set(d)
}
// Delete the device. This function panics if this is a new device.
func (d *Device) Delete() error {
if d.IsNew() {
panic("ttn-sdk: you can not update new devices")
}
return d.deviceManager.Delete(d.DevID)
}
// PersonalizeRandom personalizes a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey
// to randomly generated values. This function panics if this is a new device, so make sure you Get() the device first.
func (d *Device) PersonalizeRandom() error {
return d.PersonalizeFunc(func(_ types.DevAddr) (nwkSKey types.NwkSKey, appSKey types.AppSKey) {
random.FillBytes(nwkSKey[:])
random.FillBytes(appSKey[:])
return
})
}
// Personalize a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey to the given values.
// This function panics if this is a new device, so make sure you Get() the device first.
func (d *Device) Personalize(nwkSKey types.NwkSKey, appSKey types.AppSKey) error {
return d.PersonalizeFunc(func(_ types.DevAddr) (types.NwkSKey, types.AppSKey) {
return nwkSKey, appSKey
})
}
// PersonalizeFunc personalizes a device by requesting a DevAddr from the network, and setting the NwkSKey and AppSKey
// to the result of the personalizeFunc. This function panics if this is a new device, so make sure you Get() the device
// first.
func (d *Device) PersonalizeFunc(personalizeFunc func(types.DevAddr) (types.NwkSKey, types.AppSKey)) error {
if d.IsNew() {
panic("ttn-sdk: you can not update new devices. Use the Get() function to retrieve the device from the server first.")
}
manager, ok := d.deviceManager.(*deviceManager)
if !ok {
panic("ttn-sdk: you can only personalize devices on The Things Network")
}
d.addActivationConstraint("abp")
ctx, cancel := context.WithTimeout(manager.getContext(context.Background()), manager.requestTimeout)
defer cancel()
res, err := manager.devAddrClient.GetDevAddr(ctx, &lorawan.DevAddrRequest{Usage: strings.Split(d.ActivationConstraints, ",")})
if err != nil {
return err
}
d.DevAddr = &res.DevAddr
nwkSKey, appSKey := personalizeFunc(res.DevAddr)
d.NwkSKey, d.AppSKey = &nwkSKey, &appSKey
return d.Update()
}
func (d *Device) fromProto(dev *handler.Device) {
d.SparseDevice.fromProto(dev)
if lorawanDevice := dev.GetLoRaWANDevice(); lorawanDevice != nil {
d.FCntUp = lorawanDevice.FCntUp
d.FCntDown = lorawanDevice.FCntDown
d.DisableFCntCheck = lorawanDevice.DisableFCntCheck
d.Uses32BitFCnt = lorawanDevice.Uses32BitFCnt
d.ActivationConstraints = lorawanDevice.ActivationConstraints
d.LastSeen = time.Unix(0, lorawanDevice.LastSeen)
}
}
func (d *Device) toProto(dev *handler.Device) {
d.SparseDevice.toProto(dev)
lorawanDevice := dev.GetLoRaWANDevice()
lorawanDevice.FCntUp = d.FCntUp
lorawanDevice.FCntDown = d.FCntDown
lorawanDevice.DisableFCntCheck = d.DisableFCntCheck
lorawanDevice.Uses32BitFCnt = d.Uses32BitFCnt
lorawanDevice.ActivationConstraints = d.ActivationConstraints
}