The Linux Foundation Projects
Skip to main content
By | July 23, 2020

EdgeX Foundry Device Actuation from the Cloud

Written by Jason Bonafide, EdgeX Foundry Contributor and Principal Software Engineer at Dell Technologies

EdgeX Foundry is a platform that serves as middleware in edge computing – serving between physical sensing and actuating devices and our information technology (IT) systems. Edgex’s interoperability enables communication between real world devices with simplicity in mind.

EdgeX Foundry is composed of several interoperable service layers. The layer that we will focus on in this tutorial is called Device Services Layer. The role of a Device Service is to connect “things” (sensors and devices) into EdgeX. For those looking to establish communication between devices and perform actions on conditional system-state, this tutorial is for you!

At Dell Technologies, we wanted to establish bi-directional communication between simulated devices on the edge (in an isolated network) with EdgeX Foundry. This enabled us to deploy a single up-to-date EdgeX Foundry cluster in VMWare’s One Cloud. This would speed up the process in bootstrapping systems on the edge which can showcase the variety of protocols supported by EdgeX. Demonstrating this flow, is the goal of this tutorial.

Tools and Technologies

While this tutorial aims to suit the needs of a variety of setups, below are used in this tutorial:

System Overview

Figure A contains the following components:

  • MQTT Device Service: Application which connects our Temperature Sensor to EdgeX.
  • Temperature Sensor: Simulated MQTT device on the edge.
  • EdgeX Foundry: EdgeX Core Services which handle the device readings, data, and actuation.

Device Overview

EdgeX Foundry’s MQTT Device Service leverages 3 topics when integrating a device with the platform:

  • Device-list Protocol Topic: This topic is used by MQTT Device Service for invoking a command on the device. In our example, we refer to this topic as the CommandTopic. This configuration can be modified at toml.
  • Driver IncomingTopic: This topic is by MQTT Device Service for receiving data, and publishing the data to core-data for persistence. This configuration can be modified at toml.
  • Driver ResponseTopic: This topic is used by core-command in receiving responses from the device actuation. This configuration is can be modified at toml.

Simulated Temperature Sensor Controller Device

  • Publishes temperature every 5 seconds to DataTopic.
  • Subscribes to CommandTopic which contains commands from edgex-core-command.
  • Publishes response for inbound command to RepsonseTopic.

This application used to simulate a temperature sensor can be found here.

Defining Device Profiles

As a pre-requisite to configuring the MQTT Device Service, we will need to define the Device Profiles for the devices.

A Device Profile defines the device’s values and operation methods (Read or Write). The device values and operation methods are essential in defining the “what” and “how” we can begin to tell EdgeX Foundry how it can interact with our devices.

Each Device Profile contains the following sections:

  • Identification: fields which pertain to the identification of a Device Profile.
  • DeviceResources: specification of sensor values within a device which may be read from or written to either individually or as part of a deviceCommand.
  • DeviceCommands: defines access to read and writes for simultaneous device resources.
  • CoreCommands: specifies the commands which are available via the core-command microservice.

Temperature Sensor Device Profile

# Identification properties
name: “TemperatureSensor”
manufacturer: “Generic”
model: “MQTT-123”

# Labels that we can use for filtering especially when invoking endpoints throughout the microservices.
labels:
– “temperature”
– “sensor”
– “controller”
– “mqtt”
description: “Simulated temperature sensor controller device”

# The Sensor is a simple one which only provides read-only access to its constantly updating temperature value.
deviceResources:
– name: temperature
description: “Sensor temperature value”
properties:
value:
{ type: “string”, “readWrite”: “R” }
units:
{ type: “string”, readWrite: “R” }

# Commands that we are concerned with in regards to interacting with the device.
deviceCommands:
– name: temperature
get:
– { index: “1”, “operation”: “get”, deviceResource: “temperature” }

# Defining of endpoints within core-command that we can use to interact with the sensor.
coreCommands:
– name: temperature
get:
path: “/api/v1/device/{deviceId}/temperature”

