A library that simplifies interactions with the IBM Internet of Things Foundation

Dependencies:   MQTT

Dependents:   IBMIoTClientLibrarySamplefinal IBMWIoTPSecureClientSample IBMIoTClientLibrarySample

Introduction

Use the mBed C++ client library to easily connect mBed devices, such as LPC1768 or FRDM-K64F, to the IBM Watson™ IoT Platform service.

Although the library uses C++, it still avoids dynamic memory allocations and the use of STL functions, because the mBed devices sometimes have idiosyncratic memory models that make porting difficult. In any case, the library allows you to make memory use as predictable as possible.

Dependencies

DependencyDescription
Eclipse Paho MQTT libraryProvides an MQTT client library for mBed devices. For more information, see Embedded MQTT C/C++ client libraries
mbed-osARM mbed OS

How to use the library

Use the mBed compiler to create your applications when you use the mBed C++ IBMIoTF client library. The mBed compiler provides a lightweight online C/C++ IDE that is configured for writing, compiling, and downloading programs to run on your mBed microcontroller.

Note: You don't have to install or set up anything to get running with mBed.

For information on how to connect an ARM mBed NXP LPC 1768 microcontroller to the Watson IoT Platform, see the mBed C++ client library for IBM Watson IoT Platform recipe.

Supported Features

FeatureSupported?
Device connectivityYes
Event publishYes
Command receiptYes
SSL/TLSYes
Device ManagementNo

License

This library is shipped under License Eclipse Public License - v 1.0 and Eclipse Distribution License v1.0

For more information about the Eclipse Public License - v 1.0, refer this page. Also, for more information about the Eclipse Distribution License v1.0, refer this page.

Contributor License Agreement

Version 1.0.0 January 14, 2014

In order for You (as defined below) to make intellectual property Contributions (as defined below) now or in the future to IBM repositories, You must agree to this Contributor License Agreement ("CLA").

Please read this CLA carefully before accepting its terms. By accepting the CLA, You are agreeing to be bound by its terms. If You submit a Pull Request against this repository You must include in the Pull Request a statement of Your acceptance of this CLA.

As used in this CLA: (i) "You" (or "Your") shall mean the entity that is making this Agreement with IBM; (ii)"Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is submitted by You to IBM for inclusion in, or documentation of, any of the IBM repositories; (iii) "Submit" (or "Submitted") means any form of communication sent to IBM (e.g. the content You post in the Issue or submit as part of a Pull Request).

This agreement applies to all Contributions You Submit.

This CLA, and the license(s) associated with the particular IBM repositories You are contributing to, provides a license to Your Contributions to IBM and downstream consumers, but You still own Your Contributions, and except for the licenses provided for in this CLA, You reserve all right, title and interest in Your Contributions.

IBM requires that each Contribution You Submit now or in the future comply with the following four commitments.

1) You will only Submit Contributions where You have authored 100% of the content. 2) You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions. 3) Whatever content You Contribute will be provided under the license(s) associated with the particular IBM repository You are contributing to. 4) You understand and agree that IBM repositories and Your contributions are public, and that a record of the contribution (including all personal information You submit with it) is maintained indefinitely and may be redistributed consistent with the license(s) involved. You will promptly notify the Eclipse Foundation if You become aware of any facts or circumstances that would make these commitments inaccurate in any way. To do so, please create an Issue in the appropriate repository.

DeviceClient.cpp

Committer:
lokeshhk
Date:
2017-05-30
Revision:
6:5f037f24ce29
Parent:
5:ea9f483e0294

File content as of revision 6:5f037f24ce29:

/*******************************************************************************
 * Copyright (c) 2015 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Sathisumar Palaniappan - initial implementation
 *    Sathisumar Palaniappan - added reconnect logic and isConnected() method
 *    Lokesh K Haralakatta - Port to mbed OS 5 support
 *    Lokesh K Haralakatta - Added SSL/TLS Support
 *******************************************************************************/
