diff --git a/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino b/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino index 07a7deed3..56679ab6e 100644 --- a/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino +++ b/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino @@ -1,11 +1,14 @@ #include #include - #include "arduino_secrets.h" + +#define TIMEOUT 7000 + ///////please enter your sensitive data in the Secret tab/arduino_secrets.h -char ssid[] = SECRET_SSID; // your network SSID (name) -char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) +char ssid[] = SECRET_SSID; // your network SSID (name) +char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) int status = WL_IDLE_STATUS; // the WiFi radio's status +String cloudSerialBuffer = ""; // the string used to compose network messages from the received characters WiFiClient wifiClient; @@ -16,9 +19,8 @@ unsigned long getTime() { void setup() { //Initialize serial and wait for port to open: Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } + int timeout = millis() + TIMEOUT; + while (!Serial && (millis() < timeout)) {} // check for the presence of the shield: if (WiFi.status() == WL_NO_SHIELD) { @@ -33,7 +35,8 @@ void setup() { } // attempt to connect to WiFi network: - while (status != WL_CONNECTED) { + int attempts = 0; + while (status != WL_CONNECTED && attempts < 6) { Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network: @@ -41,16 +44,29 @@ void setup() { // wait 10 seconds for connection: delay(10000); + attempts++; + } + + if (status != WL_CONNECTED) { + Serial.println("Failed to connect to Wifi!"); + while (true); } // you're connected now, so print out the data: Serial.print("You're connected to the network"); Serial.println(); - Serial.println("Attempting to connect to Arduino Cloud ..."); + Serial.println("Attempting to connect to Arduino Cloud"); ArduinoCloud.onGetTime(getTime); - if (!ArduinoCloud.connect()) { + + attempts = 0; + while (!ArduinoCloud.connect() && attempts < 10) { + Serial.print("."); + attempts++; + } + + if (attempts >= 10) { Serial.println("Failed to connect to Arduino Cloud!"); while (1); } @@ -58,17 +74,67 @@ void setup() { Serial.println("Successfully connected to Arduino Cloud :)"); CloudSerial.begin(9600); + CloudSerial.print("I'm ready for blinking!\n"); } void loop() { ArduinoCloud.poll(); + // check if there is something waiting to be read if (CloudSerial.available()) { - Serial.write(CloudSerial.read()); + char character = CloudSerial.read(); + cloudSerialBuffer += character; + + // if a \n character has been received, there should be a complete command inside cloudSerialBuffer + if (character == '\n') { + manageString(); + } + } + else // if there is nothing to read, it could be that the last command didn't end with a '\n'. Check. + { + manageString(); } + // Just to be able to simulate MKR1000's responses through the serial monitor if (Serial.available()) { CloudSerial.write(Serial.read()); } } +void manageString() { + // Don't proceed if the string is empty + if (cloudSerialBuffer.equals("")) return; + + // Remove whitespaces + cloudSerialBuffer.trim(); + + // Make it uppercase; + cloudSerialBuffer.toUpperCase(); + + if (cloudSerialBuffer.equals("ON")) { + digitalWrite(6, HIGH); + } + if (cloudSerialBuffer.equals("OFF")) { + digitalWrite(6, LOW); + } + + sendString(cloudSerialBuffer); + + // Reset cloudSerialBuffer + cloudSerialBuffer = ""; +} + +// sendString sends a string to the Arduino Cloud. +void sendString(String stringToSend) { + // send the characters one at a time + char lastSentChar = 0; + for (int i = 0; i < stringToSend.length(); i++) { + lastSentChar = stringToSend.charAt(i); + CloudSerial.write(lastSentChar); + } + + // if the last sent character wasn't a '\n' add it + if (lastSentChar != '\n') { + CloudSerial.write('\n'); + } +} diff --git a/examples/utility/Provisioning/Provisioning.ino b/examples/utility/Provisioning/Provisioning.ino index 1d5dce576..7ec476346 100644 --- a/examples/utility/Provisioning/Provisioning.ino +++ b/examples/utility/Provisioning/Provisioning.ino @@ -5,6 +5,7 @@ #include #include +const bool DEBUG = true; const int keySlot = 0; const int compressedCertSlot = 10; const int serialNumberAndAuthorityKeyIdentifierSlot = 11; @@ -55,7 +56,8 @@ void setup() { while (1); } - ECCX08Cert.setSubjectCommonName(ECCX08.serialNumber()); + String thingId = promptAndReadLine("Please enter the thing id: "); + ECCX08Cert.setSubjectCommonName(thingId); String csr = ECCX08Cert.endCSR(); @@ -68,7 +70,6 @@ void setup() { Serial.println(); Serial.println(csr); - String thingId = promptAndReadLine("Please enter the thing id: "); String issueYear = promptAndReadLine("Please enter the issue year of the certificate (2000 - 2031): "); String issueMonth = promptAndReadLine("Please enter the issue month of the certificate (1 - 12): "); String issueDay = promptAndReadLine("Please enter the issue day of the certificate (1 - 31): "); @@ -127,22 +128,25 @@ void setup() { while (1); } - Serial.println("Compressed cert = "); + if (DEBUG) { + Serial.println("Compressed cert = "); - const byte* certData = ECCX08Cert.bytes(); - int certLength = ECCX08Cert.length(); + const byte* certData = ECCX08Cert.bytes(); + int certLength = ECCX08Cert.length(); - for (int i = 0; i < certLength; i++) { - byte b = certData[i]; + for (int i = 0; i < certLength; i++) { + byte b = certData[i]; - if (b < 16) { - Serial.print('0'); + if (b < 16) { + Serial.print('0'); + } + Serial.print(b, HEX); } - Serial.print(b, HEX); + Serial.println(); } - Serial.println(); } + void loop() { } diff --git a/src/ArduinoCloud.cpp b/src/ArduinoCloud.cpp index dddf3683d..bff7939f2 100644 --- a/src/ArduinoCloud.cpp +++ b/src/ArduinoCloud.cpp @@ -1,11 +1,10 @@ -#include - -#include "utility/ECCX08Cert.h" -#include "CloudSerial.h" #include "ArduinoCloudV2.h" +#include +#include +#include "CloudSerial.h" -const static char server[] = "a19g5nbe27wn47.iot.us-east-1.amazonaws.com"; //"xxxxxxxxxxxxxx.iot.xx-xxxx-x.amazonaws.com"; +const static char server[] = "mqtts-sa.iot.oniudra.cc"; const static int keySlot = 0; const static int compressedCertSlot = 10; @@ -14,7 +13,8 @@ const static int thingIdSlot = 12; ArduinoCloudClass::ArduinoCloudClass() : _bearSslClient(NULL), - _mqttClient(256) + // Size of the receive buffer + _mqttClient(MQTT_BUFFER_SIZE) { } @@ -28,7 +28,7 @@ ArduinoCloudClass::~ArduinoCloudClass() int ArduinoCloudClass::begin(Client& net) { byte thingIdBytes[72]; - + if (!ECCX08.begin()) { return 0; } @@ -42,7 +42,7 @@ int ArduinoCloudClass::begin(Client& net) return 0; } - ECCX08Cert.setSubjectCommonName(ECCX08.serialNumber()); + ECCX08Cert.setSubjectCommonName(_id); ECCX08Cert.setIssuerCountryName("US"); ECCX08Cert.setIssuerOrganizationName("Arduino LLC US"); ECCX08Cert.setIssuerOrganizationalUnitName("IT"); @@ -57,30 +57,112 @@ int ArduinoCloudClass::begin(Client& net) } _bearSslClient = new BearSSLClient(net); _bearSslClient->setEccSlot(keySlot, ECCX08Cert.bytes(), ECCX08Cert.length()); + + // Begin function for the MQTTClient + mqttClientBegin(*_bearSslClient); + // Thing initialization + Thing.begin(); - _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); - _mqttClient.begin(server, 8883, *_bearSslClient); + return 1; +} - _stdoutTopic = "$aws/things/" + _id + "/stdout"; - _stdinTopic = "$aws/things/" + _id + "/stdin"; +// private class method used to initialize mqttClient class member. (called in the begin class method) +void ArduinoCloudClass::mqttClientBegin(Client& net) +{ + // MQTT topics definition + _stdoutTopic = "/a/d/" + _id + "/s/o"; + _stdinTopic = "/a/d/" + _id + "/s/i"; + _dataTopicIn = "/a/d/" + _id + "/e/i"; + _dataTopicOut = "/a/d/" + _id + "/e/o"; - return 1; + // use onMessage as callback for received mqtt messages + _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); + _mqttClient.begin(server, 8883, net); + // Set will for MQTT client: {topic, qos, retain message} + const char lastMessage[] = "abcb"; + _mqttClient.setWill(_dataTopicOut.c_str(), lastMessage, false, 1); + // Set MQTT connection options + _mqttClient.setOptions(mqttOpt.keepAlive, mqttOpt.cleanSession, mqttOpt.timeout); } int ArduinoCloudClass::connect() { + //TODO MQTT brocker connection + // Username: device id + // Password: empty if (!_mqttClient.connect(_id.c_str())) { return 0; } - _mqttClient.subscribe(_stdinTopic); + _mqttClient.subscribe(_dataTopicIn); return 1; } +bool ArduinoCloudClass::disconnect() +{ + return _mqttClient.disconnect(); +} + void ArduinoCloudClass::poll() { + // If user call poll() without parameters use the default ones + poll(MAX_RETRIES, RECONNECTION_TIMEOUT); +} + +bool ArduinoCloudClass::mqttReconnect(int maxRetries, int timeout) +{ + // Counter for reconnection retries + int retries = 0; + + // Check for MQTT broker connection, of if maxReties limit is reached + // if MQTTClient is connected , simply do nothing and retun true + while(!_mqttClient.connected() && retries++ < maxRetries) { + + // Get last MTTQClient error, (a common error may be a buffer overflow) + lwmqtt_err_t err = _mqttClient.lastError(); + + // try establish the MQTT broker connection + connect(); + // delay eventually used for the nex re-connection try + delay(timeout); + } + + // It was impossible to establish a connection, return + if (retries == maxRetries) + return false; + + return true; +} + +void ArduinoCloudClass::poll(int reconnectionMaxRetries, int reconnectionTimeoutMs) +{ + // Method's argument controls + int maxRetries = (reconnectionMaxRetries > 0) ? reconnectionMaxRetries : MAX_RETRIES; + int timeout = (reconnectionTimeoutMs > 0) ? reconnectionTimeoutMs : RECONNECTION_TIMEOUT; + + // If the reconnect() culd not establish the connection, return the control to the user sketch + if (!mqttReconnect(maxRetries, timeout)) + return; + + // MTTQClient connected!, poll() used to retrieve data from MQTT broker _mqttClient.loop(); + + uint8_t data[MQTT_BUFFER_SIZE]; + int length = Thing.poll(data, sizeof(data)); + // Are there some read properties that must be sent to the cloud ?? + if (length > 0) { + // Check the connection is ok! if not reconnect + // If a thing has all read properties only try to reconnect when the have to be sent(based on their update policy) + writeProperties(data, length); + } +} + +void ArduinoCloudClass::reconnect(Client& net) +{ + // Initialize again the MQTTClient, otherwise it would not be able to receive messages through its callback + mqttClientBegin(net); + connect(); } void ArduinoCloudClass::onGetTime(unsigned long(*callback)(void)) @@ -93,6 +175,11 @@ int ArduinoCloudClass::connected() return _mqttClient.connected(); } +int ArduinoCloudClass::writeProperties(const byte data[], int length) +{ + return _mqttClient.publish(_dataTopicOut.c_str(), (const char*)data, length); +} + int ArduinoCloudClass::writeStdout(const byte data[], int length) { return _mqttClient.publish(_stdoutTopic.c_str(), (const char*)data, length); @@ -105,8 +192,11 @@ void ArduinoCloudClass::onMessage(MQTTClient* /*client*/, char topic[], char byt void ArduinoCloudClass::handleMessage(char topic[], char bytes[], int length) { - if (_stdinTopic == topic) { + if (strcmp(_stdinTopic.c_str(), topic) == 0) { CloudSerial.appendStdin((uint8_t*)bytes, length); + } + if (strcmp(_dataTopicIn.c_str(), topic) == 0) { + Thing.decode((uint8_t*)bytes, length); } } diff --git a/src/ArduinoCloudV2.h b/src/ArduinoCloudV2.h index 6a592565f..42527c884 100644 --- a/src/ArduinoCloudV2.h +++ b/src/ArduinoCloudV2.h @@ -3,44 +3,81 @@ #include #include +#include #include "CloudSerial.h" +// Declaration of the struct for the mqtt connection options +typedef struct mqtt_opt{ + int keepAlive; + bool cleanSession; + int timeout; +} mqttConnectionOptions; + class ArduinoCloudClass { public: - ArduinoCloudClass(); - ~ArduinoCloudClass(); - - int begin(Client& net); - - int connect(); - - void poll(); - - void onGetTime(unsigned long(*)(void)); - - int connected(); + ArduinoCloudClass(); + ~ArduinoCloudClass(); + + // Class constant declaration + static const int MQTT_BUFFER_SIZE = 256; + static const int MAX_RETRIES = 5; + static const int RECONNECTION_TIMEOUT = 2000; + const mqttConnectionOptions mqttOpt = {120, false, 1000}; + + int begin(Client& net); + int connect(); + bool disconnect(); + void poll(); + // defined for users who want to specify max reconnections reties and timeout between them + void poll(int reconnectionMaxRetries, int reconnectionTimeoutMs); + // It must be a user defined function, in order to avoid ArduinoCloud include specificW iFi file + // in this case this library is independent from the WiFi one + void onGetTime(unsigned long(*)(void)); + int connected(); + // Clean up existing Mqtt connection, create a new one and initialize it + void reconnect(Client& net); + + #define addProperty( v, ...) addPropertyReal(v, #v, __VA_ARGS__) + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, long seconds = ON_CHANGE, T minDelta = T(0), void(*fn)(void) = NULL) { + Thing.addPropertyReal(property, name).publishEvery(seconds).setPermission(_permission).onUpdate(fn).minimumDelta(&minDelta); + } + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, T minDelta = T(0)) { + Thing.addPropertyReal(property, name).publishEvery(seconds).setPermission(_permission).onUpdate(fn).minimumDelta(&minDelta); + } + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, void(*fn)(void) = NULL, long seconds = ON_CHANGE, T minDelta = T(0)) { + Thing.addPropertyReal(property, name).publishEvery(seconds).setPermission(_permission).onUpdate(fn).minimumDelta(&minDelta); + } protected: - friend class CloudSerialClass; - int writeStdout(const byte data[], int length); + friend class CloudSerialClass; + int writeStdout(const byte data[], int length); + int writeProperties(const byte data[], int length); + // Used to initialize MQTTClient + void mqttClientBegin(Client& net); + // Function in charge of perform MQTT reconnection, basing on class parameters(retries,and timeout) + bool mqttReconnect(int maxRetries, int timeout); private: - static void onMessage(MQTTClient *client, char topic[], char bytes[], int length); - - void handleMessage(char topic[], char bytes[], int length); - -private: - String _id; - BearSSLClient* _bearSslClient; - MQTTClient _mqttClient; - - String _stdinTopic; - String _stdoutTopic; + static void onMessage(MQTTClient *client, char topic[], char bytes[], int length); + void handleMessage(char topic[], char bytes[], int length); + + String _id; + ArduinoCloudThing Thing; + BearSSLClient* _bearSslClient; + MQTTClient _mqttClient; + + // Class attribute to define MTTQ topics 2 for stdIn/out and 2 for data, in order to avoid getting previous pupblished payload + String _stdinTopic; + String _stdoutTopic; + String _dataTopicIn; + String _dataTopicOut; }; - extern ArduinoCloudClass ArduinoCloud; #endif