· Determine Serial Device Nodes
Cloud Enabled Serial Communication
· Sending Messages to the Cloud
· Receiving Messages from the Cloud
This section provides an example that shows 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
· 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 portion of the tutorial, as an optional exercise, you can “cloud-enable” the serial device by extending the bundle to transmit messages from the serial device to the Everyware Cloud, 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.
· Setting up ESF Development Environment
· Hello World Using the ESF Logger
· Have appropriate hardware available:
o
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.)
o Ensure minicom is installed on the embedded device.
· To communicate serial data through the Everyware Cloud, have your Everyware Cloud account credentials handy. If you do not have an account, contact your Eurotech sales representative.
· See Connecting to Everyware Cloud.
This section of the tutorial covers 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.
Your setup requirements will depend on your hardware platform. At a minimum, you will need two serial ports with a null modem serial cross-over cable connecting them together.
· 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 to your device, you can attach the null modem serial cable between the two ports.
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, the associated device nodes are typically allocated dynamically by Linux at insertion time. In order to determine what they are, run the following command at a terminal on the embedded gateway:
tail -f /var/log/syslog
|
Note: /var/log/syslog may not be the right file depending on your specific Linux implementation. Other potential 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 was allocated a device node of “/dev/ttyUSB10”. Your results may be different, but the important thing is to identify the “tty” device that was allocated. For the rest of this tutorial, this device will be referenced as [device_node_1], which in this example is equal to /dev/ttyUSB10. It is also important to note that these values are dynamic. From one boot to the next and one insertion to the next, these values may change. Keep this in mind as you are developing. 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].
Now that you have two serial ports connected to each other, you are ready to implement the code. You will use the same general method for implementing the bundle as in Hello World Using the ESF Logger, but the actual code in this example will have the following differences from the Hello World example:
· Name the new Plug-in Project “com.eurotech.example.serial” instead of “com.eurotech.example.hello_osgi”
· Create a class named “SerialExample” in the package com.eurtoech.example.serial
· In the Automated Management of Dependencies section of the Dependencies tab of the MANIFEST.MF include the following bundles:
o com.eurotech.framework.api
o slf4j.api
o org.eclipse.osgi
o org.eclipse.osgi.services
o org.eclipse.osgi.util
Now you need to write the source code. Below are the four files that need to be implemented:
· META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and it’s 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
The META-INF/MANIFEST.MF file should look as follows when complete. Also note whitespace is significant in this file. Make sure yours matches this file exactly, except that the Execution Environment may be JavaSE-1.6 or JavaSE-1.7 depending on the Java installation of your device.
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.6 Service-Component: OSGI-INF/*.xml Bundle-ActivationPolicy: lazy Import-Package: com.eurotech.framework.comm, com.eurotech.framework.configuration, 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" Require-Bundle: org.eclipse.equinox.io Bundle-ClassPath: .
|
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>
|
The OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml file should look as follows when complete, with the exception of the “serial.device” parameter. You should set its “default” property to [device_node_1] that you found earlier in this tutorial.
<?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" default="/dev/ttyUSB10" 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>
|
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 com.eurotech.framework.comm.CommConnection; import com.eurotech.framework.comm.CommURI; import com.eurotech.framework.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.
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 install the bundle to the remote target unit. When this installation completes successfully, you should see a message similar to the one below out of /var/log/esf.log, showing the bundle was successfully installed and configured. (You can also run this example in the emulation environment in a Linux or OS X environment, but make sure that your user account has owner permission for the serial device in /dev.)
21:49:22,238 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Activating SerialExample... 21:49:22,239 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - component.name: com.eurotech.example.serial.SerialExample 21:49:22,240 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.baudrate: 9600 21:49:22,240 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.device: /dev/ttyUSB10 21:49:22,240 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - objectClass: [Ljava.lang.String;@1546e3a 21:49:22,240 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.parity: none 21:49:22,241 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.stop-bits: 1 21:49:22,241 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - service.pid: com.eurotech.example.serial.SerialExample 21:49:22,241 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - serial.data-bits: 8 21:49:22,241 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Update - component.id: 28 21:49:22,259 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - /dev/ttyUSB10 open 21:49:22,261 [Component Resolve Thread (Bundle 6)] INFO c.e.e.s.SerialExample - Activating SerialExample... Done. 21:49:22,269 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurableComponentTracker - Adding ConfigurableComponent com.eurotech.example.serial.SerialExample 21:49:22,269 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registration of ConfigurableComponent com.eurotech.example.serial.SerialExample by com.eurotech.framework.core.configuration.ConfigurationServiceImpl@d64869... 21:49:22,279 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registering com.eurotech.example.serial.SerialExample with ocd: com.eurotech.framework.core.configuration.metatype.Tocd@1ed98f8 ... 21:49:22,280 [Component Resolve Thread (Bundle 6)] INFO c.e.f.c.c.ConfigurationServiceImpl - Registration Completed for Component com.eurotech.example.serial.SerialExample.
|
Next, you need to test that your bundle does indeed echo characters back to us. Open minicom and configure it to use [device_node_2] that you determined earlier. Start by opening minicom, using the following command at a Linux terminal on the remote gateway device:
minicom -s
|
This command will open a view similar to the following screen capture.
Scroll down to Serial port setup and press <ENTER>. This step will open a new dialog as shown.
Use the menu options on the left (A, B, C, etc) to change desired fields. Set the fields to the same values as the previous screen capture with the exception that the Serial Device should match the [device_node_2] on your target device. Once you have set this correctly, press <ENTER> to exit out of 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, and minicom is acting as a serial device that can send and receive commands to your ESF bundle. You can try it by typing some characters and pressing <ENTER>. The ESF application code is written such that <ENTER> or specifically a ‘\n’ character signals to the ESF application to echo the previously buffered characters back to the serial device (minicom in our case). The following screen capture shows an example of that interaction.
Note that at 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 listening on the port connected to the other end of the null modem cable.
Once you are done, you can exit minicom by pressing ‘<CTRL> a’ and then ‘q’ and then ‘<ENTER>’. This will bring you back to the Linux command prompt.
This is the end of 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]). The application outputs data back to the same serial port, which is received in minicom. The minicom program is simulating a serial device that can both send and receive data. The ESF application could send and receive binary data rather than ASCII if the device supports it.
The 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. So, the ESF-enabled device will act 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.
You need to make the following changes 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.
The updated META-INF/MANIFEST.MF file should look as follows. Note that whitespace is significant in this file. Make sure yours matches this file exactly, except that the Execution Environment may be JavaSE-1.6 or JavaSE-1.7 depending on the Java installation of your device.
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.6 Service-Component: OSGI-INF/*.xml Bundle-ActivationPolicy: lazy Import-Package: com.eurotech.framework.cloud, com.eurotech.framework.comm, com.eurotech.framework.configuration, com.eurotech.framework.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" Require-Bundle: org.eclipse.equinox.io Bundle-ClassPath: .
|
The updated OSGI-INF/component.xml should look as follows.
<?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="com.eurotech.framework.cloud.CloudService" />
</scr:component>
|
The updated OSGI-INF/metatype/com.eurotech.example.serial.SerialExample.xml file should look as follows when complete, with the exception of the “serial.device” parameter. You should set the “default” parameter to the [device_node_1] for your target device.
<?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" default="/dev/ttyUSB10" 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>
|
The updated com.eurotech.example.serial.SerialExample.java file should look as follows.
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 com.eurotech.framework.cloud.CloudClient; import com.eurotech.framework.cloud.CloudClientListener; import com.eurotech.framework.cloud.CloudService; import com.eurotech.framework.comm.CommConnection; import com.eurotech.framework.comm.CommURI; import com.eurotech.framework.configuration.ConfigurableComponent; import com.eurotech.framework.message.EsfPayload;
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, EsfPayload 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, EsfPayload 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 { EsfPayload esfPayload = new EsfPayload(); esfPayload.setTimestamp(new Date()); esfPayload.addMetric(metricName, dataRead); m_cloudClient.publish(topic, esfPayload, 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.
Follow the mtoolkit instructions for installing a single bundle to install the bundle to the remote target device. When this installation completes successfully, you should see a message similar to the following one out of /var/log/esf.log, showing the bundle was successfully installed and configured. This process should go exactly as in the first part of this tutorial, except you will be prompted that the bundle is already installed and asked if you want to install the new copy. Select yes when prompted.
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 com.eurotech.framework.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: com.eurotech.framework.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.
|
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.
Note 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, and in designing your application you need to be aware of data costs and design the application accordingly.
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. Replace the following two parameters with a valid username and password of your Everyware Cloud account:
· [user_name] (This must be a user with at least the following permissions: account:view, broker:connect, and data:manage.)
· [password] (Use the backslash character \ before symbols in the password to ensure they are “escaped”.)
Note 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’) come out 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 then ‘<ENTER>’. This will bring you back to the Linux command prompt.
This is the end of 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.