ThingWorx Azure IoT Hub Connector > Azure IoT Edge Module Twin
Azure IoT Edge Module Twin
The Azure IoT Hub Connector supports the following activities with Azure IoT Edge Devices:
Read and write edge module twin properties. These properties are represented as properties in ThingWorx.
Convert telemetry messages that the edge module sends into ThingWorx property updates.
The sections that follow explain this support.
Read and Write Edge Module Twin Properties
To process property reads and writes for edge modules, the Connector applies namespacing to modules Namespacing means that module twin properties are defined using a module prefix and a delimiter (two colons), followed by the property name itself. The Connector applies the namespacing to remote services defined on the Edge Module as well. The general formats of property and service names in the ThingWorx Platform follow:
(moduleId)::(propertyName)
(moduleId)::(serviceName)
Module twin properties are represented as "remote" properties in ThingWorx. Here, "remote" indicates that the source of the properties is the edge. Note that you must define twinDesired, twinReported, and twinTags properties with unique names. Here, unique means that the names must differ from the property names of the existing device twin. Each property can have a unique name, but the remote property names must be defined using the moduleId:

(moduleId)::twinDesired
(moduleId)::twinReported
(moduleId)::twinTags
Convert Telemetry Messages from the Edge Module into ThingWorx Property Updates
After defining the remote property names using the moduleId and delimiter format, you must configure the routing rules in the deployment manifest of the edge module so that messages are forwarded from the Azure IoT Edge Hub to the Azure IoT Hub that connects to the ThingWorx Platform. For example, here is a snippet of routing rules defined in a file, deployment.json::

"$edgeHub": {
"properties.desired": {
"schemaVersion": "1.0",
"routes": {
"TWXEdgeModuleToIoTHub": "FROM /messages/* INTO $upstream"
},
"storeAndForwardConfiguration": {
"timeToLiveSecs": 7200
}
}
},
Notice that this rule routes all telemetry messages into $upstream, which is the IoT Hub. The telemetry messages contain a system property, iothub-connection-module-id, which defines the moduleId. The Connector parses each JSON payload in each message and attempts to convert each key into a property write. As long as the moduleId is set, the Connector generates the remote property name and forwards the property and its value to the ThingWorx Platform.
How to Read/Write Edge Module Twin Properties
* 
The examples shown below use the name TWXEdgeModule. Keep in mind that these are just examples. The names you use for modules is up to you. Just be consistent between Azure IoT Edge and ThingWorx.
Follow these steps to read and write Edge Module Twin properties:
1. In ThingWorx Composer create a new remote property named moduleTwinDesired and set:
RemotePropertName = TWXEdgeModule::twinDesired
Cache Method = "Fetch from remote every read"
2. In Composer, create a new remote property named moduleTwinReported and set:
RemotePropertyName = TWXEdgeModule::twinReported
Read Only = "true"
RemotePropertyName = TWXEdgeModule::twinDesired
Cache Method = "Fetch from remote every read"
3. In Composer, create a new remote property named moduleTwinTags and set:
RemotePropertyName = TWXEdgeModule::twinTags
Cache Method = "Fetch from remote every read"
4. Write the moduleTwinDesired property by setting its value from Composer:
5. Write the moduleTwinTags property by setting its value from Composer:
6. Validate that the module twin property writes were pushed to the edge device in Azure by selecting hubName > IoT Edge > edgeDevice > yourModuleName > Module Identity Twin:
7. Validate that each moduleTwin property can be read in Composer by refreshing your edge Thing properties:
* 
The moduleTwinReported is empty in the list above because the edge module did not set the module twin "reported" property.
How Telemetry Messages from the Edge Module Become Property Updates
Follow these steps to learn how telemetry messages sent from the edge module are converted into ThingWorx property updates:
1. First update your edge module Java code to periodically send telemetry messages for temperature and humidity. Here is some sample code that does this:

package com.thingworx.edgemodule;

import com.microsoft.azure.sdk.iot.device.*;
import com.microsoft.azure.sdk.iot.device.DeviceTwin.*;
import com.google.gson.Gson;

import java.io.*;
import java.net.URISyntaxException;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class TwxEdgeDemo {

// Define method response codes
private static final int METHOD_SUCCESS = 200;
private static final int METHOD_NOT_DEFINED = 404;
private static final int INVALID_PARAMETER = 400;

// Using the MQTT protocol to connect to IoT Hub
private IotHubClientProtocol protocol = IotHubClientProtocol.MQTT;
private ModuleClient client;
private ExecutorService threadPool = Executors.newFixedThreadPool(1);

private static int telemetryInterval = 5000;
private boolean running = true;

public void start() {
try {
client = ModuleClient.createFromEnvironment(protocol);
client.open();

// Register to receive direct method calls.
client.subscribeToMethod(new DeviceMethodCallback() {
@Override
public DeviceMethodData call(String methodName, Object methodData, Object context) {
return handleDirectMethod(methodName, methodData, context);
}
}, null, new IotHubEventCallback() {
@Override
public void execute(IotHubStatusCode responseStatus, Object callbackContext) {
System.out
.println("Direct method # IoT Hub responded to device method acknowledgement with status: "
+ responseStatus.name());
}
}, null);

client.setMessageCallback(new MessageCallback() {

@Override
public IotHubMessageResult execute(Message message, Object callbackContext) {

System.out.println("Received message with content: "
+ new String(message.getBytes(), Message.DEFAULT_IOTHUB_MESSAGE_CHARSET));
for (MessageProperty messageProperty : message.getProperties()) {
System.out.println(messageProperty.getName() + " : " + messageProperty.getValue());
}
return IotHubMessageResult.COMPLETE;
}
}, null);

// Create new thread and start sending messages
threadPool.execute(new MessageSender());

while(running) {
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws IOException, URISyntaxException {
TwxEdgeDemo demo = new TwxEdgeDemo();
demo.start();

}

private DeviceMethodData handleDirectMethod(String methodName, Object methodData, Object context) {
DeviceMethodData deviceMethodData;
String payload = new String((byte[]) methodData);
switch (methodName) {
case "SetTelemetryInterval": {
int interval;
try {
int status = METHOD_SUCCESS;
interval = Integer.parseInt(payload);
System.out.println("Direct method - Setting telemetry interval (seconds): " + interval);
telemetryInterval = interval * 1000;
deviceMethodData = new DeviceMethodData(status, "Executed direct method " + methodName);
} catch (NumberFormatException e) {
int status = INVALID_PARAMETER;
deviceMethodData = new DeviceMethodData(status, "Invalid parameter " + payload);
}
break;
}
case "SetModuleProperty": {
System.out.println("Direct method - Set Module Property called with: " + payload);
deviceMethodData = new DeviceMethodData(METHOD_SUCCESS, "Executed direct method " + methodName);
break;
}
default: {
int status = METHOD_NOT_DEFINED;
deviceMethodData = new DeviceMethodData(status, "Not defined direct method " + methodName);
}
}
return deviceMethodData;
}

// Specify the telemetry to send to your IoT hub.
private class TelemetryDataPoint {
private double temperature;
private double humidity;

public TelemetryDataPoint(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
}

// Serialize object to JSON format.
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this);
}
}

private class MessageSender implements Runnable {
public void run() {
try {
// Initialize the simulated telemetry.
double minTemperature = 20;
double minHumidity = 60;
Random rand = new Random();

while (true) {
// Simulate telemetry.
double currentTemperature = minTemperature + rand.nextDouble() * 15;
double currentHumidity = minHumidity + rand.nextDouble() * 20;
TelemetryDataPoint telemetryDataPoint = new TelemetryDataPoint(currentTemperature, currentHumidity);

// Add the telemetry to the message body as JSON.
Message msg = new Message(telemetryDataPoint.toJson());

// Add a custom application property to the message.
// An IoT hub can filter on these properties without access to the message body.
msg.setProperty("temperatureAlert", (currentTemperature > 30) ? "true" : "false");

System.out.println("Sending message: " + telemetryDataPoint.toJson());

Object lockobj = new Object();
// Send the message.
client.sendEventAsync(msg, new IotHubEventCallback() {
@Override
public void execute(IotHubStatusCode responseStatus, Object context) {
System.out.println("IoT Hub responded to message with status: " + responseStatus.name());

if (context != null) {
synchronized (context) {
context.notify();
}
}
}
}, lockobj);

synchronized (lockobj) {
lockobj.wait();
}
Thread.sleep(telemetryInterval);
}
} catch (InterruptedException e) {
System.out.println("Finished.");
}
}
}
}
2. From ThingWorx Composer, create a new remote property named temperature with TWXEdgeModule as the moduleId such that the remote property name is TWXEdgeModule::temperature
3. From Composer, create a new remote property named humidity with TWXEdgeModule as the moduleId such that the remote property name is TWXEdgeModule::humidity.
4. Refresh the page, and validate that the temperature and humidity values sent from the edge module are being processed by the ThingWorx Platform.