Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
DeviceClient.cpp
- Committer:
- lamell
- Date:
- 2020-03-10
- Revision:
- 23:1523bdaba8c8
- Parent:
- 19:85e9bc1a3a6a
- Child:
- 25:f4727705353b
File content as of revision 23:1523bdaba8c8:
/*******************************************************************************
* 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 "mbed.h"
#include "MQTTClient.h"
#include "DeviceClient.h"
//Addedd the use of mutexes and semaphores
// need a wrapper since K64F and LPC1768 wont have the same name for mii read methods
#if defined(TARGET_UBLOX_C027) || defined(TARGET_K64F) || defined(TARGET_DISCO_F746NG)
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 = (char*)QUICKSTART;
} else {
organizationName = this->org;
}
// Check if device type is already mentioned
if(this->deviceType == NULL || (strcmp("", this->deviceType) == 0))
{
typeId = (char*)"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();
//NetworkInterface* net = mqttNetwork->network;
EthernetInterface* net=mqttNetwork->net;
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 = (char*)"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();
//NetworkInterface* net = mqttNetwork->network;
EthernetInterface* net = mqttNetwork->net;
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){
//Here's my modification. If not connected, RESET the board.
NVIC_SystemReset();
return false;
} else {
thread_sleep_for(timeout);
}
} while(true);
}
//void DeviceClient::logData(NetworkInterface* net, char *hostname, char *clientId)
void DeviceClient::logData(EthernetInterface* 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: Length: %ul. Payload: %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();
//NetworkInterface* net = mqttNetwork->network;
EthernetInterface* net = mqttNetwork->net;
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();
//NetworkInterface* net = mqttNetwork->network;
EthernetInterface* net = mqttNetwork->net;
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;
}
char* DeviceClient::ipaddress() {
//char iplocal[25];
//NetworkInterface* net = mqttNetwork->getEth();
//NetworkInterface* net = mqttNetwork->network;
EthernetInterface* net = mqttNetwork->net;
//const char* ip = net->get_ip_address();
//strcpy(iplocal,ip);
//return iplocal;
return (char*)net->get_ip_address();
}
//NetworkInterface* DeviceClient::eth() {
// NetworkInterface* net = mqttNetwork->getEth();
// return (NetworkInterface*)net;
//}