# 200 response with the temperature value in the response.
responses:
– code: “200”
description: “Get the temperature”
expectedValues: [ “temperature” ] – code: “500”
description: “internal server error”
expectedValues: []

Configuring MQTT Device Service

EdgeX Foundry offers an MQTT Device Service in which we will configure for our devices.

Host Configuration

While we intend to communicate with our device on a local network, we will still want to configure the Host for device-mqtt-go to be localhost. Later on in this tutorial, we will talk about port-forwarding to this process.

Each system may look different as to where you can find EdgeX and how you may connect to it. Be sure to update the Host and Port configuration properties for the clients. The device service will need to talk to some Core EdgeX Services.

Device Configuration

We leverage [[DeviceList]] to create our pre-defined Temperature Sensor.

In this section, we flesh out the device’s metadata.

  • Name: TemperatureSensor
  • Profile: TemperatureSensor
  • Description = ‘Simulated MQTT Temperature Sensor device’
  • Labels = [ ‘MQTT’, ‘Temperature’, ‘Sensor’ ]

Next we define MQTT protocol configuration:

  • Schema: tcp
  • Host: [MQTT Borker Host] – In my case, this points to our eclipse mosquitto instance in the Kubernetes cluster.
  • Port: [MQTT Broker Port] – In my case, this ports to the expose NodePort for eclipse moquitto.

Driver Configuration

Other than general MQTT configuration, which will need to point to the eclipse-mosquitto instance, you may or may not need to update the IncomingTopic and ResponseTopic. The example we use in the tutorial, uses the default configuration values provided within the example of the Device Service.

  • IncomingTopic: DataTopic – topic in which our device will push temperature value to.
  • ResponseTopic: ResponseTopic – topic in which we will publish the response from the command invocation.

Finalized configuration.toml

IP Addresses have been redacted and should be modified to better fit your scenario.

# Turning the log-level to DEBUG so that we can capture publish/subscribe actions
[Writable] LogLevel = ‘DEBUG’

# Configuration for the device MQTT service:
# Set the Host my machine’s IP address
# Leaving the defaults for the remaining configurations.
[Service] BootTimeout = 30000
CheckInterval = ’10s’
ClientMonitor = 15000
Host = ‘localhost’
Port = 49982
Protocol = ‘http’
StartupMsg = ‘device mqtt started’
Timeout = 5000
ConnectRetries = 10
Labels = [] EnableAsyncReadings = true
AsyncBufferSize = 16

# We are not using the Registry within this example
[Registry] Host = ‘localhost’
Port = 8500
Type = ‘consul’

# Using defaults
[Logging] EnableRemote = false
File = ”

# Configure clients for the other microservices:
# Pointing at the EdgeX Deployments in the VMWare One Cloud.
[Clients] [Clients.Data] Protocol = ‘http’
Host = ‘k8s.cluster’
Port = 48080

[Clients.Metadata] Protocol = ‘http’
Host = ‘k8s.cluster’
Port = 48081

[Clients.Logging] Protocol = ‘http’
Host = ‘k8s.cluster’
Port = 48061

# Configuration for the device:
# Ensure that ProfilesDir is pointing to the directory which the device profile configrations reside.
[Device] DataTransform = true
InitCmd = ”
InitCmdArgs = ”
MaxCmdOps = 128
MaxCmdValueLen = 256
RemoveCmd = ”
RemoveCmdArgs = ”
ProfilesDir = ‘./res/lf-edge’
UpdateLastConnected = false

# Pre-define Devices:
# Temperature Sensor and AC Fan devices.
# Configured to point at MQTT broker in the Cloud.
# Using CommandTopic as the topic which will handle actuating the devices.
# Configure automated events on the temperature Device Resource
[[DeviceList]] Name = ‘Temperature Sensor’
Profile = ‘TemperatureSensorProfile’
Description = ‘Simulated MQTT Temperature Sensor device’
Labels = [ ‘MQTT’, ‘Temperature’, ‘Sensor’ ] [DeviceList.Protocols] [DeviceList.Protocols.mqtt] Schema = ‘tcp’
Host = ‘k8s.cluster’
Port = ‘1883’
ClientId = ‘CommandPublisher’
User = ‘admin’
Password = ‘public’
Topic = ‘CommandTopic’
[[DeviceList.AutoEvents]] Frequency = ’20s’
OnChange = false
Resource = ‘temperature’