#include "MQTTClient.h"
#include "DeviceClient.h"

// need a wrapper since K64F and LPC1768 wont have the same name for mii read methods
#if defined(TARGET_UBLOX_C027) || defined(TARGET_K64F)

static uint32_t linkStatus(void)
{
    return (1);
}
#elif defined(TARGET_LPC1768)
#include "lpc_phy.h"

static uint32_t linkStatus(void)
{
   return (lpc_mii_read_data() & 1);
}
#endif
using namespace IoTF;

CommandHandler handler = NULL;
void msgArrived(MQTT::MessageData& md);

/**
 * A client, used by device, that handles connections with the IBM Internet of Things Foundation.
 * This class allows device to publish events and receive commands to/from IBM IoT Foundation wtih simple function calls.
 */
DeviceClient::DeviceClient():org(NULL),deviceType(NULL),deviceId(NULL),
        authMethod(NULL),authToken(NULL),mqttNetwork(NULL),mqttClient(NULL),connected(false),port(0)
{
    LOG("Constructor#1 called::\r\n");
}

DeviceClient::DeviceClient(char *orgId, char *typeId, char *id, int port):org(orgId),deviceType(typeId),
        deviceId(id),authMethod(NULL),authToken(NULL),connected(false), port(port)
{
    LOG("Constructor#2 called:: org=%s, type=%s, id=%s\r\n", (org==NULL)?"NULL":org,
                    (deviceType==NULL)?"NULL":deviceType, (deviceId==NULL)?"NULL":deviceId);

    if(strcmp(this->org, QUICKSTART) != 0) {
        WARN("Registered flow must provide valid token\r\n");
    }

    mqttNetwork = new MQTTNetwork();
    mqttClient = new MQTT::Client<MQTTNetwork, Countdown>(*mqttNetwork);

}

DeviceClient::DeviceClient(char *orgId, char *typeId,char *id, char *method, char *token, int port):org(orgId),
        deviceType(typeId),deviceId(id),authMethod(method),authToken(token),connected(false), port(port)
{
    // Don't print token for security reasons
    LOG("Constructor#3 called:: org=%s, type=%s, id=%s\r\n", (org==NULL)?"NULL":org,
                    (deviceType==NULL)?"NULL":deviceType, (deviceId==NULL)?"NULL":deviceId);

    mqttNetwork = new MQTTNetwork();
    mqttClient = new MQTT::Client<MQTTNetwork, Countdown>(*mqttNetwork);
}

/**
 * Connect to the IBM Internet of Things Foundation
 */
bool DeviceClient::connect()
{
    char *organizationName, *typeId, *id;
    bool rc = false;
    // Check if any organization is set
    if(this->org == NULL || (strcmp("", this->org) == 0))
    {
        organizationName = QUICKSTART;
    } else {
        organizationName = this->org;
    }

    // Check if device type is already mentioned
    if(this->deviceType == NULL || (strcmp("", this->deviceType) == 0))
    {
        typeId = "iotsample-mbed";
    } else {
        typeId = this->deviceType;
    }

    char hostname[strlen(organizationName) + strlen(IBM_IOT_MESSAGING) + 1];
    sprintf(hostname, "%s%s", organizationName, IBM_IOT_MESSAGING);

    NetworkInterface* net = mqttNetwork->getEth();
    const char* ip = net->get_ip_address();

    // Get devices MAC address if deviceId is not set already
    if(this->deviceId == NULL || (strcmp("", this->deviceId) == 0))
    {
        char tmpBuf[50];
        id = getMac(tmpBuf, sizeof(tmpBuf));
    } else {
        id = this->deviceId;
    }

    // Construct clientId - d:org:type:id
    char clientId[strlen(organizationName) + strlen(typeId) + strlen(id) + 5];
    sprintf(clientId, "d:%s:%s:%s", organizationName, typeId, id);

    // Initialize MQTT Connect
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    data.MQTTVersion = 4;
    data.clientID.cstring = clientId;

    int quickstartMode = (strcmp(organizationName, QUICKSTART) == 0);

    if (!quickstartMode)
    {
        data.username.cstring = "use-token-auth";
        data.password.cstring = this->authToken;

        //Check and initialize appropriate port
        if(port == 1883)
            port = MQTT_TLS_PORT;
    }

    logData(net, hostname, clientId);

    if(ip){
       rc = tryConnect(hostname, data);
       // By default subscribe to commands if we are in registered flow
       if(rc == true && !quickstartMode)
       {
           subscribeToCommands();
       }
       if(rc == true)
       {
          connected = true;
          LOG("Device Client Connected to %s:%d\r\n",hostname,port);
        }
    }
    else
       LOG("No IP Assigned to Network Interface...\r\n");

    return rc;
}

