This technical report presents the technical aspects regarding the implementation of the Web of Things platform project done for the Web Application Development course. The project consists of a Web application that allows its users to manage their own smart homes by adding smart devices to the homes, interacting with the smart devices and adding other users to the home.
We developed the application using the REST paradigm, and the communication between the client (the front-end of the application) and the server (the back-end application) is done through a REST API. We made this design choice because the REST paradigm "enforces the separation of concerns and helps the client and the server components evolve independently" (Restful API website), which is a general development good practice that helped us better organize inside the team.
The use cases that are covered in our application are the ones presented in the following use case diagram:
After creating an account and logging in to the application, a user can create homes, add smart devices to them, become part of another user's homes and interact with the smart devices inside the homes.
In the following sections, we will present more details regarding the implementation of this solution.
The resources that are used inside our Web application are:
The Owner of a home will be able to group the smart devices inside the home in the following categories:
Other permissions related to each role inside the smart home are:
The resources presented previously are accessible through the REST API, by making a GET request to the website’s endpoint /{resourceName}/{resourceIdentifier}. Before fetching the resource, the server first checks if the user has the necessary permissions to access that request. The responses of the REST API will always be JSON-LD objects, offering context to each mapped field of the returned resource. Each API Call made by the front-end application (except the ones for registering a new user and retrieving an existing user's access token) is authenticated using an authorization token that will also be used to determine if the requester has the necessary rights to perform that operation.
For the architecture of the application, we use a layered approach. The presentation layer, which is the front-end of the application, communicates with the business layer, which is the API of the application. The business layer handles checking the permissions of the user for which the API Calls are made, using utilitary functions for communicating with the database, and making API Calls to the smart devices, in order to interact with them. The utilitary functions contain the SPARQL query logic, which communicate with the TDB database using Apache Jena.
A high-level presentation of the main modules of the application is shown in the C4 diagram, level 3 below:
For this project, in order to simulate the IoTs adopting the WoT Thing Description specification, we are using Mozilla WebThings Framework. According to the project’s official webpage:
The WebThings Framework is a collection of reusable software components to help you build your own web things, which directly expose the Web Thing API. This means they can be discovered by a Web of Things gateway or client, which can then automatically detect the device's capabilities and monitor and control it over the web.
These IoTs are built in a server separate from the main Web Application. This server exposes different endpoints for each object, respecting the Web Thing API specification for that specific object. For the WebThings server, we use the official code example for the framework containing a lamp and a humidity sensor.
By using the URL of a smart device's WoT specification, a user will be able to save the new device in the application and then add it to his home. Adding a device to his home offers offer him the possibility to see the object’s properties and perform the supported actions on them.
The WoT descriptions of the objects are stored in the application's database, so that the user can easily control his linked smart devices. The back-end server then makes API calls to interact with the smart objects by using the base URL and constructing the endpoints for properties and actions using the base link and their names.
This application adheres to the linked data principles as each resource has a unique HTTP URI (represented by the REST API endpoint /{resourceType}/{resourceIdentifier}, which returns relevant information about that resource in the JSON-LD format, containing data for discovering other linked resources. The JSON-LD responses also contain information about the context of each property, so that crawlers can also understand their meanings.
For expressing these properties, we used the following vocabularies:
At first, we wanted to use BrightstarDB for storing the data, but we had issues configuring it in our project and we switched to Apache Jena, which is free, open-source and well-documented. Apache Jena offered us TDB, which, according to their website, is a native high performance triple store, and the RDF API, which enabled us to easily work with resources inside the model and perform various SPARQL queries on the data.
The most important interrogations that are used inside the applications are finding all homes linked to a user, all users that have a role inside a home, all smart devices inside a home and all properties and actions applicable to a smart device. The SPARQL queries are done in the back-end, so that the front-end does not need to handle the logic of building and executing these queries. The results of the SPARQL queries are also be parsed in the back-end so the REST API will return the responses structured in a way that will require less processing on the front-end side.
The frontend solution is represented by a React web application that offers a user-friendly interface to the user and communicates with the backend server's REST API to offer full-functionality to the user. A user-guide for the application can be found here.
The backend application is a Spring server with three controllers, corresponding to the three main resources of the application. At the server startup, the application establishes the connection with the TDB triple-store, which is then used throughout the application for persistence.
Each controller contains the main logic for all the different endpoints it exposes, and uses some utilitary classes in order to perform the different SPARQL queries needed for reading the data from the database, and then processing the data so it's mapped to the internal models used in the application. In order to enrich those models with semantic information and respond with proper JSON-LD files, we use the Jackson-Jsonld Java library, which enables us to add annotations inside the models for expressing the context for the attributes, and then to serialize them in JSON-LD format.
A more complex SPARQL query that is used for fetching details about a device with a given id is built in the following code snippet:
Here we fetch all necessary data regarding a device by selecting all its properties, either literals or resources, using the relationships that were used when posting that device. There are four optional fields that do not depend on each other, meaning category, baseLink, property and action. They belong to separate OPTIONAL bodies since the absence of one of them does not mean that we don't want to fetch the other data.
We are also using constants for fetching URLS of vocabularies and properties, so that they are easy to change if the vocabulary is updated or we decide to use a different approach for expressing one of the relationships.
Another interesting SPARQL query is the one where we find the role of a user inside the home where a device is located, having as input the user id and the device id.
Here we find a home that has as one of its members a user with the given userId and also contains a device with the given deviceId, and then we extract the home id for logging purpose and the user role as part of the query, since this is something that we use for checking if a user is allowed to perform a certain action on a device.
Updating a resource is done through PATCH operations and is only possible for Users and Homes. In order to implement the PATCH operations, we use the Java library json-patch, which processes the standard operations for patching an object. After obtaining the patched object, we use an utilitary function (either from the class UserResourceUpdater or HomeResourceUpdater) to observe the differences between the old object and the patched object, and then to modify the corresponding triples inside the triple-store - which is done by deleting the old triples and adding new ones with the updated values.
An example of building a delete query that is used for updating the user's inside a home is the following:
Here we delete all three statements related to a user's relationship with a house: the hasMember relationship that is tied to a blank node and the two statements that are tied to the blank node, one related to the id of the user of the home and the other one related to the role of the user in that home.
Deleting a resource is done through the Apache Jena RDF API, by deleting all the statements inside the triple-store that contain the given resource.
Communication with a smart device is done for multiple endpoints: