Skip to content

Commit a646d69

Browse files
bridge: implement thing description to support Web of Things (#463)
* bridge: implement thing description to support Web of Things --------- Co-authored-by: Daniel Adam <daniel.adam1922@protonmail.com>
1 parent d7aeb36 commit a646d69

28 files changed

+1396
-70
lines changed

bridge/device/cloud/manager_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func TestManagerDeviceBecomesUnauthorized(t *testing.T) {
102102
})
103103
deviceID := uuid.New().String()
104104
tickInterval := time.Second
105-
d1 := bridgeTest.NewBridgedDevice(t, s1, deviceID, true, false, device.WithCloudOptions(cloud.WithTickInterval(tickInterval)))
105+
d1 := bridgeTest.NewBridgedDevice(t, s1, deviceID, true, false, true, device.WithCloudOptions(cloud.WithTickInterval(tickInterval)))
106106
s1Shutdown := bridgeTest.RunBridgeService(s1)
107107
t.Cleanup(func() {
108108
_ = s1Shutdown()
@@ -167,7 +167,7 @@ func TestProvisioningOnDeviceRestart(t *testing.T) {
167167
_ = s1.Shutdown()
168168
})
169169
deviceID := uuid.New().String()
170-
d1 := bridgeTest.NewBridgedDevice(t, s1, deviceID, true, false)
170+
d1 := bridgeTest.NewBridgedDevice(t, s1, deviceID, true, false, true)
171171
s1Shutdown := bridgeTest.RunBridgeService(s1)
172172
t.Cleanup(func() {
173173
_ = s1Shutdown()

bridge/device/device.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ import (
2828
"github.com/google/uuid"
2929
"github.com/plgd-dev/device/v2/bridge/device/cloud"
3030
"github.com/plgd-dev/device/v2/bridge/device/credential"
31+
"github.com/plgd-dev/device/v2/bridge/device/thingDescription"
3132
"github.com/plgd-dev/device/v2/bridge/net"
3233
"github.com/plgd-dev/device/v2/bridge/resources"
3334
cloudResource "github.com/plgd-dev/device/v2/bridge/resources/cloud"
3435
resourcesDevice "github.com/plgd-dev/device/v2/bridge/resources/device"
3536
"github.com/plgd-dev/device/v2/bridge/resources/discovery"
3637
"github.com/plgd-dev/device/v2/bridge/resources/maintenance"
3738
credentialResource "github.com/plgd-dev/device/v2/bridge/resources/secure/credential"
39+
thingDescriptionResource "github.com/plgd-dev/device/v2/bridge/resources/thingDescription"
3840
"github.com/plgd-dev/device/v2/pkg/eventloop"
3941
pkgLog "github.com/plgd-dev/device/v2/pkg/log"
4042
"github.com/plgd-dev/device/v2/schema"
@@ -47,9 +49,10 @@ import (
4749
"github.com/plgd-dev/go-coap/v3/message/codes"
4850
"github.com/plgd-dev/go-coap/v3/message/pool"
4951
"github.com/plgd-dev/go-coap/v3/pkg/sync"
52+
wotTD "github.com/web-of-things-open-source/thingdescription-go/thingDescription"
5053
)
5154

52-
type Resource interface {
55+
type Resource = interface {
5356
Close()
5457
ETag() []byte
5558
GetHref() string
@@ -59,18 +62,20 @@ type Resource interface {
5962
GetPolicyBitMask() schema.BitMask
6063
SetObserveHandler(loop *eventloop.Loop, createSubscription resources.CreateSubscriptionFunc)
6164
UpdateETag()
65+
SupportsOperations() resources.SupportedOperation
6266
}
6367

6468
type Device struct {
65-
cfg Config
66-
resources *sync.Map[string, Resource]
67-
cloudManager *cloud.Manager
68-
credentialManager *credential.Manager
69-
onDeviceUpdated func(d *Device)
70-
loop *eventloop.Loop
71-
runLoop bool
72-
done chan struct{}
73-
stopped atomic.Bool
69+
cfg Config
70+
resources *sync.Map[string, Resource]
71+
cloudManager *cloud.Manager
72+
credentialManager *credential.Manager
73+
thingDescriptionManager *thingDescription.Manager
74+
onDeviceUpdated func(d *Device)
75+
loop *eventloop.Loop
76+
runLoop bool
77+
done chan struct{}
78+
stopped atomic.Bool
7479
}
7580

7681
func NewLogger(id uuid.UUID, level pkgLog.Level) pkgLog.Logger {
@@ -170,6 +175,15 @@ func New(cfg Config, opts ...Option) (*Device, error) {
170175
d.cloudManager = cm
171176
d.AddResources(cloudResource.New(cloudSchema.ResourceURI, d.cloudManager))
172177
}
178+
if o.getThingDescription != nil {
179+
td := thingDescription.New(d, o.loop)
180+
tdRes := thingDescriptionResource.New(thingDescriptionResource.ResourceURI, func(ctx context.Context, endpoints schema.Endpoints) *wotTD.ThingDescription {
181+
return o.getThingDescription(ctx, d, endpoints)
182+
}, td.RegisterSubscription)
183+
tdRes.SetObserveHandler(o.loop, tdRes.CreateSubscription)
184+
d.AddResources(tdRes)
185+
d.thingDescriptionManager = td
186+
}
173187

174188
d.AddResources(resourcesDevice.New(plgdDevice.ResourceURI, d, o.getAdditionalProperties))
175189
// oic/res is not discoverable
@@ -212,6 +226,11 @@ func (d *Device) GetCloudManager() *cloud.Manager {
212226
return d.cloudManager
213227
}
214228

229+
// GetThingDescriptionManager returns thing description manager of the device.
230+
func (d *Device) GetThingDescriptionManager() *thingDescription.Manager {
231+
return d.thingDescriptionManager
232+
}
233+
215234
func (d *Device) Range(f func(resourceHref string, resource Resource) bool) {
216235
d.resources.Range(f)
217236
}
@@ -308,6 +327,9 @@ func (d *Device) Close() {
308327
if d.credentialManager != nil {
309328
d.credentialManager.Close()
310329
}
330+
if d.thingDescriptionManager != nil {
331+
d.thingDescriptionManager.Close()
332+
}
311333
for _, resource := range d.resources.LoadAndDeleteAll() {
312334
resource.Close()
313335
}

bridge/device/options.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,21 @@
1919
package device
2020

2121
import (
22+
"context"
2223
"crypto/x509"
2324

2425
"github.com/plgd-dev/device/v2/bridge/device/cloud"
2526
"github.com/plgd-dev/device/v2/bridge/resources/device"
2627
"github.com/plgd-dev/device/v2/pkg/eventloop"
2728
"github.com/plgd-dev/device/v2/pkg/log"
29+
"github.com/plgd-dev/device/v2/schema"
30+
wotTD "github.com/web-of-things-open-source/thingdescription-go/thingDescription"
2831
)
2932

30-
type OnDeviceUpdated func(d *Device)
33+
type (
34+
OnDeviceUpdated func(d *Device)
35+
GetThingDescription func(ctx context.Context, d *Device, endpoints schema.Endpoints) *wotTD.ThingDescription
36+
)
3137

3238
type CAPoolGetter interface {
3339
IsValid() bool
@@ -43,6 +49,7 @@ type OptionsCfg struct {
4349
loop *eventloop.Loop
4450
runLoop bool
4551
cloudOptions []cloud.Option
52+
getThingDescription GetThingDescription
4653
}
4754

4855
type Option func(*OptionsCfg)
@@ -89,3 +96,9 @@ func WithCloudOptions(cloudOptions ...cloud.Option) Option {
8996
o.cloudOptions = cloudOptions
9097
}
9198
}
99+
100+
func WithThingDescription(getThingDescription GetThingDescription) Option {
101+
return func(o *OptionsCfg) {
102+
o.getThingDescription = getThingDescription
103+
}
104+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/****************************************************************************
2+
*
3+
* Copyright (c) 2024 plgd.dev s.r.o.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"),
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
14+
* either express or implied. See the License for the specific
15+
* language governing permissions and limitations under the License.
16+
*
17+
****************************************************************************/
18+
19+
package device
20+
21+
import (
22+
"context"
23+
"crypto/tls"
24+
"crypto/x509"
25+
"testing"
26+
"time"
27+
28+
"github.com/plgd-dev/device/v2/bridge/device/cloud"
29+
"github.com/plgd-dev/device/v2/bridge/device/credential"
30+
"github.com/plgd-dev/device/v2/pkg/eventloop"
31+
"github.com/plgd-dev/device/v2/pkg/log"
32+
"github.com/plgd-dev/device/v2/schema"
33+
"github.com/stretchr/testify/require"
34+
wotTD "github.com/web-of-things-open-source/thingdescription-go/thingDescription"
35+
)
36+
37+
func TestOptions(t *testing.T) {
38+
cfg := OptionsCfg{}
39+
40+
opts := []Option{}
41+
onDeviceUpdated := func(*Device) {
42+
// no-op
43+
}
44+
opts = append(opts, WithOnDeviceUpdated(onDeviceUpdated))
45+
46+
getAdditionalPropertiesForResponseFunc := func() map[string]interface{} {
47+
return nil
48+
}
49+
opts = append(opts, WithGetAdditionalPropertiesForResponse(getAdditionalPropertiesForResponseFunc))
50+
51+
getCertificates := func(string) []tls.Certificate {
52+
return nil
53+
}
54+
opts = append(opts, WithGetCertificates(getCertificates))
55+
56+
getCAPool := func() []*x509.Certificate {
57+
return []*x509.Certificate{{}}
58+
}
59+
caPool := credential.MakeCAPool(nil, getCAPool)
60+
opts = append(opts, WithCAPool(caPool))
61+
62+
logger := log.NewNilLogger()
63+
opts = append(opts, WithLogger(logger))
64+
65+
loop := eventloop.New()
66+
opts = append(opts, WithEventLoop(loop))
67+
68+
cloudOpt := cloud.WithTickInterval(time.Second)
69+
opts = append(opts, WithCloudOptions(cloudOpt))
70+
71+
getThingDescription := func(context.Context, *Device, schema.Endpoints) *wotTD.ThingDescription {
72+
return nil
73+
}
74+
opts = append(opts, WithThingDescription(getThingDescription))
75+
76+
for _, o := range opts {
77+
o(&cfg)
78+
}
79+
80+
require.NotNil(t, cfg.onDeviceUpdated)
81+
require.NotNil(t, cfg.getAdditionalProperties)
82+
require.NotNil(t, cfg.getCertificates)
83+
require.NotNil(t, cfg.caPool)
84+
require.Equal(t, logger, cfg.logger)
85+
require.Equal(t, loop, cfg.loop)
86+
require.Len(t, cfg.cloudOptions, 1)
87+
require.NotNil(t, cfg.getThingDescription)
88+
}

0 commit comments

Comments
 (0)