/**
 * Reconnect when the connection is lost. This method disconnects the active connection if any
 * and tries to initiate a fresh connection.
 * This method uses the Ethernet Link status wherever applicable while reconnecting. i.e, tries to
 * initiate the connection only when the Ethernet cable is plugged in.
 */
bool DeviceClient::reConnect()
{
    LOG("DeviceClient::reConnect() entry and connected = %s\r\n",(connected == true)?"true":"false");
    if(connected == true)
    {
        disconnect();
    }

    if(linkStatus())
    {
        NetworkInterface* net = mqttNetwork->getEth();
        if(net->connect() == 0)
        {
            bool status = connect();
            if(status == false)
            {
                net->disconnect();
            }
            return status;
        }
    }
    return false;
}

bool DeviceClient::tryConnect(char *hostname, MQTTPacket_connectData &data)
{
    int rc = -1;
    int retryAttempt = 0;
    do {
        rc = mqttNetwork->connect(hostname, port);
        if (rc != 0)
        {
            WARN("mqttNetwork connect returned: %d\r\n", rc);
        }

        // MQTT connect
        if (rc == 0 && (rc = mqttClient->connect(data)) != 0)
        {
            WARN("MQTT connect returned %d\r\n", rc);
            if (rc == MQTT_NOT_AUTHORIZED || rc == MQTT_BAD_USERNAME_OR_PASSWORD)
                return false; // don't reattempt to connect if credentials are wrong
        } else if (rc == MQTT_CONNECTION_ACCEPTED) {
            return true;
        }

        int timeout = getConnTimeout(++retryAttempt);

        WARN("Retry attempt number %d waiting %d\r\n", retryAttempt, timeout);

        // enough retry is done - return to application
        if (retryAttempt == 5)
            return false;
        else
            wait(timeout);
    } while(true);
}

void DeviceClient::logData(NetworkInterface* net, char *hostname, char *clientId)
{
    // Network debug statements
    LOG("=====================================\r\n");
    LOG("Connection Config Details:\r\n");
    LOG("IP ADDRESS: %s\r\n", net->get_ip_address());
    LOG("MAC ADDRESS: %s\r\n", net->get_mac_address());
    LOG("Gateway: %s\r\n", net->get_gateway());
    LOG("Network Mask: %s\r\n", net->get_netmask());
    LOG("Server Hostname: %s\r\n", hostname);
    LOG("Server Port: %d\r\n", port);
    LOG("Client ID: %s\r\n", clientId);
    LOG("=====================================\r\n");
}

int DeviceClient::getConnTimeout(int attemptNumber)
{
    // Try to increase the timeout every time
    return (attemptNumber * attemptNumber * 5);
}

/**
 * Returns the connection status, connected or disconnected.
 */
bool DeviceClient::isConnected() {
    return mqttClient->isConnected();
}

/**
 * Publish data to the IBM Internet of Things Foundation. Note that data is published
 * by default at Quality of Service (QoS) 0, which means that a successful send
 * does not guarantee receipt even if the publish has been successful.
 */
