Skip to content
This repository was archived by the owner on Dec 14, 2021. It is now read-only.

Commit 80a3da2

Browse files
committed
Add ttnsdk implementation
1 parent 2fa5ddf commit 80a3da2

13 files changed

+1815
-0
lines changed

application_manager.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright © 2017 The Things Network
2+
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3+
4+
package ttnsdk
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"fmt"
10+
"time"
11+
12+
"github.com/TheThingsNetwork/go-utils/log"
13+
"github.com/TheThingsNetwork/ttn/api/handler"
14+
)
15+
16+
// ApplicationManager manages an application.
17+
type ApplicationManager interface {
18+
// Get the payload format used in this application. If the payload format is "custom", you can get the custom JS
19+
// payload functions with the GetCustomPayloadFunctions() function.
20+
GetPayloadFormat() (string, error)
21+
22+
// Set the payload format to use in this application. If you want to use custom JS payload functions, use the
23+
// SetCustomPayloadFunctions() function instead. If you want to disable payload conversion, pass an empty string.
24+
SetPayloadFormat(format string) error
25+
26+
// Get the custom JS payload functions.
27+
GetCustomPayloadFunctions() (jsDecoder, jsConverter, jsValidator, jsEncoder string, err error)
28+
29+
// Set the custom JS payload functions.
30+
//
31+
// Example Decoder:
32+
//
33+
// // Decoder (Array<byte>, uint8) returns (Object)
34+
// function Decoder(bytes, port) {
35+
// var decoded = {};
36+
// return decoded;
37+
// }
38+
//
39+
// Example Converter:
40+
//
41+
// // Converter (Object, uint8) returns (Object)
42+
// function Converter(decoded, port) {
43+
// var converted = decoded;
44+
// return converted;
45+
// }
46+
//
47+
// Example Validator:
48+
// // Validator (Object, uint8) returns (Boolean)
49+
// function Validator(converted, port) {
50+
// return true;
51+
// }
52+
//
53+
// Example Encoder:
54+
//
55+
// // Validator (Object, uint8) returns (Array<byte>)
56+
// function Encoder(object, port) {
57+
// var bytes = [];
58+
// return bytes;
59+
// }
60+
SetCustomPayloadFunctions(jsDecoder, jsConverter, jsValidator, jsEncoder string) error
61+
}
62+
63+
func (c *client) ManageApplication() (ApplicationManager, error) {
64+
if err := c.connectHandler(); err != nil {
65+
return nil, err
66+
}
67+
return &applicationManager{
68+
logger: c.Logger,
69+
client: handler.NewApplicationManagerClient(c.handler.conn),
70+
getContext: c.getContext,
71+
requestTimeout: c.RequestTimeout,
72+
appID: c.appID,
73+
}, nil
74+
}
75+
76+
type applicationManager struct {
77+
logger log.Interface
78+
client handler.ApplicationManagerClient
79+
getContext func(context.Context) context.Context
80+
requestTimeout time.Duration
81+
82+
appID string
83+
}
84+
85+
func (a *applicationManager) getApplication() (*handler.Application, error) {
86+
ctx, cancel := context.WithTimeout(a.getContext(context.Background()), a.requestTimeout)
87+
defer cancel()
88+
return a.client.GetApplication(ctx, &handler.ApplicationIdentifier{AppId: a.appID})
89+
}
90+
91+
func (a *applicationManager) setApplication(app *handler.Application) error {
92+
ctx, cancel := context.WithTimeout(a.getContext(context.Background()), a.requestTimeout)
93+
defer cancel()
94+
_, err := a.client.SetApplication(ctx, app)
95+
return err
96+
}
97+
98+
func (a *applicationManager) GetPayloadFormat() (string, error) {
99+
app, err := a.getApplication()
100+
if err != nil {
101+
return "", err
102+
}
103+
return app.PayloadFormat, nil
104+
}
105+
106+
func (a *applicationManager) SetPayloadFormat(format string) error {
107+
app, err := a.getApplication()
108+
if err != nil {
109+
return err
110+
}
111+
app.PayloadFormat = format
112+
if app.PayloadFormat != "custom" {
113+
app.Decoder, app.Converter, app.Validator, app.Encoder = "", "", "", ""
114+
}
115+
return a.setApplication(app)
116+
}
117+
118+
func (a *applicationManager) GetCustomPayloadFunctions() (jsDecoder, jsConverter, jsValidator, jsEncoder string, err error) {
119+
app, err := a.getApplication()
120+
if err != nil {
121+
return jsDecoder, jsConverter, jsValidator, jsEncoder, err
122+
}
123+
if app.PayloadFormat != "custom" {
124+
return jsDecoder, jsConverter, jsValidator, jsEncoder, fmt.Errorf("ttn-sdk: application does not have custom payload functions, but uses \"%s\"", app.PayloadFormat)
125+
}
126+
return app.Decoder, app.Converter, app.Validator, app.Encoder, nil
127+
}
128+
129+
func (a *applicationManager) SetCustomPayloadFunctions(jsDecoder, jsConverter, jsValidator, jsEncoder string) error {
130+
app, err := a.getApplication()
131+
if err != nil {
132+
return err
133+
}
134+
app.PayloadFormat = "custom"
135+
app.Decoder, app.Converter, app.Validator, app.Encoder = jsDecoder, jsConverter, jsValidator, jsEncoder
136+
return a.setApplication(app)
137+
}
138+
139+
func (a *applicationManager) TestCustomUplinkPayloadFunctions(jsDecoder, jsConverter, jsValidator string, payload []byte, port uint8) (*handler.DryUplinkResult, error) {
140+
ctx, cancel := context.WithTimeout(a.getContext(context.Background()), a.requestTimeout)
141+
defer cancel()
142+
return a.client.DryUplink(ctx, &handler.DryUplinkMessage{
143+
Payload: payload,
144+
App: &handler.Application{
145+
AppId: a.appID,
146+
PayloadFormat: "custom",
147+
Decoder: jsDecoder,
148+
Converter: jsConverter,
149+
Validator: jsValidator,
150+
},
151+
Port: uint32(port),
152+
})
153+
}
154+
155+
func (a *applicationManager) TestCustomDownlinkPayloadFunctions(jsEncoder string, fields map[string]interface{}, port uint8) (*handler.DryDownlinkResult, error) {
156+
ctx, cancel := context.WithTimeout(a.getContext(context.Background()), a.requestTimeout)
157+
defer cancel()
158+
fieldsJSON, err := json.Marshal(fields)
159+
if err != nil {
160+
return nil, err
161+
}
162+
return a.client.DryDownlink(ctx, &handler.DryDownlinkMessage{
163+
Fields: string(fieldsJSON),
164+
App: &handler.Application{
165+
AppId: a.appID,
166+
PayloadFormat: "custom",
167+
Encoder: jsEncoder,
168+
},
169+
Port: uint32(port),
170+
})
171+
}

