I am trying to create a bridge between the Arduino IoT Cloud and my home IoT hub which runs on a Raspberry Pi 4 and has its own MQTT broker. The tidiest implementation I can think of is to create a Thing containing a second MQTT client. (The first comes as standard with the Arduino IoT software and the second would be a client of my home broker). The task would then be reduced to a matter of conveying state changes between IoT variables and the home MQTT client.
The basic code for writing to the home MQTT broker correctly posts messages on the home broker when run in a standalone device, but crashes continuously when executed as an Arduino IoT Thing.
The messages in the serial monitor are here:
***** Arduino IoT Cloud - configuration info *****
Device ID: bb8ed424-11dc-445e-9938-51b78c36f988
MQTT Broker: mqtts-sa.iot.arduino.cc:8883
Attempting to connect to the home MQTT broker: 192.168.1.151
MQTT connection failed! Error code = -2
According to the source code ( MqttClient.h) error message of -2 means MQTT_CONNECTION_REFUSED, but I can't think why that should be.
I'm running the full Arduino editor on Windows 11/Edge. The device I am using is an MKR WIFI 1010.
Can anyone suggest what the problem might be? Is it even feasible to run two clients in the same device?
/*
Sketch generated by the Arduino IoT Cloud Thing "Untitled"
https://create.arduino.cc/cloud/things/f571cdb6-12e9-4f00-a4f8-a7914e69b755
Arduino IoT Cloud Variables description
The following variables are automatically generated and updated when changes are made to the Thing
- No variables have been created, add cloud variables on the Thing Setup page
to see them declared here
Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
which are called when their values are changed from the Dashboard.
These functions are generated with the Thing and added at the end of this sketch.
*/
/*
The intention of this sketch is to serve as a two-way bridge between the Arduino IoT Cloud and the
Home IoT hub MQTT broker residing on a Raspberry Pi.
The simplest implementation of a bridge should be to run an MQTT client connected to the Home IoT
broker with the sketch simply shuffling IoT variable updates around.
However, I have been unable to get the MQTT client to run, even though it compiles correctly.
The implementation of the Arduino IoT cloud already uses an MQTT client and it does appear to
take kindly to a second being used.
*/
#include "thingProperties.h"
const char broker[] = "192.168.1.151";
int port = 1883;
const char mqtt_user[] = "<USER GOES HERE>";
const char mqtt_pass[] = "<PASSWORD GOES HERE>";
char thing_name[] = "MKR1010-001";
const char topic[] = "MKR1010-001/count";
//WiFiClient wifiClient;
MqttClient * homeMqttClient;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
unsigned interval = 2000;
int count = 0;
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
// Set debug message level
setDebugMessageLevel(4);
ArduinoCloud.printDebugInfo();
// Initialise the build in LED pin
pinMode(LED_BUILTIN, OUTPUT);
// Create a MQTT client
homeMqttClient = new MqttClient(ArduinoIoTPreferredConnection.getClient());
// You can provide a unique client ID, if not set the library uses Arduino-millis()
// Each client must have a unique client ID
homeMqttClient->setId(thing_name);
// You can provide a username and password for authentication
homeMqttClient->setUsernamePassword(mqtt_user, mqtt_pass);
delay(2000);
Serial.print("Attempting to connect to the home MQTT broker: ");
Serial.println(broker);
while (!homeMqttClient->connected()){
if (!homeMqttClient->connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(homeMqttClient->connectError());
delay(2000);
}
}
Serial.println("You're connected to the MQTT broker!");
Serial.println();
}
void loop() {
ArduinoCloud.update();
// Your code here
// call poll() regularly to allow the library to send MQTT keep alives which
// avoids being disconnected by the broker
homeMqttClient->poll();
// to avoid having delays in loop, we'll use the strategy from BlinkWithoutDelay
// see: File -> Examples -> 02.Digital -> BlinkWithoutDelay for more info
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time a message was sent
previousMillis = currentMillis;
Serial.print("Sending message to topic: ");
Serial.print(topic);
Serial.print(" ");
Serial.println(count);
// send message, the Print interface can be used to set the message contents
homeMqttClient->beginMessage(topic);
homeMqttClient->print(count);
homeMqttClient->endMessage();
Serial.println();
count++;
// Flash the LED as a heartbeat
digitalWrite(LED_BUILTIN, count&2); // turn the LED on (HIGH is the voltage level)
}
}
192.168.1.151 is an address in your local network. It's not visible from the Arduino IoT cloud.
Surely the WiFi client can communicate with both local and non-local IP addresses.
When I run in a stand-alone configuration the WiFi client can access both my local MQTT broker and an NTP server. That's both local and non-local.
Forgive me if I'm not using the correct terminology.
Oh, my mistake - I thought you were trying to have the IoT cloud reach back to your local broker.
In that case, perhaps try running the broker in verbose mode to see why it's refusing the connection.
That's a good point. I didn't think of looking at the broker logs. I'll give that a try. Cheers....
Update: I switched the broker logging to its most verbose and logged in via MQTT Explorer to prove that connection status is reported and it was. I then logged out and tried to log back in with the wrong pass word and it reported a failed connection attempt. I then ran the Arduino sketch which repeatedly tried to connect to the broker and failed and nothing was reported. That sort of points to the broker never receiving a connection request - perhaps.
Ok, I've think I've cracked it. It appears that the WiFi client isn't fully available for use in the creation of the MQTT client until some time after setup() has exited. In fact it appears to need a few calls to ArduinoCloud.update() before ArduinoCloud.connected() returns a 1, then it's usable some time after that. Exactly how long I'm not sure yet.
The bottom line is that the creation of the MQTT client and its connection etc., has to be in loop() not setup(), and it can't be created immediately.
I've got a dirty solution communicating with my home broker, but will post a clean version when I have it.
I have a cheap fix.
Placing code below before the creation of the MQTT client, but still inside setup(), makes it work. I don't know if there are any down sides to calling ArduinoCloud.update() outside loop(), but this solution allows set-up code to remain inside setup(), which seems correct.
while(ArduinoCloud.connected()==0){
ArduinoCloud.update();
}
Simply waiting for ArduinoCloud.connected()==1 without the calls to ArduinoCloud.update() causes the sketch to hang for a while and then crash. Presumably something times out, but I don't know.
Here is the full sketch, demonstrating a second MQTT client. I tested it on an MKR WiFi 1010.
/*
Sketch generated by the Arduino IoT Cloud Thing "Untitled"
https://create.arduino.cc/clouhttps://create.arduino.cc/editor/robertics/4a293d69-5427-4f72-a26f-5e751eed04b5d/things/f571cdb6-12e9-4f00-a4f8-a7914e69b755
Arduino IoT Cloud Variables description
The following variables are automatically generated and updated when changes are made to the Thing
int test_int_0;
int test_int_1;
bool test_bool;
Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
which are called when their values are changed from the Dashboard.
These functions are generated with the Thing and added at the end of this sketch.
*/
/*
This sketch demonstrates a two-way bridge between the Arduino IoT Cloud and a Home IoT MQTT
broker, residing on the local WiFi network. On Raspberry Pi, for example.
Three Arduino IoT Variables are managed. They demonstrate the distribution of data originating from
an Arduino IoT dashboard widget, this Thing and the home IoT broker respectively.
test_bool: The value of this is defined by an Arduino IoT dashboard switch widget of the same
name. The state of this variable is sent to the Home IoT MQTT broker.
test_int_0: The value of this is defined by this Thing and is sent to both an Arduino IoT
dashboard value widget of the same name and to the Home IoT MQTT broker.
test_int_1: The value of this is defined in the Home IoT MQTT broker and sent to the Arduino
IoT dashboard value widget of the same name.
Using MQTT Explorer connected to the Home MQTT broker will display the variables like this:
MKR1010-001/test_bool = true
/test_int_0 = 49
/test_int_1 = 123
Reconnects to the Home MQTT broker are handled automatically.
*/
#include "thingProperties.h"
const char broker[] = "192.168.1.151"; // IP address Home IoT MQTT broker
int port = 1883;
const char mqtt_user[] = "<USER GOES HERE>";
const char mqtt_pass[] = "<PASSWORD GOES HERE>";
char thing_name[] = "MKR1010-001";
const char topic_test_int_0[] = "MKR1010-001/test_int_0";
const char topic_test_int_1[] = "MKR1010-001/test_int_1";
const char topic_test_bool[] = "MKR1010-001/test_bool";
MqttClient * homeMqttClient = 0; // MQTT Client that will connect to the Home IoT broker
bool gotMqtt = false; // Indicates MQTT service availability
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
unsigned interval = 10000;
int count = 0;
void setup() {
Serial.begin(9600);
delay(1500);
initProperties();
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
setDebugMessageLevel(4);
ArduinoCloud.printDebugInfo();
pinMode(LED_BUILTIN, OUTPUT);
// Don't create the MQTT client here as the WiFi client doesn't exist yet
}
void loop() {
ArduinoCloud.update();
// The MQTT is managed by this call, which should be done often
// Every loop seems to be good
gotMqtt = manageMqtt(gotMqtt);
// Increment 'count' every ten seconds and get it to the Arduino IoT Cloud and
// the Home IoT MQTT broker
currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// Post the latest value to the Home IoT broker if the MQTT service is up and running
if (gotMqtt){
Serial.print("Sending: ");
Serial.print(topic_test_int_0);
Serial.print(" ");
Serial.println(count);
homeMqttClient->beginMessage(topic_test_int_0);
homeMqttClient->print(count);
homeMqttClient->endMessage();
}
// Update the IoT variable
test_int_0 = count;
count++;
// Flash the LED as a heartbeat, albeint a slow one.
digitalWrite(LED_BUILTIN, count&2);
}
}
// Manage the MQTT service
bool manageMqtt(bool _gotMqtt){
bool returnValue = _gotMqtt;
if (!ArduinoCloud.connected()){
// No Arduino IoT Cloud connection, so no MQTT. Do nothing.
returnValue = false;
}
else if (homeMqttClient==0){
// MQTT client has not yet been created, so create it
homeMqttClient = new MqttClient(ArduinoIoTPreferredConnection.getClient());
homeMqttClient->setId(thing_name);
homeMqttClient->setUsernamePassword(mqtt_user, mqtt_pass);
returnValue = false;
}
else if (!homeMqttClient->connected()){
// MQTT Client is not connected, so attempt to connect it.
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
if (!homeMqttClient->connect(broker, port)) {
// The connection attempt failed.
Serial.print("MQTT connection failed! Error code = ");
Serial.println(homeMqttClient->connectError());
returnValue = false;
}
else{
// The connection attempt succeded.
Serial.println("You're connected to the MQTT broker!");
// Define message handler
homeMqttClient->onMessage(onMqttMessage);
// Subscribe to topic
homeMqttClient->subscribe(topic_test_int_1);
returnValue = true;
}
}
else{
homeMqttClient->poll();
}
return(returnValue);
}
// Handle messages received
void onMqttMessage(int messageSize) {
// Received a message, print out the topic and contents
Serial.print("Received:");
String _topic = homeMqttClient->messageTopic();
Serial.print(_topic);
Serial.print(" ");
// Transfer the message to a char[]
char _tmp[messageSize+1];
int _tmpPtr = 0;
while (homeMqttClient->available()) {
_tmp[_tmpPtr] = homeMqttClient->read();
_tmpPtr++;
}
_tmp[_tmpPtr] = 0;
// Transfer the message to a String so that it can be parsed to a value
String _message = _tmp;
Serial.println(_message);
// Update the Arduino IoT variable
if (_topic==topic_test_int_1){
test_int_1 = _message.toInt();
}
}
/*
Since test_bool is READ_WRITE variable, onTestBoolChange() is
executed every time a new value is received from IoT Cloud.
*/
void onTestBoolChange() {
// The value of test_bool has been changed so send it to
// the Home IoT broker
if (gotMqtt){
homeMqttClient->beginMessage(topic_test_bool);
if (test_bool)
homeMqttClient->print("true");
else
homeMqttClient->print("false");
homeMqttClient->endMessage();
}
}