# Driver configs:
# DataTopic is the topic the Device Service will use to provide to Core Data.
# Reference MQTT Broker in the Cloud.
# ResponseTopic is the topic which the Device scripts will use to communicated responses on command invocations.
[Driver] IncomingSchema = ‘tcp’
IncomingHost = ‘k8s.cluster’
IncomingPort = ‘1883’
IncomingUser = ‘admin’
IncomingPassword = ‘public’
IncomingQos = ‘0’
IncomingKeepAlive = ‘3600’
IncomingClientId = ‘IncomingDataSubscriber’
IncomingTopic = ‘DataTopic’
ResponseSchema = ‘tcp’
ResponseHost = ‘k8s.cluster’
ResponsePort = ‘1883’
ResponseUser = ‘admin’
ResponsePassword = ‘public’
ResponseQos = ‘0’
ResponseKeepAlive = ‘3600’
ResponseClientId = ‘CommandResponseSubscriber’
ResponseTopic = ‘ResponseTopic’
ConnEstablishingRetry = ’10’
ConnRetryWaitTime = ‘5’

Router Port-Forwarding

Given a scenario where EdgeX Foundry is deployed in the cloud, and our devices are on a local network, one option is to leverage port-forwarding our your router.

In my scenario, EdgeX Foundry exists in VMWare’s One Cloud while my MQTT Device Service and device simulations reside on my local home network. While it is not recommended for this setup on a home network (for security purposes), I am going to use my home network and configure port-forwarding on my router to demonstrate this flow for similar environments. This will allow me to register my MQTT Device Service within EdgeX Foundry using my public IP address.

With the help of port-forwarding, I can configure my router to forward all requests to http://[PUBLIC IP]:49982 to the machine which is running the MQTT Device Service on port 49982.

The flow demonstrated in Figure B assumes ingress/egress routing supports communication between both entities.

Actuating MQTT Devices from the cloud

Before diving in, here are the host and port configurations I will use to access EdgeX:

  • edgex-core-data: k8s.cluster:30800
  • edgex-core-metadata: k8s.cluster:30801
  • edgex-core-command: k8s.cluster:30802

Earlier, we’ve configured MQTT Device Service to run on localhost on a machine within a local network. If your setup is similar to mine, this will not be accessible from a cloud environment. Fortunately, we can use core-metadata’s API to modify the URLs at which the Device Service can be accessed from the cloud. Since device-mqtt-go is still running on localhost with port-forwarding configured, the Device Service can be accessed by EdgeX in the cloud.

Let’s get the Device Service’s Addressable details in effort to capture this device information we will use to update some routing information.

# Invoking core-metadata

curl –location –request GET ‘k8s.cluster:30801/api/v1/addressable’

This will yield a response similar to:

[
{
“created”: 1594771598214,
“modified”: 1594771598214,
“origin”: 1594771598226,
“id”: “0da6eda1-fad9-41b6-a1c3-c50aab4679f8”,
“name”: “edgex-device-mqtt”,
“protocol”: “HTTP”,
“method”: “POST”,
“address”: “localhost”,
“port”: 49982,
“path”: “/api/v1/callback”,
“baseURL”: “http://localhost:49982”,
“url”: “http://localhost:49982/api/v1/callback”
}
]

Now lets use the previous response body and update the following fields to reference our network’s public IP: – address – baseUrl – url

Note that [PUBLIC IP] is used in place of your the IP address that you will use when routing to your network

# Invoking core-metadata

