How to Use Serial Ports
The first section of this tutorial provides an example of how to create an ESF bundle that will communicate with a serial device. In this example, you will communicate with a simple terminal emulator to demonstrate both transmitting and receiving data. You will learn how to perform the following functions:
-
Create a plugin that communicates to serial devices
-
Export the bundle
-
Install the bundle on the remote device
-
Test the communication with minicom (where minicom is acting as an attached serial device such as an NFC reader, GPS device, or some other ASCII based communication device)
In the second part of this tutorial, you can optionally “cloud-enable” the serial device by extending the bundle to transmit messages from the serial device to the Everyware Cloud and allowing them to be viewed in the Everyware Cloud Console. You will also implement the CloudClientListener in ESF to receive messages from the Everyware Cloud, allowing them to be sent via the REST API through the cloud to the attached serial device.
Prerequisites
-
Hardware
-
Use an embedded device running ESF with two serial ports available. (If the device does not have a serial port, USB to serial adapters can be used.)
-
Ensure that minicom is installed on the embedded device.
-
-
Optionally, you can communicate serial data through the Everyware Cloud. To do so, have your Everyware Cloud account credentials handy. If you do not have an account, contact your Eurotech sale representative.
Serial Communication with ESF
This section of the tutorial addresses setting up the hardware, determining serial port device nodes, implementing the basic serial communication bundle, deploying the bundle, and validating its functionality. After completing this section, you should be able to communicate with any ASCII-based serial device attached to an ESF-enabled embedded gateway. In this example, we are using ASCII for clarity, but these same techniques can be used to communicate with serial devices that communicate using binary protocols.
Setup the Hardware
Setup requirements depend on your hardware platform. At a minimum, you will need two serial ports with a null modem serial crossover cable connecting them.
-
If your platform has integrated serial ports, you only need to connect them using a null modem serial cable.
-
If you do not have integrated serial ports on your platform, you will need to purchase USB-to-Serial adapters. It is recommended to use a USB-to-Serial adapter with either the PL-2303 or FTDI chipset, but others may work depending on your hardware platform and underlying Linux support. Once you have attached these adapters to your device, you can attach the null modem serial cable between the two ports.
Determine Serial Device Nodes
This step is hardware specific. If your hardware device has integrated serial ports, contact your hardware device manufacturer or review the documentation to find out how the ports are named in the operating system. The device identifiers should be similar to the following:
-
/dev/ttySxx
-
/dev/ttyUSBxx
-
/dev/ttyACMxx
If you are using USB-to-Serial adapters, Linux usually allocates the associated device nodes dynamically at the time of insertion. In order to determine what they are, run the following command at a terminal on the embedded gateway:
tail -f /var/log/syslog
Depending on your specific Linux implementation, other possible log files may be: /var/log/kern.log, /var/log/kernel, or /var/log/dmesg.
With the above command running, insert your USB-to-Serial adapter. You should see output similar to the following:
root@localhost:/root\> tail -f /var/log/syslog
Aug 15 18:43:47 localhost kernel: usb 3-2: new full speed USB device using uhci_hcd and address 3
Aug 15 18:43:47 localhost kernel: pl2303 3-2:1.0: pl2303 converter detected
Aug 15 18:43:47 localhost kernel: usb 3-2: pl2303 converter now attached to ttyUSB10
In this example, our device is a PL2303-compatible device and is allocated a device node of /dev/ttyUSB10. Your results may be different, but the key is to identify the tty device that was allocated. For the purpose of this tutorial, the device will be referred to as [device_node_1].
During the development process, it is also important to keep in mind that these values are dynamic; therefore, from one boot to the next and one insertion to the next, these values may change. To stop tail from running in your console, escape with ctrl-c.
If you are using two USB-to-Serial adapters, repeat the above procedure for the second serial port. The resulting device node will be referred to as [device_node_2].
Implement the Bundle
Now that you have two serial ports connected to each other, you are ready to develop the serial bundle as follows:
For more detailed information about bundle development (i.e., the plug-in project, classes, and MANIFEST file configuration), please refer to the Hello World Example.
-
Create a Plug-in Project named com.eurotech.example.serial.
-
Create a class named SerialExample in the com.eurotech.example.serial project.
-
Include the following bundles in the MANIFEST.MF:
- org.eclipse.kura.api
- slf4j.api
- org.eclipse.osgi
- org.eclipse.osgi.services
- org.eclipse.osgi.util
- org.eclipse.equinox.io
The following files need to be implemented in order to write the source code:
-
META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and its dependencies.
-
OSGI-INF/component.xml - declarative services definition describing what services are exposed by and consumed by this bundle.
-
OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml - configuration description of the bundle and its parameters, types, and defaults.
-
com.eurotech.example.serial.SerialExample.java - main implementation class.
META-INF/MANIFEST File
The META-INF/MANIFEST.MF file should look as follows when complete:
Whitespaces are significant in this file; make sure yours matches this file exactly.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Serial Example
Bundle-SymbolicName: com.eurotech.example.serial;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: EUROTECH
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Service-Component: OSGI-INF/*.xml
Bundle-ActivationPolicy: lazy
Import-Package: javax.microedition.io;resolution:=optional,
org.eclipse.kura.comm;version="1.0.0",
org.eclipse.kura.configuration;version="1.0.0",
org.osgi.service.component;version="1.2.0",
org.osgi.service.io;version="1.0.0",
org.slf4j;version="1.6.4"
Bundle-ClassPath: .
OSGI-INF/component.xml File
The OSGI-INF/component.xml should look as follows when complete:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.eurotech.example.serial.SerialExample" activate="activate"
deactivate="deactivate" modified="updated" enabled="true" immediate="true"
configuration-policy="require">
<implementation class="com.eurotech.example.serial.SerialExample" />
<!-- If the component is configurable through the ESF ConfigurationService,
it must expose a Service. -->
<property name="service.pid" type="String"
value="com.eurotech.example.serial.SerialExample" />
<service>
<provide interface="com.eurotech.example.serial.SerialExample" />
</service>
<reference bind="setConnectionFactory" cardinality="1..1"
interface="org.osgi.service.io.ConnectionFactory" name="ConnectionFactory"
policy="static" unbind="unsetConnectionFactory" />
</scr:component>
OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml File
The OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml file should look as follows when complete:
<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.2.0" localization="en_us">
<OCD id="com.eurotech.example.serial.SerialExample"
name="SerialExample"
description="Example of a Configuring ESF Application echoing data read from the serial port.">
<Icon resource="http://sphotos-a.xx.fbcdn.net/hphotos-ash4/p480x480/408247_10151040905591065_1989684710_n.jpg" size="32"/>
<AD id="serial.device"
name="serial.device"
type="String"
cardinality="0"
required="false"
description="Name of the serial device (e.g. /dev/ttyS0, /dev/ttyACM0, /dev/ttyUSB0)."/>
<AD id="serial.baudrate"
name="serial.baudrate"
type="String"
cardinality="0"
required="true"
default="9600"
description="Baudrate.">
<Option label="9600" value="9600"/>
<Option label="19200" value="19200"/>
<Option label="38400" value="38400"/>
<Option label="57600" value="57600"/>
<Option label="115200" value="115200"/>
</AD>
<AD id="serial.data-bits"
name="serial.data-bits"
type="String"
cardinality="0"
required="true"
default="8"
description="Data bits.">
<Option label="7" value="7"/>
<Option label="8" value="8"/>
</AD>
<AD id="serial.parity"
name="serial.parity"
type="String"
cardinality="0"
required="true"
default="none"
description="Parity.">
<Option label="none" value="none"/>
<Option label="even" value="even"/>
<Option label="odd" value="odd"/>
</AD>
<AD id="serial.stop-bits"
name="serial.stop-bits"
type="String"
cardinality="0"
required="true"
default="1"
description="Stop bits.">
<Option label="1" value="1"/>
<Option label="2" value="2"/>
</AD>
</OCD>
<Designate pid="com.eurotech.example.serial.SerialExample">
<Object ocdref="com.eurotech.example.serial.SerialExample"/>
</Designate>
</MetaData>
com.eurotech.example.serial.SerialExample.java File
The com.eurotech.example.serial.SerialExample.java file should look as follows when complete.
package com.eurotech.example.serial;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.io.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.kura.comm.CommConnection;
import org.eclipse.kura.comm.CommURI;
import org.eclipse.kura.configuration.ConfigurableComponent;
public class SerialExample implements ConfigurableComponent {
private static final Logger s_logger = LoggerFactory.getLogger(SerialExample.class);
private static final String SERIAL_DEVICE_PROP_NAME= "serial.device";
private static final String SERIAL_BAUDRATE_PROP_NAME= "serial.baudrate";
private static final String SERIAL_DATA_BITS_PROP_NAME= "serial.data-bits";
private static final String SERIAL_PARITY_PROP_NAME= "serial.parity";
private static final String SERIAL_STOP_BITS_PROP_NAME= "serial.stop-bits";
private ConnectionFactory m_connectionFactory;
private CommConnection m_commConnection;
private InputStream m_commIs;
private OutputStream m_commOs;
private ScheduledThreadPoolExecutor m_worker;
private Future<?> m_handle;
private Map<String, Object> m_properties;
// ----------------------------------------------------------------
//
// Dependencies
//
// ----------------------------------------------------------------
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = connectionFactory;
}
public void unsetConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = null;
}
// ----------------------------------------------------------------
//
// Activation APIs
//
// ----------------------------------------------------------------
protected void activate(ComponentContext componentContext, Map<String,Object> properties) {
s_logger.info("Activating SerialExample...");
m_worker = new ScheduledThreadPoolExecutor(1);
m_properties = new HashMap<String, Object>();
doUpdate(properties);
s_logger.info("Activating SerialExample... Done.");
}
protected void deactivate(ComponentContext componentContext) {
s_logger.info("Deactivating SerialExample...");
// shutting down the worker and cleaning up the properties
m_handle.cancel(true);
m_worker.shutdownNow();
//close the serial port
closePort();
s_logger.info("Deactivating SerialExample... Done.");
}
public void updated(Map<String,Object> properties) {
s_logger.info("Updated SerialExample...");
doUpdate(properties);
s_logger.info("Updated SerialExample... Done.");
}
// ----------------------------------------------------------------
//
// Private Methods
//
// ----------------------------------------------------------------
/**
* Called after a new set of properties has been configured on the service
*/
private void doUpdate(Map<String, Object> properties) {
try {
for (String s : properties.keySet()) {
s_logger.info("Update - "+s+": "+properties.get(s));
}
// cancel a current worker handle if one if active
if (m_handle != null) {
m_handle.cancel(true);
}
//close the serial port so it can be reconfigured
closePort();
//store the properties
m_properties.clear();
m_properties.putAll(properties);
//reopen the port with the new configuration
openPort();
//start the worker thread
m_handle = m_worker.submit(new Runnable() {
@Override
public void run() {
doSerial();
}
});
} catch (Throwable t) {
s_logger.error("Unexpected Throwable", t);
}
}
private void openPort() {
String port = (String) m_properties.get(SERIAL_DEVICE_PROP_NAME);
if (port == null) {
s_logger.info("Port name not configured");
return;
}
int baudRate = Integer.valueOf((String) m_properties.get(SERIAL_BAUDRATE_PROP_NAME));
int dataBits = Integer.valueOf((String) m_properties.get(SERIAL_DATA_BITS_PROP_NAME));
int stopBits = Integer.valueOf((String) m_properties.get(SERIAL_STOP_BITS_PROP_NAME));
String sParity = (String) m_properties.get(SERIAL_PARITY_PROP_NAME);
int parity = CommURI.PARITY_NONE;
if (sParity.equals("none")) {
parity = CommURI.PARITY_NONE;
} else if (sParity.equals("odd")) {
parity = CommURI.PARITY_ODD;
} else if (sParity.equals("even")) {
parity = CommURI.PARITY_EVEN;
}
String uri = new CommURI.Builder(port)
.withBaudRate(baudRate)
.withDataBits(dataBits)
.withStopBits(stopBits)
.withParity(parity)
.withTimeout(1000)
.build().toString();
try {
m_commConnection = (CommConnection) m_connectionFactory.createConnection(uri, 1, false);
m_commIs = m_commConnection.openInputStream();
m_commOs = m_commConnection.openOutputStream();
s_logger.info(port+" open");
} catch (IOException e) {
s_logger.error("Failed to open port " + port, e);
cleanupPort();
}
}
private void cleanupPort() {
if (m_commIs != null) {
try {
s_logger.info("Closing port input stream...");
m_commIs.close();
s_logger.info("Closed port input stream");
} catch (IOException e) {
s_logger.error("Cannot close port input stream", e);
}
m_commIs = null;
}
if (m_commOs != null) {
try {
s_logger.info("Closing port output stream...");
m_commOs.close();
s_logger.info("Closed port output stream");
} catch (IOException e) {
s_logger.error("Cannot close port output stream", e);
}
m_commOs = null;
}
if (m_commConnection != null) {
try {
s_logger.info("Closing port...");
m_commConnection.close();
s_logger.info("Closed port");
} catch (IOException e) {
s_logger.error("Cannot close port", e);
}
m_commConnection = null;
}
}
private void closePort() {
cleanupPort();
}
private void doSerial() {
if (m_commIs != null) {
try {
int c = -1;
StringBuilder sb = new StringBuilder();
while (m_commIs != null) {
if (m_commIs.available() != 0) {
c = m_commIs.read();
} else {
try {
Thread.sleep(100);
continue;
} catch (InterruptedException e) {
return;
}
}
// on reception of CR, publish the received sentence
if (c==13) {
s_logger.debug("Received serial input, echoing to output: " + sb.toString());
sb.append("\r\n");
String dataRead = sb.toString();
//echo the data to the output stream
m_commOs.write(dataRead.getBytes());
//reset the buffer
sb = new StringBuilder();
} else if (c!=10) {
sb.append((char) c);
}
}
} catch (IOException e) {
s_logger.error("Cannot read port", e);
} finally {
try {
m_commIs.close();
} catch (IOException e) {
s_logger.error("Cannot close buffered reader", e);
}
}
}
}
}
At this point the bundle implementation is complete. Make sure to save all files before proceeding.
Deploy the Bundle
In order to proceed, you need to know the IP address of your embedded gateway that is running ESF. Once you do, follow the mToolkit instructions for installing a single bundle to the remote target device located here. When this installation completes, the bundle starts automatically. You should see a message similar to the one below from /var/log/esf.log indicating that the bundle was successfully installed and configured. (You can also run this example in the emulation environment in a Linux or OS X environment. Make sure that your user account has owner permission for the serial device in /dev.)
14:19:10,861 [Component Resolve Thread (Bundle 38)] INFO SerialExample:62 - Activating SerialExample...
14:19:10,861 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - objectClass: [Ljava.lang.String;@69c41ba8
14:19:10,861 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - serial.parity: none
14:19:10,861 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - serial.data-bits: 8
14:19:10,861 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - service.pid: com.eurotech.example.serial.SerialExample
14:19:10,862 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - component.name: com.eurotech.example.serial.SerialExample
14:19:10,862 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - component.id: 22
14:19:10,862 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - serial.baudrate: 9600
14:19:10,862 [Component Resolve Thread (Bundle 38)] INFO SerialExample:99 - Update - serial.stop-bits: 1
14:19:10,862 [Component Resolve Thread (Bundle 38)] INFO SerialExample:133 - Port name not configured
14:19:10,866 [Component Resolve Thread (Bundle 38)] INFO SerialExample:66 - Activating SerialExample... Done.
14:19:10,867 [Component Resolve Thread (Bundle 38)] INFO ConfigurableComponentTracker:118 - Adding ConfigurableComponent com.eurotech.example.serial.SerialExample
14:19:10,867 [Component Resolve Thread (Bundle 38)] INFO ConfigurationServiceImpl:553 - Registration of ConfigurableComponent com.eurotech.example.serial.SerialExample by org.eclipse.kura.core.configuration.ConfigurationServiceImpl@1b020bff...
14:19:10,870 [Component Resolve Thread (Bundle 38)] INFO ConfigurationServiceImpl:564 - Registering com.eurotech.example.serial.SerialExample with ocd: org.eclipse.kura.core.configuration.metatype.Tocd@4104ebd ...
14:19:10,870 [Component Resolve Thread (Bundle 38)] INFO ConfigurationServiceImpl:571 - Registration Completed for Component com.eurotech.example.serial.SerialExample.
14:19:11,236 [HouseKeeperTask] INFO HouseKeeperTask:44 - HouseKeeperTask started.
14:19:11,236 [HouseKeeperTask] INFO HouseKeeperTask:48 - HouseKeeperTask: Check store...
14:19:11,304 [HouseKeeperTask] INFO HouseKeeperTask:53 - HouseKeeperTask: Delete confirmed messages...
14:19:11,306 [HouseKeeperTask] INFO HouseKeeperTask:63 - HouseKeeperTask: Performing store checkpoint with defrag...
14:19:11,317 [HouseKeeperTask] INFO HouseKeeperTask:67 - HouseKeeperTask ended.
Validate the Bundle
Next, you need to test that your bundle does indeed echo characters back by opening minicom and configuring it to use [device_node_2] that was previously determined.
Open minicom using the following command at a Linux terminal on the remote gateway device:
minicom -s
This command opens a view similar to the following screen capture:
Scroll down to Serial port setup and press ENTER. This step opens a new dialog as shown:
Use the menu options on the left (i.e., A, B, C, etc.) to change desired fields. Set the fields to the same values as shown in the previous screen capture, with the exception that the Serial Device should match the [device_node_2] on your target device. Once this is set, press ENTER to exit from this menu.
In the main configuration menu, select Exit (do not select Exit from Minicom). At this point, you have successfully started minicom on the second serial port attached to your null modem cable, allowing minicom to act as a serial device that can send and receive commands to your ESF bundle. You can verify this operation by typing characters and pressing ENTER. The ENTER function (specifically a ‘\n’ character) signals to the ESF application to echo the buffered characters back to the serial device (minicom in this case). The following screen capture shows an example of that interaction:
Upon startup, minicom sends an initialization string to the serial device. In the example shown above, that initialization string was ‘AT S7=45 S0=0 L1 V1 X4 &c1 E1 Q0’. Those characters were echoed back to your minicom terminal because they were echoed back by our ESF application, which is listening on the port connected to the other end of the null modem cable.
When you are done, exit minicom by pressing ctrl-a and then q and then ENTER. Doing so returns you to the Linux command prompt.
This completes the required, first part of this tutorial. In this example so far, you have written and deployed an ESF bundle on your target device that listens to serial data (coming from the minicom terminal and received on [device_node_1]). This tutorial also demonstrated that the application echoes data back to the same serial port that is received in minicom, which acts a serial device that sends and receives data. If supported by the device, ESF may send and receive binary data instead of ASCII.
Cloud-Enabled Serial Communication
This second part of the tutorial is optional, depending on whether you have an active Everyware Cloud account.
In this section, you can enable the application bundle to communicate with the Everyware Cloud. This capability will require making a fairly significant change to the ESF bundle application. Instead of simply echoing characters to an attached serial device, ESF will send all incoming bytes to the Everyware Cloud using the CloudClient service. ESF will also subscribe for incoming messages from the Everyware Cloud. When a message is received, it will be sent to the serial device. In this application, the ESF-enabled device acts as a gateway for the attached serial device, passing all messages to and from the Everyware Cloud. Before continuing, make sure your embedded gateway is already connected to the Everyware Cloud as described here.
Modify the Code
The following changes must be made to the code and metadata files:
-
Update the Manifest to reflect the new dependencies on cloud capabilities in ESF.
-
Update the component.xml to reflect that the application now depends on the CloudService in ESF.
-
Add to the defined properties in the application.
-
Modify the code to integrate the new cloud capabilities. This is mostly in the doSerial() method and the newly added CloudClientListener callback methods. In addition, new set/unset methods are required for the new CloudService dependency.
All of these changes are shown in the following updated file contents.
META-INF/MANIFEST File
The updated META-INF/MANIFEST.MF file should look as follows when complete:
Whitespaces are significant in this file; make sure yours matches this file exactly.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Serial Example
Bundle-SymbolicName: com.eurotech.example.serial;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: EUROTECH
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Service-Component: OSGI-INF/*.xml
Bundle-ActivationPolicy: lazy
Import-Package: org.eclipse.kura.cloud,
org.eclipse.kura.comm,
org.eclipse.kura.configuration,
org.eclipse.kura.message,
javax.microedition.io;resolution:=optional,
org.osgi.service.component;version="1.2.0",
org.osgi.service.io;version="1.0.0",
org.slf4j;version="1.6.4"
Bundle-ClassPath: .
OSGI-INF/component.xml File
The updated OSGI-INF/component.xml should look as follows when complete:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.eurotech.example.serial.SerialExample" activate="activate"
deactivate="deactivate" modified="updated" enabled="true" immediate="true"
configuration-policy="require">
<implementation class="com.eurotech.example.serial.SerialExample" />
<!-- If the component is configurable through the ESF ConfigurationService,
it must expose a Service. -->
<property name="service.pid" type="String"
value="com.eurotech.example.serial.SerialExample" />
<service>
<provide interface="com.eurotech.example.serial.SerialExample" />
</service>
<reference bind="setConnectionFactory" cardinality="1..1"
interface="org.osgi.service.io.ConnectionFactory" name="ConnectionFactory"
policy="static" unbind="unsetConnectionFactory" />
<reference name="CloudService" policy="static" bind="setCloudService"
unbind="unsetCloudService" cardinality="1..1"
interface="org.eclipse.kura.cloud.CloudService" />
</scr:component>
OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml File
The OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml file should look as follows when complete:
<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.2.0" localization="en_us">
<OCD id="com.eurotech.example.serial.SerialExample"
name="SerialExample"
description="Example of a Configuring ESF Application echoing data read from the serial port.">
<Icon resource="http://sphotos-a.xx.fbcdn.net/hphotos-ash4/p480x480/408247_10151040905591065_1989684710_n.jpg" size="32"/>
<AD id="serial.device"
name="serial.device"
type="String"
cardinality="0"
required="false"
description="Name of the serial device (e.g. /dev/ttyS0, /dev/ttyACM0, /dev/ttyUSB0)."/>
<AD id="serial.baudrate"
name="serial.baudrate"
type="String"
cardinality="0"
required="true"
default="9600"
description="Baudrate.">
<Option label="9600" value="9600"/>
<Option label="19200" value="19200"/>
<Option label="38400" value="38400"/>
<Option label="57600" value="57600"/>
<Option label="115200" value="115200"/>
</AD>
<AD id="serial.data-bits"
name="serial.data-bits"
type="String"
cardinality="0"
required="true"
default="8"
description="Data bits.">
<Option label="7" value="7"/>
<Option label="8" value="8"/>
</AD>
<AD id="serial.parity"
name="serial.parity"
type="String"
cardinality="0"
required="true"
default="none"
description="Parity.">
<Option label="none" value="none"/>
<Option label="even" value="even"/>
<Option label="odd" value="odd"/>
</AD>
<AD id="serial.stop-bits"
name="serial.stop-bits"
type="String"
cardinality="0"
required="true"
default="1"
description="Stop bits.">
<Option label="1" value="1"/>
<Option label="2" value="2"/>
</AD>
<AD id="publish.semanticTopic"
name="publish.semanticTopic"
type="String"
cardinality="0"
required="true"
default="data"
description="Default semantic topic to publish the messages to."/>
<AD id="publish.qos"
name="publish.qos"
type="Integer"
cardinality="0"
required="true"
default="0"
description="Default QoS to publish the messages with.">
<Option label="Fire and forget" value="0"/>
<Option label="Al least once" value="1"/>
<Option label="At most once" value="2"/>
</AD>
<AD id="publish.retain"
name="publish.retain"
type="Boolean"
cardinality="0"
required="true"
default="false"
description="Default retaing flag for the published messages."/>
<AD id="publish.metric.name"
name="publish.metric.name"
type="String"
cardinality="0"
required="true"
default="message"
description="Metric name for messages coming from the serial device to be sent to Everyware Cloud"/>
<AD id="subscribe.semanticTopic"
name="subscribe.semanticTopic"
type="String"
cardinality="0"
required="true"
default="forward"
description="Semantic topic for messages coming from Everyware Cloud to be forwarded to the Serial Device"/>
<AD id="subscribe.metric.name"
name="subscribe.metric.name"
type="String"
cardinality="0"
required="true"
default="message"
description="Metric name for messages coming from Everyware Cloud to be forwarded to the Serial Device"/>
</OCD>
<Designate pid="com.eurotech.example.serial.SerialExample">
<Object ocdref="com.eurotech.example.serial.SerialExample"/>
</Designate>
</MetaData>
com.eurotech.example.serial.SerialExample.java File
The updated com.eurotech.example.serial.SerialExample.java file should look as follows when complete:
package com.eurotech.example.serial;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.ComponentException;
import org.osgi.service.io.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.kura.cloud.CloudClient;
import org.eclipse.kura.cloud.CloudClientListener;
import org.eclipse.kura.cloud.CloudService;
import org.eclipse.kura.comm.CommConnection;
import org.eclipse.kura.comm.CommURI;
import org.eclipse.kura.configuration.ConfigurableComponent;
import org.eclipse.kura.message.KuraPayload;
public class SerialExample implements ConfigurableComponent, CloudClientListener {
private static final Logger s_logger = LoggerFactory.getLogger(SerialExample.class);
// Cloud Application identifier
private static final String APP_ID = "EXAMPLE_SERIAL_PUBLISHER";
private static final String SERIAL_DEVICE_PROP_NAME = "serial.device";
private static final String SERIAL_BAUDRATE_PROP_NAME = "serial.baudrate";
private static final String SERIAL_DATA_BITS_PROP_NAME = "serial.data-bits";
private static final String SERIAL_PARITY_PROP_NAME = "serial.parity";
private static final String SERIAL_STOP_BITS_PROP_NAME = "serial.stop-bits";
// Publishing Property Names
private static final String PUBLISH_TOPIC_PROP_NAME = "publish.semanticTopic";
private static final String PUBLISH_QOS_PROP_NAME = "publish.qos";
private static final String PUBLISH_RETAIN_PROP_NAME = "publish.retain";
private static final String PUBLISH_METRIC_PROP_NAME = "publish.metric.name";
// Subscribing Property Names
private static final String SUBSCRIBE_TOPIC_PROP_NAME = "subscribe.semanticTopic";
private static final String SUBSCRIBE_METRIC_PROP_NAME = "subscribe.metric.name";
private CloudService m_cloudService;
private CloudClient m_cloudClient;
private ConnectionFactory m_connectionFactory;
private CommConnection m_commConnection;
private InputStream m_commIs;
private OutputStream m_commOs;
private ScheduledThreadPoolExecutor m_worker;
private Future<?> m_handle;
private Map<String, Object> m_properties;
// ----------------------------------------------------------------
//
// Dependencies
//
// ----------------------------------------------------------------
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = connectionFactory;
}
public void unsetConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = null;
}
public void setCloudService(CloudService cloudService) {
m_cloudService = cloudService;
}
public void unsetCloudService(CloudService cloudService) {
m_cloudService = null;
}
// ----------------------------------------------------------------
//
// Activation APIs
//
// ----------------------------------------------------------------
protected void activate(ComponentContext componentContext, Map<String,Object> properties) {
s_logger.info("Activating SerialExample...");
m_worker = new ScheduledThreadPoolExecutor(1);
m_properties = new HashMap<String, Object>();
try {
// Acquire a Cloud Application Client for this Application
s_logger.info("Getting CloudApplicationClient for {}...", APP_ID);
m_cloudClient = m_cloudService.newCloudClient(APP_ID);
m_cloudClient.addCloudClientListener(this);
// Don't subscribe because these are handled by the default
// subscriptions and we don't want to get messages twice
doUpdate(properties);
} catch (Exception e) {
s_logger.error("Error during component activation", e);
throw new ComponentException(e);
}
s_logger.info("Activating SerialExample... Done.");
}
protected void deactivate(ComponentContext componentContext) {
s_logger.info("Deactivating SerialExample...");
// shutting down the worker and cleaning up the properties
m_handle.cancel(true);
m_worker.shutdownNow();
// Releasing the CloudApplicationClient
s_logger.info("Releasing CloudApplicationClient for {}...", APP_ID);
m_cloudClient.release();
//close the serial port
closePort();
s_logger.info("Deactivating SerialExample... Done.");
}
public void updated(Map<String,Object> properties) {
s_logger.info("Updated SerialExample...");
doUpdate(properties);
s_logger.info("Updated SerialExample... Done.");
}
// ----------------------------------------------------------------
//
// Cloud Application Callback Methods
//
// ----------------------------------------------------------------
@Override
public void onControlMessageArrived(String deviceId, String appTopic,
KuraPayload msg, int qos, boolean retain) {
s_logger.info("Control message arrived on " + appTopic);
String forwardTopic = (String) m_properties.get(SUBSCRIBE_TOPIC_PROP_NAME);
if(appTopic.equals(forwardTopic)) {
// this is a message that tells us to forward the data to the remote
// serial device, so extract it from the message and send it along
String message = (String) msg.getMetric((String) m_properties.get(SUBSCRIBE_METRIC_PROP_NAME));
if(message != null && m_commOs != null) {
try {
s_logger.info("Writing data to the serial port: " + message);
m_commOs.write(message.getBytes());
m_commOs.write("\r\n".getBytes());
m_commOs.flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
s_logger.info("Can't send incoming message to serial port");
}
}
}
@Override
public void onMessageArrived(String deviceId, String appTopic,
KuraPayload msg, int qos, boolean retain) {
s_logger.info("Message arrived on " + appTopic);
}
@Override
public void onConnectionLost() {
s_logger.info("Connection was lost");
}
@Override
public void onConnectionEstablished() {
s_logger.info("Connection was established");
}
@Override
public void onMessageConfirmed(int messageId, String appTopic) {
s_logger.info("Message confirmed on " + appTopic);
}
@Override
public void onMessagePublished(int messageId, String appTopic) {
s_logger.info("Message published on " + appTopic);
}
// ----------------------------------------------------------------
//
// Private Methods
//
// ----------------------------------------------------------------
/**
* Called after a new set of properties has been configured on the service
*/
private void doUpdate(Map<String, Object> properties) {
try {
for (String s : properties.keySet()) {
s_logger.info("Update - "+s+": "+properties.get(s));
}
// cancel a current worker handle if one if active
if (m_handle != null) {
m_handle.cancel(true);
}
//close the serial port so it can be reconfigured
closePort();
//store the properties
m_properties.clear();
m_properties.putAll(properties);
//reopen the port with the new configuration
openPort();
//start the worker thread
m_handle = m_worker.submit(new Runnable() {
@Override
public void run() {
doSerial();
}
});
} catch (Throwable t) {
s_logger.error("Unexpected Throwable", t);
}
}
private void openPort() {
String port = (String) m_properties.get(SERIAL_DEVICE_PROP_NAME);
if (port == null) {
s_logger.info("Port name not configured");
return;
}
int baudRate = Integer.valueOf((String) m_properties.get(SERIAL_BAUDRATE_PROP_NAME));
int dataBits = Integer.valueOf((String) m_properties.get(SERIAL_DATA_BITS_PROP_NAME));
int stopBits = Integer.valueOf((String) m_properties.get(SERIAL_STOP_BITS_PROP_NAME));
String sParity = (String) m_properties.get(SERIAL_PARITY_PROP_NAME);
int parity = CommURI.PARITY_NONE;
if (sParity.equals("none")) {
parity = CommURI.PARITY_NONE;
} else if (sParity.equals("odd")) {
parity = CommURI.PARITY_ODD;
} else if (sParity.equals("even")) {
parity = CommURI.PARITY_EVEN;
}
String uri = new CommURI.Builder(port)
.withBaudRate(baudRate)
.withDataBits(dataBits)
.withStopBits(stopBits)
.withParity(parity)
.withTimeout(1000)
.build().toString();
try {
m_commConnection = (CommConnection) m_connectionFactory.createConnection(uri, 1, false);
m_commIs = m_commConnection.openInputStream();
m_commOs = m_commConnection.openOutputStream();
s_logger.info(port+" open");
} catch (IOException e) {
s_logger.error("Failed to open port " + port, e);
cleanupPort();
}
}
private void cleanupPort() {
if (m_commIs != null) {
try {
s_logger.info("Closing port input stream...");
m_commIs.close();
s_logger.info("Closed port input stream");
} catch (IOException e) {
s_logger.error("Cannot close port input stream", e);
}
m_commIs = null;
}
if (m_commOs != null) {
try {
s_logger.info("Closing port output stream...");
m_commOs.close();
s_logger.info("Closed port output stream");
} catch (IOException e) {
s_logger.error("Cannot close port output stream", e);
}
m_commOs = null;
}
if (m_commConnection != null) {
try {
s_logger.info("Closing port...");
m_commConnection.close();
s_logger.info("Closed port");
} catch (IOException e) {
s_logger.error("Cannot close port", e);
}
m_commConnection = null;
}
}
private void closePort() {
cleanupPort();
}
private void doSerial() {
// fetch the publishing configuration from the publishing properties
String topic = (String) m_properties.get(PUBLISH_TOPIC_PROP_NAME);
Integer qos = (Integer) m_properties.get(PUBLISH_QOS_PROP_NAME);
Boolean retain = (Boolean) m_properties.get(PUBLISH_RETAIN_PROP_NAME);
String metricName = (String) m_properties.get(PUBLISH_METRIC_PROP_NAME);
if (m_commIs != null) {
try {
int c = -1;
StringBuilder sb = new StringBuilder();
while (m_commIs != null) {
if (m_commIs.available() != 0) {
c = m_commIs.read();
} else {
try {
Thread.sleep(100);
continue;
} catch (InterruptedException e) {
return;
}
}
// on reception of CR, publish the received sentence
if (c==13) {
s_logger.debug("Received serial input, echoing to output: " + sb.toString());
sb.append("\r\n");
String dataRead = sb.toString();
//now instead of simplying echoing data back to the serial
//port, publish it to Everyware Cloud
try {
KuraPayload kuraPayload = new KuraPayload();
kuraPayload.setTimestamp(new Date());
kuraPayload.addMetric(metricName, dataRead);
m_cloudClient.publish(topic, kuraPayload, qos, retain);
} catch (Exception e) {
s_logger.error("Cannot publish topic: "+topic, e);
}
//reset the buffer
sb = new StringBuilder();
} else if (c!=10) {
sb.append((char) c);
}
}
} catch (IOException e) {
s_logger.error("Cannot read port", e);
} finally {
try {
m_commIs.close();
} catch (IOException e) {
s_logger.error("Cannot close buffered reader", e);
}
}
}
}
}
At this point, the bundle implementation is complete. Make sure to save all files before proceeding.
Redeploy the Bundle
Follow the mToolkit instructions for installing a single bundle to the remote target device located here. When this installation completes, the bundle starts automatically. You should see a message similar to the one below from /var/log/esf.log indicating that the bundle was successfully installed and configured. This process should be the same as the first part of this tutorial, except that a prompt will appear indicating that the bundle is already installed. Select yes when asked whether you want to install a new copy.
23:49:42,886 [mToolkit Worker #1] INFO c.e.f.c.c.ConfigurableComponentTracker - Removed ConfigurableComponent com.eurotech.example.serial.SerialExample
23:49:42,887 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Deactivating SerialExample...
23:49:42,888 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Releasing CloudApplicationClient for EXAMPLE_SERIAL_PUBLISHER...
23:49:43,438 [mToolkit Worker #1] ERROR c.e.f.l.n.u.LinuxNetworkUtil - error executing command --- ifconfig 1-3.1 --- exit value = 1
23:49:43,438 [mToolkit Worker #1] WARN c.e.f.l.n.r.NetworkServiceImpl - Could not get MTU for 1-3.1
23:49:43,582 [mToolkit Worker #1] INFO c.e.f.c.d.DataServiceImpl - Storing message on topic :$EDC/#account-name/#client-id/MQTT/APPS, priority: 0
23:49:43,654 [mToolkit Worker #1] INFO c.e.f.c.d.DataServiceImpl - Stored message on topic :$EDC/#account-name/#client-id/MQTT/APPS, priority: 0
23:49:43,659 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closing port input stream...
23:49:43,661 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closed port input stream
23:49:43,661 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closing port output stream...
23:49:43,661 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closed port output stream
23:49:43,661 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closing port...
23:49:43,905 [MQTT Call: serial-example] INFO c.e.f.c.c.CloudServiceImpl - Ignoring feedback message from $EDC/edcguest/serial-example/BA/BIRTH
23:49:44,337 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Closed port
23:49:44,337 [mToolkit Worker #1] INFO c.e.e.s.SerialExample - Deactivating SerialExample... Done.
23:49:44,421 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Activating SerialExample...
23:49:44,422 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Getting CloudApplicationClient for EXAMPLE_SERIAL_PUBLISHER...
23:49:45,052 [Component Resolve Thread (Bundle 6)] ERROR c.e.f.l.n.u.LinuxNetworkUtil - error executing command --- ifconfig 1-3.1 --- exit value = 1
23:49:45,052 [Component Resolve Thread (Bundle 6)] WARN c.e.f.l.n.r.NetworkServiceImpl - Could not get MTU for 1-3.1
23:49:45,197 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.d.DataServiceImpl - Storing message on topic :$EDC/#account-name/#client-id/MQTT/APPS, priority: 0
23:49:45,290 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.d.DataServiceImpl - Stored message on topic :$EDC/#account-name/#client-id/MQTT/APPS, priority: 0
23:49:45,292 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - component.name: com.eurotech.example.serial.SerialExample
23:49:45,292 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - subscribe.metric.name: message
23:49:45,292 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.baudrate: 9600
23:49:45,292 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.device: /dev/ttyUSB10
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - objectClass: [Ljava.lang.String;@19a67a3
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.parity: none
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - subscribe.semanticTopic: forward
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - publish.semanticTopic: data
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.stop-bits: 1
23:49:45,293 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - service.pid: com.eurotech.example.serial.SerialExample
23:49:45,294 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.data-bits: 8
23:49:45,294 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - component.id: 32
23:49:45,294 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - publish.metric.name: message
23:49:45,294 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - publish.qos: 0
23:49:45,295 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - publish.retain: false
23:49:45,317 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - /dev/ttyUSB10 open
23:49:45,319 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Activating SerialExample... Done.
23:49:45,336 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurableComponentTracker - Adding ConfigurableComponent com.eurotech.example.serial.SerialExample
23:49:45,337 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registration of ConfigurableComponent com.eurotech.example.serial.SerialExample by org.eclipse.kura.core.configuration.ConfigurationServiceImpl@d64869...
23:49:45,347 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registering com.eurotech.example.serial.SerialExample with ocd: org.eclipse.kura.core.configuration.metatype.Tocd@1f9d1a0 ...
23:49:45,348 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registration Completed for Component com.eurotech.example.serial.SerialExample.
Send Messages to the Cloud
With the newly upgraded bundle installed, you can now re-establish the minicom session to see data flow to and from the minicom serial device. As before, minicom will send data from [device_node_2] to [device_node_1] through the null modem cable, which will be processed by the newly upgraded ESF bundle, and then data will be published to the Everyware Cloud.
Open and configure minicom to [device_node_2] as in the first part of this tutorial. Type some characters and press ENTER. Next, open a web browser and go to the Everyware Cloud Console. Select Data by Asset (or Data by Topic, if you prefer) and query the data for your device on the topic EXAMPLE_SERIAL_PUBLISHER/data. You should see that the data you typed in the minicom session has appeared in the Everyware Cloud data store. The following screen capture shows some data from the minicom serial device including a hand-typed message (“Hello from cloud enabled serial example”), as well as the minicom initialization string previously mentioned.
If this were a real serial device that continuously streams data, all of that data would be appearing in the Everyware Cloud as new data messages. Be aware that this simple example is only intended for illustration. As you design your application, be aware of data costs and design the application accordingly.
Receive Messages from the Cloud
The updated bundle includes the ability for the device to receive data from the Everyware Cloud. However, this cannot be done from within the Everyware Cloud Console. Instead, you can do this using a curl command and a simple XML file. To post an XML file to the REST API of Everyware Cloud, you will need a computer that has the curl command installed (this could be done from the Linux command line on the target device itself, if necessary). For more information, see the REST API documentation available on the Everyware Cloud Developers' Guide and Everyware Cloud API.
Create a file named “message.xml” that contains the simple XML content that follows. Replace the following two parameters with the account name of your Everyware Cloud account and the asset ID of your target device:
-
[account_name]
-
[asset_id]
<?xml version="1.0" encoding="UTF-8"?>
<message xmlns="http://eurotech.com/edc/2.0">
<topic>$EDC/[account_name]/[asset_id]/EXAMPLE_SERIAL_PUBLISHER/forward</topic>
<receivedOn></receivedOn>
<payload>
<sentOn>1376608860</sentOn>
<metrics>
<metric>
<name>message</name>
<type>String</type>
<value>hello</value>
</metric>
</metrics>
<body></body>
</payload>
</message>
Now from the same directory, run the following curl command as shown below. Replace the following two parameters with a valid username and password of your Everyware Cloud account:
-
user_name - Everyware Cloud account username. The user must have at least the following permissions: account:view, broker:connect, and data:manage.
-
password - Everyware Cloud account password. Use the backslash character \ before symbols in the password to ensure they are escaped.
If you have a production account, the URL will be https://api.everyware-cloud.com/v2/messages/publish instead of https://api-sandbox.everyware-cloud.com/v2/messages/publish.
curl -v -k -X POST -H "Content-Type: application/xml" --data "@message.xml" -u [user_name]:[password] https://api-sandbox.everyware-cloud.com/v2/messages/publish`
After running this command, you should see the content of the message (in this case "hello") displayed on the minicom session that you have open on the embedded gateway.
Once you are done, you can exit minicom by pressing ctrl-a and then q and finally ENTER. Doing so brings you back to the Linux command prompt.
This completes the optional second part of this tutorial, in which ESF has published data from a serial device to the Everyware Cloud, and also sent data to a local serial device that was received from the Everyware Cloud.
Updated 11 months ago