From 5ed7afb134ef571e58302c4729f27a1539fff89c Mon Sep 17 00:00:00 2001 From: Misja Heuveling Date: Wed, 4 Nov 2020 19:55:32 +0100 Subject: [PATCH] Changed API to simplify the usage and added example for storing device credentials on SPIFFS --- .../RetrieveAndStoreCredentials.ino | 193 ++++++++++++++++++ examples/SendMeasurement/SendMeasurement.ino | 25 +-- library.properties | 2 +- src/CumulocityClient.cpp | 72 ++++--- src/CumulocityClient.h | 46 +++-- 5 files changed, 272 insertions(+), 66 deletions(-) create mode 100644 examples/RetrieveAndStoreCredentials/RetrieveAndStoreCredentials.ino diff --git a/examples/RetrieveAndStoreCredentials/RetrieveAndStoreCredentials.ino b/examples/RetrieveAndStoreCredentials/RetrieveAndStoreCredentials.ino new file mode 100644 index 0000000..12ce103 --- /dev/null +++ b/examples/RetrieveAndStoreCredentials/RetrieveAndStoreCredentials.ino @@ -0,0 +1,193 @@ +#include +#ifdef ESP8266 + #include +#else //ESP32 + #include +#endif +#include "FS.h" +#include "SPIFFS.h" + +/* You only need to format SPIFFS the first time you run a + test or else use the SPIFFS plugin to create a partition + https://github.com/me-no-dev/arduino-esp32fs-plugin */ +#define FORMAT_SPIFFS_IF_FAILED true +#define CREDENTIALS_FILE "/credentials.txt" + +const char* ssid = "...."; +const char* wifiPassword = "....."; +char* host = "xxx.cumulocity.com"; +char* username = "...."; //bootstrap credentials can be requested through support +char* c8yPassword = "...."; +char* tenant = "...."; +char clientId[20]; +bool storedCredentials = false; + +WiFiClient wifiClient; +CumulocityClient c8yClient(wifiClient, clientId); + +//Serial Number taken from the ESP32, not tested on ESP8266 +void getSerialNumber() { + + uint64_t chipid = ESP.getEfuseMac(); + uint16_t chip = (uint16_t)(chipid >> 32); + + snprintf(clientId, 19, "ESP32-%04X%08X", chip, (uint32_t)chipid); + + Serial.printf("Serial Number is: %s\n", clientId); +} + +//connect to a WiFi access point +void connectWifi() { + WiFi.begin(ssid, wifiPassword); + + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + + } + Serial.println("connected to wifi"); +} + +//read device credentials from the SPIFFS filesystem +void readCredentials(fs::FS &fs, const char *path) { + Serial.printf("Reading file: %s\r\n", path); + + char buffer[4096]; + + File file = fs.open(path, FILE_READ); + if (!file || file.isDirectory()) { + Serial.println("- failed to read the file"); + return; + } + + int length = 0; + while(file.available()){ + char c = file.read(); + buffer[length++] = c; + } + buffer[length] = '\0'; + + tenant = strtok(buffer, "\n"); + Serial.printf("Retrieved tenant: %s\n", tenant); + + username = strtok(NULL, "\n"); + Serial.printf("Retrieved user: %s\n", username); + + c8yPassword = strtok(NULL, "\n"); + + if (tenant != NULL && username != NULL && c8yPassword != NULL) { + Serial.printf("found credentials for %s, %s\n", tenant, username); + storedCredentials = true; + } + + file.close(); +} + +//write data to a file +void writeFile(fs::FS &fs, const char * path, const char * message){ + Serial.printf("Writing file: %s\r\n", path); + + File file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("- failed to open file for writing"); + return; + } + if(file.print(message)){ + Serial.println("- file written"); + } else { + Serial.println("- write failed"); + } + file.close(); +} + +//add data to a file +void appendFile(fs::FS &fs, const char * path, const char * message){ + Serial.printf("Appending to file: %s\r\n", path); + + File file = fs.open(path, FILE_APPEND); + if(!file){ + Serial.println("- failed to open file for appending"); + return; + } + if(file.print(message)){ + Serial.println("- message appended"); + } else { + Serial.println("- append failed"); + } + file.close(); +} + +// store retrieved credentials to file +void storeCredentials() { + + Credentials myCredentials = c8yClient.getCredentials(); + + Serial.printf("Writing credentials %s/%s to file", myCredentials.tenant, myCredentials.username); + + writeFile(SPIFFS, CREDENTIALS_FILE, myCredentials.tenant); + appendFile(SPIFFS, CREDENTIALS_FILE, "\n"); + appendFile(SPIFFS, CREDENTIALS_FILE, myCredentials.username); + appendFile(SPIFFS, CREDENTIALS_FILE, "\n"); + appendFile(SPIFFS, CREDENTIALS_FILE, myCredentials.password); + appendFile(SPIFFS, CREDENTIALS_FILE, "\n"); + + Serial.println("Credentials stored"); +} + +//make a connection to cumulocity +void connectC8Y() { + + c8yClient.connect(host, tenant, username, c8yPassword); + + Serial.println("Retrieving device credentials"); + + if (!storedCredentials) { + c8yClient.retrieveDeviceCredentials(); + while (!c8yClient.checkCredentialsReceived()) { + Serial.print("#"); + delay(1000); + } + + Serial.println("Reconnecting to Cumulocity"); + + c8yClient.disconnect(); + c8yClient.reconnect(); + } +} + +//============================================================================ +// Setup and Loop methods +//============================================================================ + +void setup() { + + Serial.begin(115200); + + connectWifi(); + + getSerialNumber(); + + if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){ + Serial.println("SPIFFS Mount Failed"); + return; + } + + readCredentials(SPIFFS, CREDENTIALS_FILE); + + connectC8Y(); + + if (!storedCredentials) { + storeCredentials(); + } + + c8yClient.registerDevice(clientId, "c8y_esp32"); + +} + +void loop() { + + delay(1000); + c8yClient.loop(); + +} \ No newline at end of file diff --git a/examples/SendMeasurement/SendMeasurement.ino b/examples/SendMeasurement/SendMeasurement.ino index bb1dd41..d860254 100644 --- a/examples/SendMeasurement/SendMeasurement.ino +++ b/examples/SendMeasurement/SendMeasurement.ino @@ -8,15 +8,14 @@ const char* ssid = "........"; const char* wifiPassword = "........"; const char* host = "xxx.cumulocity.com"; -char* username = "........"; -char* c8yPassword = "........"; -char* tenant = "........"; +char* username = "........"; // fixed credentials can be registered in the Administration section +char* c8yPassword = "........"; // create a user in usermanagement with the "device"role and fill the credentials here +char* tenant = "........"; //tenant ID can be found by clicking on your name in the top right corner of Cumulocity const char* clientId = "........."; //Should be a unique identifier for this device, e.g. IMEI, MAC address or SerialNumber //uint64_t chipid = ESP.getEfuseMac(); WiFiClient wifiClient; -PubSubClient pubSubClient(host, 1883, wifiClient); -CumulocityClient c8yClient(pubSubClient, tenant, username, c8yPassword, clientId); +CumulocityClient c8yClient(wifiClient, clientId); void setup() { @@ -31,20 +30,9 @@ void setup() { } Serial.println("connected to wifi"); - c8yClient.connect(); + c8yClient.connect(host, tenant, username, c8yPassword); - Serial.println("Retrieving device credentials"); - - c8yClient.retrieveDeviceCredentials(); - while (!c8yClient.checkCredentialsReceived()) { - Serial.print("#"); - delay(1000); - } - - c8yClient.disconnect(); - c8yClient.connect(); - - c8yClient.registerDevice("ESP32 - Misja", "c8y_esp32"); + c8yClient.registerDevice(clientId, "c8y_esp32"); } @@ -52,5 +40,6 @@ void loop() { delay(1000); c8yClient.loop(); + c8yClient.createMeasurement("Temperature", "T", "20.5", "*C"); } diff --git a/library.properties b/library.properties index 80d1383..aff157d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Cumulocity IoT client -version=0.0.4 +version=0.0.5 author=Misja Heuveling maintainer=Misja Heuveling sentence=A client library to connect your Arduino to Cumulocity IoT cloud over MQTT. diff --git a/src/CumulocityClient.cpp b/src/CumulocityClient.cpp index 4307109..6c91c28 100644 --- a/src/CumulocityClient.cpp +++ b/src/CumulocityClient.cpp @@ -3,35 +3,58 @@ #include #include -CumulocityClient::CumulocityClient(PubSubClient client, char* tenant, char* user, char* password, const char* deviceId) { +CumulocityClient::CumulocityClient(Client& networkClient, const char* deviceId) { - Serial.printf("CumulocityClient(%s, %s, %s)\n", tenant, user, deviceId); + Serial.printf("CumulocityClient(%s)\n", deviceId); - _client = client; - _deviceId = deviceId; + this->_client = PubSubClient(networkClient); + this->_deviceId = deviceId; + this->_client.setKeepAlive(_keepAlive); +} + +bool CumulocityClient::reconnect() { + + if (_credentials.tenant != NULL && _credentials.username != NULL && _credentials.password != NULL) { + return connectClient(); + } else { + return false; + } +} +bool CumulocityClient::connect(char* host, char* tenant, char* user, char* password) { + + Serial.printf("connect(%s,%s,%s)\n", host, tenant, user); + + _host = host; _credentials.tenant = tenant; _credentials.username = user; _credentials.password = password; - _client.setKeepAlive(_keepAlive); -} + String myClientId = "d:"; + myClientId += _deviceId; + _clientId = myClientId.c_str(); -bool CumulocityClient::connect() { - - Serial.println("connect()"); - _clientId = (char*) malloc(strlen(_deviceId) +3); - strcpy(_clientId, "d:"); - _clientId = strcat(_clientId, _deviceId); + _client.setServer(_host, 1883); return connectClient(); } -bool CumulocityClient::connect(char* defaultTemplate) { +bool CumulocityClient::connect(char* host, char* tenant, char* user, char* password, char* defaultTemplate) { + + _host = host; + _credentials.tenant = tenant; + _credentials.username = user; + _credentials.password = password; + + String myClientId = "d:"; + myClientId += _deviceId; + myClientId += ":"; + myClientId += defaultTemplate; + _clientId = myClientId.c_str(); + + _client.setServer(_host, 1883); - _clientId = (char *) malloc(strlen(_deviceId) + strlen(defaultTemplate) + 4); - sprintf(_clientId, "d:%s:%s", _deviceId, defaultTemplate); return connectClient(); } @@ -46,23 +69,23 @@ bool CumulocityClient::connectClient() { } ); - char user[512];// = (char*) malloc(strlen(_credentials.tenant) + strlen(_credentials.username) + 2); - - sprintf(user, "%s/%s",_credentials.tenant, _credentials.username); - - bool success = _client.connect(_clientId, user, _credentials.password, "s/us", 0, false, "400,c8y_ConnectionEvent,\"Connections lost.\"", true); + String user = _credentials.tenant; + user += "/"; + user += _credentials.username; + bool success = + _client.connect(_clientId, user.c_str(), _credentials.password, "s/us", 0, + false, "400,c8y_ConnectionEvent,\"Connections lost.\"", true); if (!success) { + Serial.print("Unable to connect to Cumulocity as "); Serial.println(user); + Serial.println(_client.state()); } else { Serial.println("Connected to cumulocity."); } - //free(user); - //Serial.println("Freed the user string"); - return success; } @@ -70,7 +93,6 @@ void CumulocityClient::disconnect() { Serial.print("disconnect()"); _client.disconnect(); - free(_clientId); } void CumulocityClient::retrieveDeviceCredentials() { @@ -127,7 +149,7 @@ void CumulocityClient::loop() { bool myConnected = _client.loop(); if (!myConnected) { - connect(); + reconnect(); } } diff --git a/src/CumulocityClient.h b/src/CumulocityClient.h index b8a852e..7f595c8 100644 --- a/src/CumulocityClient.h +++ b/src/CumulocityClient.h @@ -21,34 +21,36 @@ typedef struct Credentials { class CumulocityClient { public: - CumulocityClient(PubSubClient client, char* tenant, char* user, char* password, const char* deviceId); - bool connect(); - bool connect(char* defaultTemplate); - void disconnect(); - void setDeviceCredentials(char* tenant, char* user, char* password); - void registerDevice(char* deviceName, char* deviceType); - void retrieveDeviceCredentials(); - Credentials getCredentials(); - bool checkCredentialsReceived(); + CumulocityClient(Client& networkClient, const char* deviceId); + bool reconnect(); + bool connect(char* host, char* tenant, char* user, char* password); + bool connect(char* host, char* tenant, char* user, char* password, char* defaultTemplate); + void disconnect(); + void setDeviceCredentials(char* tenant, char* user, char* password); + void registerDevice(char* deviceName, char* deviceType); + void retrieveDeviceCredentials(); + Credentials getCredentials(); + bool checkCredentialsReceived(); void createMeasurement(char* fragment, char* series, char* value, char* unit); - void setKeepAlive(int keepAlive); - void loop(); + void setKeepAlive(int keepAlive); + void loop(); private: - bool connectClient(); - void callbackHandler(const char* topic, byte* payload, unsigned int length); - void parseCredentials(char* payload); - char** parseCSV(char* payload); - void freeCSVElements(char **parsed); - int countFields( const char *line ); + bool connectClient(); + void callbackHandler(const char* topic, byte* payload, unsigned int length); + void parseCredentials(char* payload); + char** parseCSV(char* payload); + void freeCSVElements(char **parsed); + int countFields( const char *line ); - PubSubClient _client; + PubSubClient _client; - bool _credentialsReceived; + char* _host; Credentials _credentials; - char* _clientId; - int _keepAlive = 600; - const char* _deviceId; + const char* _clientId; + bool _credentialsReceived; + int _keepAlive = 600; + const char* _deviceId; }; #endif