All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.iotf.client.gateway.GatewayClient Maven / Gradle / Ivy

Go to download

IBM Watson IoT client library to simplify device/gateway/application interactions with the IoT Platform

The newest version!
/**
 *****************************************************************************
 * Copyright (c) 2016 IBM Corporation and other Contributors.

 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Sathiskumar Palaniappan - Initial Contribution
 *****************************************************************************
 */

package com.ibm.iotf.client.gateway;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.ibm.iotf.client.AbstractClient;
import com.ibm.iotf.client.api.APIClient;
import com.ibm.iotf.util.LoggerUtility;

/**
 * A client, used by Gateway, that simplifies the Gateway interactions with IBM Watson IoT Platform. 
* *

Gateways are a specialized class of devices in Watson IoT Platform which serve as access points to the * Watson IoT Platform for other devices. Gateway devices have additional permission when compared to * regular devices and can perform the following functions:

* *
    *
  • Register new devices to Watson IoT Platform *
  • Send and receive its own sensor data like a directly connected device, *
  • Send and receive data on behalf of the devices connected to it *
  • Run a device management agent, so that it can be managed, also manage the devices connected to it *
* *

Refer to the documentation for more information about the * Gateway support in Watson IoT Platform.

* * This is a derived class from AbstractClient. */ public class GatewayClient extends AbstractClient implements MqttCallbackExtended{ private static final String CLASS_NAME = GatewayClient.class.getName(); private static final Pattern GATEWAY_NOTIFICATION_PATTERN = Pattern.compile("iot-2/type/(.+)/id/(.+)/notify"); private static final Pattern GATEWAY_COMMAND_PATTERN = Pattern.compile("iot-2/type/(.+)/id/(.+)/cmd/(.+)/fmt/(.+)"); private GatewayCallback gwCommandCallback = null; private HashMap subscriptions = new HashMap(); private APIClient apiClient = null; /** *

Create a Gateway client for the IBM Watson IoT Platform using the properties file passed. The * properties must have the following definitions,

* *
    *
  • org - Your organization ID. *
  • type - The type of your Gateway device. *
  • id - The ID of your Gateway. *
  • auth-method - Method of authentication (The only value currently supported is "token"). *
  • auth-token - API key token. *
* @param options The Properties object creates definitions which are used to interact * with the Watson Internet of Things Platform module. * * @throws Exception Failure in parsing the properties passed */ public GatewayClient(Properties options) throws Exception { super(options); if(getOrgId()==null){ throw new Exception("Invalid Auth Key"); } else if(getOrgId().equalsIgnoreCase("quickstart")) { throw new Exception("There is no quickstart support for Gateways"); } this.clientId = "g" + CLIENT_ID_DELIMITER + getOrgId() + CLIENT_ID_DELIMITER + this.getGWDeviceType() + CLIENT_ID_DELIMITER + getGWDeviceId(); if (getAuthMethod() == null) { this.clientUsername = null; this.clientPassword = null; } else if (!getAuthMethod().equals("token")) { throw new Exception("Unsupported Authentication Method: " + getAuthMethod()); } else { // use-token-auth is the only authentication method currently supported this.clientUsername = "use-token-auth"; this.clientPassword = getAuthToken(); } createClient(this); options.setProperty("auth-method", "gateway"); this.apiClient = new APIClient(options); } /** * This constructor allows external user to pass the existing MqttAsyncClient * @param mqttAsyncClient MqttAsyncClient with the Watson IoT Platform connectivity details */ protected GatewayClient(MqttAsyncClient mqttAsyncClient) { super(mqttAsyncClient); } /** * This constructor allows external user to pass the existing MqttClient * @param mqttClient MqttClient with the Watson IoT Platform connectivity details */ protected GatewayClient(MqttClient mqttClient) { super(mqttClient); } /** * Returns the {@link com.ibm.iotf.client.api.APIClient} that allows the users to interact with * Watson IoT Platform API's to perform one or more operations like, registering a device, * getting the list of devices connected through the Gateway and etc.. * * @return APIClient */ public APIClient api() { return this.apiClient; } /** * Returns the IBM Watson IoT Platform Organization ID for this client. * * @return orgid Organization ID */ public String getOrgId() { // Check if org id is provided by the user String orgid = super.getOrgId(); if(orgid == null || orgid.equals("")) { String authKeyPassed = getAuthKey(); if(authKeyPassed != null && ! authKeyPassed.trim().equals("") && ! authKeyPassed.equals("quickstart")) { if(authKeyPassed.length() >=8){ return authKeyPassed.substring(2, 8);} else { return null; } } else { return "quickstart"; } } return orgid; } /* * old style - id * new style - Device-ID */ public String getGWDeviceId() { String id; id = options.getProperty("Gateway-ID"); if(id == null) { return super.getDeviceId(); } return trimedValue(id); } /** * Returns the Gateway ID */ public String getDeviceId() { return this.getGWDeviceId(); } public String getGWDeviceType() { String type; type = options.getProperty("Gateway-Type"); if(type == null) { type = options.getProperty("type"); } if(type == null) { type = options.getProperty("Device-Type"); } return trimedValue(type); } /** * Accessor method to retrieve auth key * @return authKey * String authKey */ private String getAuthKey() { String authKeyPassed = options.getProperty("auth-key"); if(authKeyPassed == null) { authKeyPassed = options.getProperty("API-Key"); } return trimedValue(authKeyPassed); } /** *

Connects the Gateway to IBM Watson Internet of Things Platform. * After the successful connection to the IBM Watson IoT Platform, * the Gateway client can perform the following operations,

* *
    *
  • Publish events for itself and on behalf of devices connected behind the Gateway. *
  • Subscribe to commands for itself and on behalf of devices behind the Gateway. *
* * *

The GatewayClient retries when there is a connect exception.
* * This method does not retry when the following exceptions occur.

* *
    *
  • MqttSecurityException - One or more credentials are wrong *
  • UnKnownHostException - Host doesn't exist. For example, a wrong organization name is used to connect. *
* * @throws MqttException see above **/ public void connect() throws MqttException { super.connect(true); subscribeToGatewayCommands(); } /** *

Connects the Gateway to IBM Watson Internet of Things Platform. * After the successful connection to the IBM Watson IoT Platform, * the Gateway client can perform the following operations,

* *
    *
  • Publish events for itself and on behalf of devices connected behind the Gateway. *
  • Subscribe to commands for itself and on behalf of devices behind the Gateway. *
* *

The GatewayClient retries when there is a connect exception based on the * value set in retry parameter.
* * This method does not retry when the following exceptions occur.

* *
    *
  • MqttSecurityException - One or more credentials are wrong *
  • UnKnownHostException - Host doesn't exist. For example, a wrong organization name is used to connect. *
* * @param autoRetry - tells whether to retry the connection when the connection attempt fails. * @throws MqttException see above **/ public void connect(boolean autoRetry) throws MqttException { super.connect(autoRetry); subscribeToGatewayCommands(); } /** *

Connects the Gateway to IBM Watson Internet of Things Platform. * After the successful connection to the IBM Watson IoT Platform, * the Gateway client can perform the following operations,

* *
    *
  • Publish events for itself and on behalf of devices connected behind the Gateway. *
  • Subscribe to commands for itself and on behalf of devices behind the Gateway. *
* *

The GatewayClient retries when there is a connect exception based on the * value set in retry parameter.
* * This method does not retry when the following exceptions occur.

* *
    *
  • MqttSecurityException - One or more credentials are wrong *
  • UnKnownHostException - Host doesn't exist. For example, a wrong organization name is used to connect. *
* * @param numberOfRetryAttempts - How many number of times to retry when there is a failure in connecting to Watson * IoT Platform. * @throws MqttException see above **/ @Override public void connect(int numberOfRetryAttempts) throws MqttException { super.connect(numberOfRetryAttempts); subscribeToGatewayCommands(); } /** *

While Gateway publishes events on behalf of the devices connected behind, * the Gateway can publish its own events as well. This method publishes the event with the * specified name and specified QOS.

* *

Note that data is published at Quality of Service (QoS) 0, which means that * a successful send does not guarantee receipt even if the publish has been successful.

* * @param event * Name of the dataset under which to publish the data * @param data * Object to be added to the payload as the dataset * @return Whether the send was successful. */ public boolean publishGatewayEvent(String event, Object data) { return publishDeviceEvent(this.getGWDeviceType(), this.getGWDeviceId(), event, data, 0); } /** *

While Gateway publishes events on behalf of the devices connected to it, * the Gateway can publish its own events as well. This method publishes event with the * specified name and specified QOS.

* * This method allows QoS to be passed as an argument * * @param event * Name of the dataset under which to publish the data * @param data * Object to be added to the payload as the dataset * @param qos * Quality of Service - should be 0, 1 or 2 * @return Whether the send was successful. */ public boolean publishGatewayEvent(String event, Object data, int qos) { return publishDeviceEvent(this.getGWDeviceType(), this.getGWDeviceId(), event, data, qos); } /** *

While Gateway publishes events on behalf of the devices connected to it, * the Gateway can publish its own events as well. This method publishes event with the * specified name and specified QOS.

* * This method allows QoS to be passed as an argument * * @param event * Name of the dataset under which to publish the data * @param data * Object to be added to the payload as the dataset * @param format the format of the event * @param qos * Quality of Service - should be 0, 1 or 2 * @return Whether the send was successful. * @throws Exception when the publish operation fails */ public boolean publishGatewayEvent(String event, Object data, String format, int qos) throws Exception { return publishDeviceEvent(this.getGWDeviceType(), this.getGWDeviceId(), event, data, format, qos); } /** *

Publish the event on behalf of a device to the IBM Watson IoT Platform.

* Note that data is published at Quality of Service (QoS) 0, which means that a successful send does not guarantee * receipt even if the publish is successful. * * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param event * object of String which denotes event * @param data * Payload data * @return Whether the send was successful. */ public boolean publishDeviceEvent(String deviceType, String deviceId, String event, Object data) { return publishDeviceEvent(deviceType, deviceId, event, data, 0); } /** * Publish an event on the behalf of a device to the IBM Watson IoT Platform.
* * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param event * object of String which denotes event * @param data * Payload data * @param qos * Quality of Service, in int - can have values 0,1,2 * @return Whether the send was successful. */ public boolean publishDeviceEvent(String deviceType, String deviceId, String event, Object data, int qos) { if (!isConnected() && !isAutomaticReconnect()) { return false; } final String METHOD = "publishEvent(5)"; Object payload = null; String topic = "iot-2/type/" + deviceType + "/id/" + deviceId + "/evt/" + event + "/fmt/json"; // Handle null object if(data == null) { data = new JsonObject(); } if(newFormat == false) { payload = new JsonObject(); String timestamp = ISO8601_DATE_FORMAT.format(new Date()); ((JsonObject) payload).addProperty("ts", timestamp); JsonElement dataElement = gson.toJsonTree(data); ((JsonObject) payload).add("d", dataElement); } else { payload = gson.toJsonTree(data); } LoggerUtility.fine(CLASS_NAME, METHOD, "Topic = " + topic); LoggerUtility.fine(CLASS_NAME, METHOD, "Payload = " + payload.toString()); MqttMessage msg = new MqttMessage(payload.toString().getBytes(Charset.forName("UTF-8"))); msg.setQos(qos); msg.setRetained(false); try { if (isConnected() && !isAutomaticReconnect()) { mqttAsyncClient.publish(topic, msg).waitForCompletion(); } else { mqttAsyncClient.publish(topic, msg); } } catch (MqttPersistenceException e) { e.printStackTrace(); return false; } catch (MqttException e) { e.printStackTrace(); return false; } return true; } /** * Publish event, on the behalf of a device, to the IBM Watson IoT Platform.
* * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param event * object of String which denotes event * @param data * Payload data * @param format * The message format * @param qos * Quality of Service, in int - can have values 0,1,2 * @return Whether the send was successful. * @throws Exception when the publish operation fails */ public boolean publishDeviceEvent(String deviceType, String deviceId, String event, Object data, String format, int qos) throws Exception { if (!isConnected()) { return false; } final String METHOD = "publishEvent(6)"; String topic = "iot-2/type/" + deviceType + "/id/" + deviceId + "/evt/" + event + "/fmt/" + format; Object payload = null; MqttMessage msg = null; // Handle null object if(data == null) { data = new JsonObject(); } if(data.getClass() == String.class) { payload = data; msg = new MqttMessage(payload.toString().getBytes(Charset.forName("UTF-8"))); } else if(data.getClass().getName().equals("[B")) { // checking for byte array msg = new MqttMessage((byte[]) data); payload = Arrays.toString((byte[]) data); } else { payload = gson.toJsonTree(data); msg = new MqttMessage(payload.toString().getBytes(Charset.forName("UTF-8"))); } LoggerUtility.fine(CLASS_NAME, METHOD, "Topic = " + topic); LoggerUtility.fine(CLASS_NAME, METHOD, "Payload = " + payload.toString()); msg.setQos(qos); msg.setRetained(false); try { if (isConnected() && !isAutomaticReconnect()) { mqttAsyncClient.publish(topic, msg).waitForCompletion(); } else { mqttAsyncClient.publish(topic, msg); } } catch (MqttPersistenceException e) { e.printStackTrace(); return false; } catch (MqttException e) { e.printStackTrace(); return false; } return true; } /* * This method reconnects when the connection is lost due to n/w interruption */ protected void reconnect() { try { super.connect(true); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } subscribeToGatewayCommands(); } private void subscribeToGatewayCommands() { subscribeToDeviceCommands(this.getGWDeviceType(), this.getGWDeviceId()); } /** *

Subscribe to device commands, on the behalf of a device, to the IBM Watson IoT Platform.
* Note that the, Quality of Service is set to 0.

* This method subscribes to all commands, for a given device type and device id. * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId */ public void subscribeToDeviceCommands(String deviceType, String deviceId) { subscribeToDeviceCommands(deviceType, deviceId, "+", 0); } /** * Unsubscribe from device commands, on the behalf of a device, from the IBM Watson IoT Platform. * * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId */ public void unsubscribeFromDeviceCommands(String deviceType, String deviceId) { unsubscribeFromDeviceCommands(deviceType, deviceId, "+"); } /** * Subscribe to device commands, on the behalf of a device, to the IBM Watson IoT Platform.
* Quality of Service is set to 0. * * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes command */ public void subscribeToDeviceCommands(String deviceType, String deviceId, String command) { subscribeToDeviceCommands(deviceType, deviceId, command, 0); } /** * Subscribe to device commands, on the behalf of a device, to the IBM Watson IoT Platform.
* * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes command * @param qos * Quality of Service, in int - can have values 0,1,2 */ public void subscribeToDeviceCommands(String deviceType, String deviceId, String command, int qos) { try { String newTopic = "iot-2/type/"+deviceType+"/id/"+deviceId+"/cmd/" + command + "/fmt/+"; subscriptions.put(newTopic, new Integer(qos)); mqttAsyncClient.subscribe(newTopic, qos); } catch (MqttException e) { e.printStackTrace(); } } /** * Unsubscribe from device commands, on the behalf of a device, from the IBM Watson IoT Platform. * * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes the command name */ public void unsubscribeFromDeviceCommands(String deviceType, String deviceId, String command) { try { String newTopic = "iot-2/type/"+deviceType+"/id/"+deviceId+"/cmd/" + command + "/fmt/+"; subscriptions.remove(newTopic); mqttAsyncClient.unsubscribe(newTopic); } catch (MqttException e) { e.printStackTrace(); } } public void subscribeToGatewayNotification() { String newTopic = "iot-2/type/"+this.getGWDeviceType() +"/id/" +this.getGWDeviceId() + "/notify"; subscriptions.put(newTopic, 0); try { mqttAsyncClient.subscribe(newTopic, 0); } catch (MqttException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Subscribe to device commands, on the behalf of a device, to the IBM Watson IoT Platform.
* Quality of Service is set to 0 * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes command * @param format * object of String which denotes format, typical example of format could be json */ public void subscribeToDeviceCommands(String deviceType, String deviceId, String command, String format) { try { String newTopic = "iot-2/type/"+deviceType+"/id/"+deviceId+"/cmd/" + command + "/fmt/" + format; subscriptions.put(newTopic, new Integer(0)); mqttAsyncClient.subscribe(newTopic, 0); } catch (MqttException e) { e.printStackTrace(); } } /** * Subscribe to device commands, on the behalf of a device, to the IBM Watson IoT Platform.
* * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes command * @param format * object of String which denotes format, typical example of format could be json * @param qos * Quality of Service, in int - can have values 0,1,2 */ public void subscribeToDeviceCommands(String deviceType, String deviceId, String command, String format, int qos) { try { String newTopic = "iot-2/type/"+deviceType+"/id/"+deviceId+"/cmd/"+ command +"/fmt/" + format; subscriptions.put(newTopic, new Integer(qos)); mqttAsyncClient.subscribe(newTopic, qos); } catch (MqttException e) { e.printStackTrace(); } } /** * Unsubscribe from device commands, on the behalf of a device, from the IBM Watson IoT Platform. * * @param deviceType * object of String which denotes deviceType * @param deviceId * object of String which denotes deviceId * @param command * object of String which denotes command name * @param format * object of String which denotes format, typical example of format could be json */ public void unsubscribeFromDeviceCommands(String deviceType, String deviceId, String command, String format) { try { String newTopic = "iot-2/type/"+deviceType+"/id/"+deviceId+"/cmd/"+ command +"/fmt/" + format; subscriptions.remove(newTopic); mqttAsyncClient.unsubscribe(newTopic); } catch (MqttException e) { e.printStackTrace(); } } /** *

This method is called by the MQTT library when the connection to the * IBM Watson Platform is lost.

* * This Watson IoT library will start the reconnect process, and application doesn't need to worry */ public void connectionLost(Throwable e) { final String METHOD = "connectionLost"; LoggerUtility.info(CLASS_NAME, METHOD, "Connection lost: " + e.getMessage()); try { if (this.isAutomaticReconnect() == false) { connect(); } if (this.isCleanSession() == true) { Iterator> iterator = subscriptions.entrySet().iterator(); LoggerUtility.info(CLASS_NAME, METHOD, "Resubscribing...."); while (iterator.hasNext()) { //Map.Entry pairs = (Map.Entry)iterator.next(); Entry pairs = iterator.next(); LoggerUtility.info(CLASS_NAME, METHOD, pairs.getKey() + " = " + pairs.getValue()); try { mqttAsyncClient.subscribe(pairs.getKey().toString(), Integer.parseInt(pairs.getValue().toString())); } catch (NumberFormatException | MqttException e1) { e1.printStackTrace(); } // iterator.remove(); // avoids a ConcurrentModificationException } } } catch (MqttException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } } /** *

This method is called by the MQTT library when a message is delivered successfully.

* * A completed delivery does not guarantee that the message is received by the service * because devices send messages with Quality of Service (QoS) 0. The message count * represents the number of messages that were sent by the device without an error on * from the perspective of the device. */ public void deliveryComplete(IMqttDeliveryToken token) { final String METHOD = "deliveryComplete"; LoggerUtility.fine(CLASS_NAME, METHOD, "token = "+token.getMessageId()); messageCount++; } /** *

This method is called by the MQTT library when a message(command) is sent by the IBM Watson IoT Platform. *

* The message(command) will be processed by this class and corresponding callback method will be called if * registered. */ public void messageArrived(String topic, MqttMessage msg) throws Exception { final String METHOD = "messageArrived"; if (gwCommandCallback != null) { /* Only check whether the message is a application command if a callback * has been defined for commands, otherwise it is a waste of time * as without a callback there is nothing to process the generated * event. */ Matcher matcher = GATEWAY_COMMAND_PATTERN.matcher(topic); if (matcher.matches()) { String type = matcher.group(1); String id = matcher.group(2); String command = matcher.group(3); String format = matcher.group(4); Command cmd = new Command(type, id, command, format, msg); if(cmd.getTimestamp() != null ) { LoggerUtility.fine(CLASS_NAME, METHOD, "Command received: " + cmd.toString()); gwCommandCallback.processCommand(cmd); } else { LoggerUtility.warn(CLASS_NAME, METHOD, "Command is not formatted properly, so not processing"); } return; } matcher = GATEWAY_NOTIFICATION_PATTERN.matcher(topic); if(matcher.matches()) { String type = matcher.group(1); String id = matcher.group(2); } } } /** *

Register the {@link com.ibm.iotf.client.gateway.GatewayCallback} class to the Gateway, so that the * {@link com.ibm.iotf.client.gateway.GatewayCallback#processCommand(com.ibm.iotf.client.gateway.Command)} method gets called when * command is received for the given subscription.

* *

Also, the * {@link com.ibm.iotf.client.gateway.GatewayCallback#processNotification(com.ibm.iotf.client.gateway.Notification)} method gets called when * any notification is received. Note that you one must have called subscribeToGatewayNotification inorder * to get the notification.

* * The messages are returned as an instance of the {@link com.ibm.iotf.client.gateway.Command}. * * @param callback an instance of {@link com.ibm.iotf.client.gateway.GatewayCallback} */ public void setGatewayCallback(GatewayCallback callback) { this.gwCommandCallback = callback; } @Override public void connectComplete(boolean reconnect, String serverURI) { final String METHOD = "connectComplete"; if (reconnect) { LoggerUtility.info(CLASS_NAME, METHOD, "Reconnected to " + serverURI ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy