Skip to content

Commit

Permalink
Merge pull request #76 from eclipse-thingweb/request-thing-description
Browse files Browse the repository at this point in the history
feat: implement requestThingDescription method
  • Loading branch information
JKRhb authored Dec 20, 2023
2 parents 6c415fe + e5b5745 commit 836ab1c
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 145 deletions.
63 changes: 29 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,54 +36,49 @@ You can then use the package in your project by adding

## Usage

Below you can find a very basic example for reading a status from a Thing (using the
`coap.me` test server).
To do so, a Thing Description JSON string is first parsed and turned into a
`ThingDescription` object, which is then passed to a WoT runtime created by a
`Servient` with CoAP support.
Below you can find a basic example for incrementing and reading the value of a
counter Thing, which is part of the
[Thingweb Online Things](https://www.thingweb.io/services).

In the example, we first create a WoT runtime using a `Servient` with CoAP
support.
With the runtime, we then retrieve a TD (using the `requestThingDescription()`
method) and consume it (using the `consume()` method), creating a
`ConsumedThing` object,
Afterward, the actual interactions with the counter are performed by calling the
`invokeAction()` and `readProperty()` methods on the `ConsumedThing`.

```dart
import 'package:dart_wot/dart_wot.dart';
Future<void> main(List<String> args) async {
final CoapClientFactory coapClientFactory = CoapClientFactory();
final servient = Servient(
protocolClients: [coapClientFactory]
clientFactories: [
CoapClientFactory(),
],
);
final wot = await servient.start();
final thingDescriptionJson = '''
{
"@context": "http://www.w3.org/ns/td",
"title": "Test Thing",
"base": "coap://coap.me",
"security": ["nosec_sc"],
"securityDefinitions": {
"nosec_sc": {
"scheme": "nosec"
}
},
"properties": {
"status": {
"forms": [
{
"href": "/hello"
}
]
}
}
}
''';
final thingDescription = ThingDescription(thingDescriptionJson);
final url = Uri.parse('coap://plugfest.thingweb.io/counter');
print('Requesting TD from $url ...');
final thingDescription = await wot.requestThingDescription(url);
final consumedThing = await wot.consume(thingDescription);
final status = await consumedThing.readProperty("status");
print(
'Successfully retrieved and consumed TD with title '
'"${thingDescription.title}"!',
);
print('Incrementing counter ...');
await consumedThing.invokeAction('increment');
final status = await consumedThing.readProperty('count');
final value = await status.value();
print(value);
print('New counter value: $value');
}
```

A more complex example can be found in the `example` directory.
More complex examples can be found in the `example` directory.

## Additional information

Expand Down
122 changes: 12 additions & 110 deletions example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,126 +8,28 @@

import 'package:dart_wot/dart_wot.dart';

final Map<String, BasicCredentials> basicCredentials = {
'urn:test': BasicCredentials('rw', 'readwrite'),
};

Future<BasicCredentials?> basicCredentialsCallback(
Uri uri,
Form? form, [
BasicCredentials? invalidCredentials,
]) async {
final id = form?.thingDescription.identifier;

return basicCredentials[id];
}

Future<void> main(List<String> args) async {
final coapClientFactory = CoapClientFactory();
final httpClientFactory =
HttpClientFactory(basicCredentialsCallback: basicCredentialsCallback);
final mqttClientFactory = MqttClientFactory();

final servient = Servient(
clientFactories: [
coapClientFactory,
httpClientFactory,
mqttClientFactory,
CoapClientFactory(),
],
);

final wot = await servient.start();

const thingDescriptionJson = '''
{
"@context": "http://www.w3.org/ns/td",
"title": "Test Thing",
"id": "urn:test",
"base": "coap://coap.me",
"security": ["auto_sc"],
"securityDefinitions": {
"auto_sc": {
"scheme": "auto"
}
},
"properties": {
"status": {
"forms": [
{
"href": "/hello"
}
]
},
"status2": {
"observable": true,
"forms": [
{
"href": "mqtt://test.mosquitto.org:1884",
"mqv:filter": "test",
"op": ["readproperty", "observeproperty"],
"contentType": "text/plain"
}
]
}
},
"actions": {
"toggle": {
"forms": [
{
"href": "mqtt://test.mosquitto.org:1884",
"mqv:topic": "test",
"mqv:retain": true
}
]
}
}
}
''';
final url = Uri.parse('coap://plugfest.thingweb.io/counter');
print('Requesting TD from $url ...');
final thingDescription = await wot.requestThingDescription(url);

final thingDescription = ThingDescription(thingDescriptionJson);
final consumedThing = await wot.consume(thingDescription);
final status = await consumedThing.readProperty('status');
final value = await status.value();
print(value);
final subscription = await consumedThing.observeProperty(
'status2',
(data) async {
final value = await data.value();
print(value);
},
print(
'Successfully retrieved and consumed TD with title '
'"${thingDescription.title}"!',
);

await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await subscription.stop();
print('Incrementing counter ...');
await consumedThing.invokeAction('increment');

final thingUri = Uri.parse(
'https://raw.githubusercontent.com/w3c/wot-testing'
'/b07fa6124bca7796e6ca752a3640fac264d3bcbc/events/2021.03.Online/TDs'
'/Oracle/oracle-Festo_Shared.td.jsonld',
);

final thingDiscovery = wot.discover(thingUri);

await for (final thingDescription in thingDiscovery) {
final consumedDiscoveredThing = await wot.consume(thingDescription);
print(
'The title of the fetched TD is '
'${consumedDiscoveredThing.thingDescription.title}.',
);
}

await consumedThing.invokeAction('toggle', 'Bye World!');
await consumedThing.readAndPrintProperty('status2');
print('Done!');
}

extension ReadAndPrintExtension on ConsumedThing {
Future<void> readAndPrintProperty(String propertyName) async {
final output = await readProperty(propertyName);
final value = await output.value();
print(value);
}
final status = await consumedThing.readProperty('count');
final value = await status.value();
print('New counter value: $value');
}
105 changes: 105 additions & 0 deletions example/mqtt_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause

// ignore_for_file: avoid_print

import 'package:dart_wot/dart_wot.dart';

const thingDescriptionJson = '''
{
"@context": "https://www.w3.org/2022/wot/td/v1.1",
"title": "Test Thing",
"id": "urn:test",
"base": "coap://coap.me",
"security": ["auto_sc"],
"securityDefinitions": {
"auto_sc": {
"scheme": "auto"
}
},
"properties": {
"status": {
"observable": true,
"forms": [
{
"href": "mqtt://test.mosquitto.org:1884",
"mqv:filter": "test",
"op": ["readproperty", "observeproperty"],
"contentType": "text/plain"
}
]
}
},
"actions": {
"toggle": {
"input": {
"type": "string"
},
"forms": [
{
"href": "mqtt://test.mosquitto.org:1884",
"mqv:topic": "test",
"mqv:retain": true
}
]
}
}
}
''';

final Map<String, BasicCredentials> basicCredentials = {
'urn:test': BasicCredentials('rw', 'readwrite'),
};

Future<BasicCredentials?> basicCredentialsCallback(
Uri uri,
Form? form, [
BasicCredentials? invalidCredentials,
]) async {
final id = form?.thingDescription.identifier;

return basicCredentials[id];
}

Future<void> main(List<String> args) async {
final servient = Servient(
clientFactories: [
MqttClientFactory(basicCredentialsCallback: basicCredentialsCallback),
],
);

final wot = await servient.start();

final thingDescription = ThingDescription(thingDescriptionJson);
final consumedThing = await wot.consume(thingDescription);
await consumedThing.readAndPrintProperty('status');

final subscription = await consumedThing.observeProperty(
'status',
(data) async {
final value = await data.value();
print(value);
},
);

await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await consumedThing.invokeAction('toggle', 'Hello World!');
await subscription.stop();

await consumedThing.invokeAction('toggle', 'Bye World!');
await consumedThing.readAndPrintProperty('status');
print('Done!');
}

extension ReadAndPrintExtension on ConsumedThing {
Future<void> readAndPrintProperty(String propertyName) async {
final output = await readProperty(propertyName);
final value = await output.value();
print(value);
}
}
8 changes: 8 additions & 0 deletions lib/src/binding_coap/coap_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,12 @@ final class CoapClient implements ProtocolClient {
yield content;
}
}

@override
Future<Content> requestThingDescription(Uri url) async => _sendRequest(
url,
coap.RequestMethod.get,
form: null,
accept: coap.CoapMediaType.applicationTdJson,
);
}
14 changes: 14 additions & 0 deletions lib/src/binding_http/http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,18 @@ final class HttpClient implements ProtocolClient {

yield encodedLinks;
}

@override
Future<Content> requestThingDescription(Uri url) async {
final request = Request(HttpRequestMethod.get.methodName, url);
const tdContentType = 'application/td+json';
request.headers['Accept'] = tdContentType;

final response = await _client.send(request);

return Content(
response.headers['Content-Type'] ?? tdContentType,
response.stream,
);
}
}
6 changes: 6 additions & 0 deletions lib/src/binding_mqtt/mqtt_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,10 @@ final class MqttClient implements ProtocolClient {
// TODO: implement discoverWithCoreLinkFormat
throw UnimplementedError();
}

@override
Future<Content> requestThingDescription(Uri url) {
// TODO: implement requestThingDescription
throw UnimplementedError();
}
}
3 changes: 3 additions & 0 deletions lib/src/core/protocol_interfaces/protocol_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ abstract interface class ProtocolClient {
void Function(Exception error)? error,
required void Function() complete,
});

/// Requests a Thing Description as [Content] from a [url].
Future<Content> requestThingDescription(Uri url);
}
Loading

0 comments on commit 836ab1c

Please sign in to comment.