diff --git a/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino b/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino index 07a7deed3..a8fda62f6 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 // change to WiFiNINA.h if you are using the MKR WiFi 1010 or MKR Vidor 4000 #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) 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 the board 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 a58b486b3..cf18f0664 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): "); @@ -78,9 +79,6 @@ void setup() { String authorityKeyIdentifier = promptAndReadLine("Please enter the certificates authority key identifier: "); String signature = promptAndReadLine("Please enter the certificates signature: "); - serialNumber.toUpperCase(); - signature.toUpperCase(); - byte thingIdBytes[72]; byte serialNumberBytes[16]; byte authorityKeyIdentifierBytes[20]; @@ -130,6 +128,10 @@ void setup() { while (1); } + if (!DEBUG) { + return; + } + Serial.println("Compressed cert = "); const byte* certData = ECCX08Cert.bytes(); @@ -179,8 +181,9 @@ String readLine() { return line; } -void hexStringToBytes(const String& in, byte out[], int length) { +void hexStringToBytes(String& in, byte out[], int length) { int inLength = in.length(); + in.toUpperCase(); int outLength = 0; for (int i = 0; i < inLength && outLength < length; i += 2) { @@ -190,6 +193,6 @@ void hexStringToBytes(const String& in, byte out[], int length) { byte highByte = (highChar <= '9') ? (highChar - '0') : (highChar + 10 - 'A'); byte lowByte = (lowChar <= '9') ? (lowChar - '0') : (lowChar + 10 - 'A'); - out[outLength++] = (highByte << 4) | lowByte; + out[outLength++] = (highByte << 4) | (lowByte & 0xF); } } diff --git a/src/ArduinoCloud.cpp b/src/ArduinoCloud.cpp index dddf3683d..bed2a727f 100644 --- a/src/ArduinoCloud.cpp +++ b/src/ArduinoCloud.cpp @@ -5,7 +5,7 @@ #include "ArduinoCloudV2.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 +14,8 @@ const static int thingIdSlot = 12; ArduinoCloudClass::ArduinoCloudClass() : _bearSslClient(NULL), - _mqttClient(256) + // Size of the receive buffer + _mqttClient(MQTT_BUFFER_SIZE) { } @@ -42,7 +43,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"); @@ -58,31 +59,95 @@ int ArduinoCloudClass::begin(Client& net) _bearSslClient = new BearSSLClient(net); _bearSslClient->setEccSlot(keySlot, ECCX08Cert.bytes(), ECCX08Cert.length()); - _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); - _mqttClient.begin(server, 8883, *_bearSslClient); - - _stdoutTopic = "$aws/things/" + _id + "/stdout"; - _stdinTopic = "$aws/things/" + _id + "/stdin"; + // Begin function for the MQTTClient + mqttClientBegin(*_bearSslClient); return 1; } +// 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"; + + // use onMessage as callback for received mqtt messages + _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); + _mqttClient.begin(server, 8883, net); + + // Set MQTT connection options + _mqttClient.setOptions(mqttOpt.keepAlive, mqttOpt.cleanSession, mqttOpt.timeout); +} + int ArduinoCloudClass::connect() { + // Username: device id + // Password: empty if (!_mqttClient.connect(_id.c_str())) { return 0; } - _mqttClient.subscribe(_stdinTopic); 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; + unsigned long start = millis(); + + // 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) && (millis() - start < timeout)) { + + // 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(); + } + + // It was impossible to establish a connection, return + if ((retries == maxRetries) || (millis() - start >= timeout)) + 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(); } +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)) { ArduinoBearSSL.onGetTime(callback); diff --git a/src/ArduinoCloudV2.h b/src/ArduinoCloudV2.h index 6a592565f..6b6b8ff6e 100644 --- a/src/ArduinoCloudV2.h +++ b/src/ArduinoCloudV2.h @@ -6,6 +6,13 @@ #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: @@ -14,33 +21,47 @@ class ArduinoCloudClass { int begin(Client& net); - int connect(); + // 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 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 specific WiFi 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); protected: friend class CloudSerialClass; int writeStdout(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; + // 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; }; - extern ArduinoCloudClass ArduinoCloud; #endif