bool DeviceClient::publishEvent(char *eventName, char *data, MQTT::QoS qos)
{
    if(!mqttClient->isConnected())
    {
        WARN("Client is not connected \r\n");
        return false;
    }

    MQTT::Message message;
    /* Topic format must be iot-2/evt/<eventName>/fmt/json (let us stick to json format for now)
     *
     * So length must be 10 + strlen(eventName) + 9 + 1
     * iot-2/evt/ = 10
     * /fmt/json = 9
     * NULL char = 1
     */

    char topic[10 + strlen(eventName) + 9 + 1];
    sprintf(topic, "%s%s%s", "iot-2/evt/", eventName, "/fmt/json");

    message.qos = qos;
    message.retained = false;
    message.dup = false;
    message.payload = (void*)data;
    message.payloadlen = strlen(data);

    LOG("Publishing %s\r\n", data);
    int rc = mqttClient->publish(topic, message);
    return rc == 0;
}

void DeviceClient::setCommandCallback(CommandHandler callbackFunc)
{
    handler = callbackFunc;
}
/**
 * Subscribe to commands from the application. This will be executed only for
 * registered flow (quickstart flow does not support command publish)
 */
int DeviceClient::subscribeToCommands()
{
    int rc = 0;
    // iot-2/cmd/+/fmt/+
    if ((rc = mqttClient->subscribe("iot-2/cmd/+/fmt/+", MQTT::QOS2, msgArrived)) != 0)
        WARN("rc from MQTT subscribe is %d\r\n", rc);
    return rc;
}

/**
 * Callback method to be registered with MQTT::Client. MQTT::Client calls whenever
 * any command is published to the topic subscribed earlier.
 */
void msgArrived(MQTT::MessageData& md)
{
    // check whether callback is registered by the client code
    if(handler == NULL)
    {
        return;
    }

    MQTT::Message &message = md.message;
    char topic[md.topicName.lenstring.len + 1];

    sprintf(topic, "%.*s", md.topicName.lenstring.len, md.topicName.lenstring.data);

    LOG("Message arrived on topic %s: %.*s\r\n",  topic, message.payloadlen, message.payload);

    // Command topic: iot-2/cmd/blink/fmt/json - cmd is the string between cmd/ and /fmt/
    char* start = strstr(topic, "/cmd/") + 5;
    int len = strstr(topic, "/fmt/") - start;

    char name[len + 1];

    memcpy(name, start, len);
    name[len] = NULL;

    start = strstr(topic, "/fmt/") + 5;

    char format[20];  // ToDO: need to find the length of the format
    strcpy(format, start);

    char payload[message.payloadlen + 1];
    sprintf(payload, "%.*s", message.payloadlen, (char*)message.payload);

    IoTF::Command cmd(name, format, payload);
    (*handler)(cmd);
}

/**
 * Disconnects the connection in order.
 */
bool DeviceClient::disconnect()
{
    int rc = 0;
    if(mqttClient->isConnected())
    {
        rc = mqttClient->disconnect();
    }

    NetworkInterface* net = mqttNetwork->getEth();
    mqttNetwork->disconnect();
    net->disconnect();
    connected = false;
    return rc == 0;
}

// Yield to allow MQTT client to process the command
void DeviceClient::yield(int ms)
{
    if(mqttClient->isConnected())
    {
        mqttClient->yield(ms);
    }
}

// Obtain DeviceId address
char* DeviceClient::getDeviceId(char* buf, int buflen)
{
    if(this->deviceId == NULL || (strcmp("", this->deviceId) == 0))
    {
        return getMac(buf, buflen);
    } else {
        return strncpy(buf, this->deviceId, buflen);
    }
}
// Obtain MAC address
char* DeviceClient::getMac(char* buf, int buflen)
{
    NetworkInterface* net = mqttNetwork->getEth();
    strncpy(buf, net->get_mac_address(), buflen);

    char* pos;                                                 // Remove colons from mac address
    while ((pos = strchr(buf, ':')) != NULL)
        memmove(pos, pos + 1, strlen(pos) + 1);
    return buf;
}