curl –location –request PUT ‘k8s.cluster:30801/api/v1/addressable’ \
–header ‘Content-Type: application/json’ \
–data-raw ‘{
“id”: “0da6eda1-fad9-41b6-a1c3-c50aab4679f8”,
“name”: “edgex-device-mqtt”,
“protocol”: “HTTP”,
“method”: “POST”,
“address”: “[PUBLIC IP]”,
“port”: 49982,
“path”: “/api/v1/callback”,
“baseURL”: “http://[PUBLIC IP]:49982”,
“url”: “http://[PUBLIC IP]:49982/api/v1/callback”
}’

At this point, the Device Service is routable from the cloud. Next, let’s get the TemperatureSensor device ID:

# Invoking core-command

curl –location –request GET ‘k8s.cluster:30802/api/v1/device/name/TemperatureSensor’

This will result in a response like this:

{
“id”: “7af6bad7-7c4f-408b-9a62-eb909e0aad4f”,
“name”: “TemperatureSensor”,
“adminState”: “UNLOCKED”,
“operatingState”: “ENABLED”,
“labels”: [
“MQTT”,
“Temperature”,
“Sensor”
],
“commands”: [
{
“created”: 1594771641016,
“modified”: 1594771641016,
“id”: “118cb3c7-3c1b-4088-bd88-7c27d304aba9”,
“name”: “temperature”,
“get”: {
“path”: “/api/v1/device/{deviceId}/temperature”,
“responses”: [
{
“code”: “200”,
“description”: “Get the temperature”,
“expectedValues”: [
“temperature”
] },
{
“code”: “500”,
“description”: “internal server error”
}
],
“url”: “http://localhost:48082/api/v1/device/7af6bad7-7c4f-408b-9a62-eb909e0aad4f/command/118cb3c7-3c1b-4088-bd88-7c27d304aba9”
},
“put”: {
“url”: “http://localhost:48082/api/v1/device/7af6bad7-7c4f-408b-9a62-eb909e0aad4f/command/118cb3c7-3c1b-4088-bd88-7c27d304aba9”
}
}
] }

We can extract the get command from the response for TemperatureSensor. This URL can be used to get the temperature value from the device itself.

Something worth noting is that the URL generated within the response above, uses the configured Host and Port values from core-command. Depending on your setup, you may need to invoke a different URL to access core-command. In my particular instance, I am exposing core-command via NodePort Service type. This means that core-command is accessible only through a port range allowed by Kubernetes. The port assigned to core-command is not the same as the port used within the cluster. With a load-balancer, ingress, or some proper network configuration, it could certainly be set up in such that the internal DNS names can be resolved.

# Invoking core-command

curl –location –request GET ‘http://k8s.cluster:30802/api/v1/device/7af6bad7-7c4f-408b-9a62-eb909e0aad4f/command/118cb3c7-3c1b-4088-bd88-7c27d304aba9’

response

{
“device”: “TemperatureSensor”,
“origin”: 1594773351741563527,
“readings”: [
{
“origin”: 1594773351741189868,
“device”: “TemperatureSensor”,
“name”: “temperature”,
“value”: “119.017532”,
“valueType”: “String”
}
],
“EncodedEvent”: null
}

We have now demonstrated bi-directional communication between devices on a local network and EdgeX Foundry in the cloud. While this is a simple demonstration, this flow can enable provisioning edge-device clusters which interact with EdgeX Foundry quickly. With an EdgeX instance deployed in the cloud, this flow (depending on networking), is portable enough to experiment with the different protocols EdgeX Foundry supports.

This tutorial is a start of what can be accomplished with EdgeX Foundry. We’ve walked through a simple flow where we can establish bi-directional communication between edge devices and EdgeX Foundry in the cloud. With all the supported protocols, it is rather simple to get EdgeX up and running with the the Device Service of your choice.

Visit the EdgeX Foundry website for more information or join Slack to ask questions and engage with community members. If you are not already a member of the community, it is really easy to join. Simply visit the wiki page and/or check out the EdgeX Foundry Git Hub.