application_manager_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright © 2017 The Things Network
2+
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3+
4+
package ttnsdk
5+
6+
import (
7+
"context"
8+
"errors"
9+
"testing"
10+
"time"
11+
12+
ttnlog "github.com/TheThingsNetwork/go-utils/log"
13+
testlog "github.com/TheThingsNetwork/go-utils/log/test"
14+
"github.com/TheThingsNetwork/ttn/api/handler"
15+
. "github.com/smartystreets/assertions"
16+
)
17+
18+
func TestApplicationManager(t *testing.T) {
19+
a := New(t)
20+
21+
log := testlog.NewLogger()
22+
ttnlog.Set(log)
23+
defer log.Print(t)
24+
25+
mock := new(mockApplicationManagerClient)
26+
27+
manager := &applicationManager{
28+
logger: log,
29+
client: mock,
30+
getContext: func(ctx context.Context) context.Context { return ctx },
31+
requestTimeout: time.Second,
32+
appID: "test",
33+
}
34+
35+
someErr := errors.New("some error")
36+
37+
{
38+
mock.reset()
39+
mock.err = someErr
40+
_, err := manager.GetPayloadFormat()
41+
a.So(err, ShouldNotBeNil)
42+
43+
mock.reset()
44+
mock.application = &handler.Application{PayloadFormat: "custom"}
45+
pf, err := manager.GetPayloadFormat()
46+
a.So(err, ShouldBeNil)
47+
a.So(pf, ShouldEqual, "custom")
48+
49+
err = manager.SetPayloadFormat("other")
50+
a.So(err, ShouldBeNil)
51+
a.So(mock.application.PayloadFormat, ShouldEqual, "other")
52+
53+
mock.err = someErr
54+
err = manager.SetPayloadFormat("")
55+
a.So(err, ShouldNotBeNil)
56+
}
57+
58+
{
59+
mock.reset()
60+
mock.err = someErr
61+
_, _, _, _, err := manager.GetCustomPayloadFunctions()
62+
a.So(err, ShouldNotBeNil)
63+
64+
mock.reset()
65+
mock.application = &handler.Application{PayloadFormat: "other"}
66+
_, _, _, _, err = manager.GetCustomPayloadFunctions()
67+
a.So(err, ShouldNotBeNil)
68+
69+
mock.application = &handler.Application{PayloadFormat: "custom", Decoder: "decoder", Converter: "converter", Validator: "validator", Encoder: "encoder"}
70+
jsDecoder, jsConverter, jsValidator, jsEncoder, err := manager.GetCustomPayloadFunctions()
71+
a.So(err, ShouldBeNil)
72+
a.So(jsDecoder, ShouldEqual, "decoder")
73+
a.So(jsConverter, ShouldEqual, "converter")
74+
a.So(jsValidator, ShouldEqual, "validator")
75+
a.So(jsEncoder, ShouldEqual, "encoder")
76+
77+
err = manager.SetCustomPayloadFunctions("newdecoder", "newconverter", "newvalidator", "newencoder")
78+
a.So(err, ShouldBeNil)
79+
a.So(mock.application.PayloadFormat, ShouldEqual, "custom")
80+
a.So(mock.application.Decoder, ShouldEqual, "newdecoder")
81+
a.So(mock.application.Converter, ShouldEqual, "newconverter")
82+
a.So(mock.application.Validator, ShouldEqual, "newvalidator")
83+
a.So(mock.application.Encoder, ShouldEqual, "newencoder")
84+
85+
mock.err = someErr
86+
err = manager.SetCustomPayloadFunctions("", "", "", "")
87+
a.So(err, ShouldNotBeNil)
88+
}
89+
90+
// TODO: TestCustomUplinkPayloadFunctions(jsDecoder, jsConverter, jsValidator string, payload []byte, port uint8) (*handler.DryUplinkResult, error) {
91+
// TODO: TestCustomDownlinkPayloadFunctions(jsEncoder string, fields map[string]interface{}, port uint8) (*handler.DryDownlinkResult, error) {
92+
}

0 commit comments

Comments
 (0)