How to have multiple Arduinos over MQTT (with Mosca)

Hi, I have been able to connect an Arduino WiFi to Node-RED over MQTT and I want to launch a big-scale project where I'll have multiple Arduinos with multiple sensors sending data to node. How would I organize this, have multiple nodes? This is the code I have been able to use so far:

/* This example shows how to use MQTT on the main dev boards on the market
 * HOW TO USE:
 * under connect method, add your subscribe channels.
 * under messageReceived (callback method) add actions to be done when a msg is received.
 * to publish, call client.publish(topic,msg)
 * in loop take care of using non-blocking method or it will corrupt.
 * 
 * Alberto Perro - Officine Innesto 2019
 */
 
#define BROKER_IP    "10.0.0.90"
#define DEV_NAME     "mqttdevice"
#define MQTT_USER    "mqtt"
#define MQTT_PW      "password"

const char ssid[] = "Xf123456";
const char pass[] = "11050927";

#include <MQTT.h>

#ifdef ARDUINO_SAMD_MKRWIFI1010
#include <WiFiNINA.h>
#elif ARDUINO_SAMD_MKR1000
#include <WiFi101.h>
#elif ESP8266
#include <ESP8266WiFi.h>
#else
#error unknown board
#endif
#include <Wire.h>
#include <Adafruit_MPL3115A2.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7); 
Adafruit_MPL3115A2 baro = Adafruit_MPL3115A2();

WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;

void connect() {
  Serial.print("checking wifi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }

  Serial.print("\nconnecting...");
  while (!client.connect(DEV_NAME, MQTT_USER, MQTT_PW)) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("\nconnected!");
  client.subscribe("/main_topic"); //SUBSCRIBE TO TOPIC /main_topic
}

void messageReceived(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  //
  // MQTT brokers usually use port 8883 for secure connections.
  client.begin(BROKER_IP, 1883, net);
  client.onMessage(messageReceived);
  connect();

  lcd.begin(16,2);   // initializing the LCD 16 x 2
  lcd.setBacklightPin(3,POSITIVE); // Enable or Turn On the backlight 
  lcd.setBacklight(HIGH);
}

void loop() {
  if (! baro.begin()) {
    Serial.println("Couldnt find sensor");
    return;
  }
  
  float tempC = baro.getTemperature();
  Serial.print(tempC); Serial.println("*C");
  
  float altm = baro.getAltitude();
  Serial.print(altm/-6); Serial.println(" meters");
  
  client.loop();
  if (!client.connected()) {
    connect();
  }

  String txt = "Hello World!";
  
  char data[6];
  dtostrf(tempC, 5, 2, data);
  
  // publish a message roughly every second.
  if (millis() - lastMillis > 1000) {
    lastMillis = millis();
    client.publish("/main_topic", data); //PUBLISH TO TOPIC /main_topic MSG world
  }
    
  lcd.home();
  lcd.print(altm/-6);
  lcd.setCursor(0,1);
  lcd.print("Meters");
  lcd.setCursor(9,0);
  lcd.print(tempC);
  lcd.setCursor(9,1);
  lcd.print("Celsius");
  lcd.setCursor(7,0);
  lcd.print("|");
  lcd.setCursor(7,1);
  lcd.print("|");
}

Note: I used sample code from the Arduino projects page (Interfacing Arduino MKR or ESP via MQTT - Node-RED 101 - Arduino Project Hub ), and added my sensor and LCD tweaks

You probably want to give each arduino a unique name and publish your data to a topic which includes that name, something like sensors/arduino5.

In Node-red you only need one mqtt-in node, subscribed to sensors/# .

Or possibly use meaningful names, such as the room name for example, so that if later you change it from an arduino to a zigbee device you don't have unhelpful topics hard coded into your flows.

So it would be something like this?

[
    {
        "id": "3379850d.902a8a",
        "type": "mqtt in",
        "z": "25945dac9d08dddc",
        "name": "",
        "topic": "/sensors/#",
        "qos": "0",
        "datatype": "auto",
        "broker": "179e5f19.296bb9",
        "nl": false,
        "rap": false,
        "inputs": 0,
        "x": 600,
        "y": 500,
        "wires": [
            [
                "9b59d15d.6d1d28",
                "eb01070fccaa53b5"
            ]
        ]
    },
    {
        "id": "179e5f19.296bb9",
        "type": "mqtt-broker",
        "name": "",
        "broker": "10.0.0.90",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "sessionExpiry": ""
    }
]

I currently don't have multiple Adafruit sensors but I'm planning to expand, so for this example I wrote a simple code for a message

/* This example shows how to use MQTT on the main dev boards on the market
 * HOW TO USE:
 * under connect method, add your subscribe channels.
 * under messageReceived (callback method) add actions to be done when a msg is received.
 * to publish, call client.publish(topic,msg)
 * in loop take care of using non-blocking method or it will corrupt.
 * 
 * Alberto Perro - Officine Innesto 2019
 */
 
#define BROKER_IP    "10.0.0.90"
#define DEV_NAME     "mqttdevice"
#define MQTT_USER    "mqtt"
#define MQTT_PW      "password"

const char ssid[] = "Xf123456";
const char pass[] = "11050927";

#include <MQTT.h>

#ifdef ARDUINO_SAMD_MKRWIFI1010
#include <WiFiNINA.h>
#elif ARDUINO_SAMD_MKR1000
#include <WiFi101.h>
#elif ESP8266
#include <ESP8266WiFi.h>
#else
#error unknown board
#endif



WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;

void connect() {
  Serial.print("checking wifi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }

  Serial.print("\nconnecting...");
  while (!client.connect(DEV_NAME, MQTT_USER, MQTT_PW)) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("\nconnected!");
  client.subscribe("/sensors/message"); //SUBSCRIBE TO TOPIC /mesage_topic
}

void messageReceived(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  //
  // MQTT brokers usually use port 8883 for secure connections.
  client.begin(BROKER_IP, 1883, net);
  client.onMessage(messageReceived);
  connect();

}

void loop() {
  
  client.loop();
  if (!client.connected()) {
    connect();
  }

  String txt = "Hello World!";

  // publish a message roughly every second.
  if (millis() - lastMillis > 1000) {
    lastMillis = millis();
    client.publish("/sensors/message", txt); //PUBLISH TO TOPIC /altitude_topic MSG world
  }
    
}

Of course I also changed the topic in the other code for the Arduino that has the sensor

You should not use a leading slash in your MQTT topic, so subscribe to sensors/# not /sensors/#

In your Arduino code you could do something like this, though choose a system that matches your current and future plans:

#define DEV_NAME "garagedoor"
#define MQTT_BASE "sensors/"
#define MQTT_TOPIC MQTT_BASE DEV_NAME

Which makes MQTT_TOPIC on this device = sensors/garagedoor.

On another Arduino #define DEV_NAME "kitchen/temperature" will publish to sensors/kitchen/temperature

Subscribing to sensors/# will catch both of these topics.

So, like this?

/* This example shows how to use MQTT on the main dev boards on the market
 * HOW TO USE:
 * under connect method, add your subscribe channels.
 * under messageReceived (callback method) add actions to be done when a msg is received.
 * to publish, call client.publish(topic,msg)
 * in loop take care of using non-blocking method or it will corrupt.
 * 
 * Alberto Perro - Officine Innesto 2019
 */
 
#define BROKER_IP    "10.0.0.90"
#define DEV_NAME     "thermo"
#define MQTT_BASE    "sensors/"
#define MQTT_TOPIC   "sensors/thermo"
#define MQTT_USER    "mqtt"
#define MQTT_PW      "password"

const char ssid[] = "Xf123456";
const char pass[] = "11050927";

#include <MQTT.h>

#ifdef ARDUINO_SAMD_MKRWIFI1010
#include <WiFiNINA.h>
#elif ARDUINO_SAMD_MKR1000
#include <WiFi101.h>
#elif ESP8266
#include <ESP8266WiFi.h>
#else
#error unknown board
#endif
#include <Wire.h>
#include <Adafruit_MPL3115A2.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7); 
Adafruit_MPL3115A2 baro = Adafruit_MPL3115A2();

WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;

void connect() {
  Serial.print("checking wifi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }

  Serial.print("\nconnecting...");
  while (!client.connect(DEV_NAME, MQTT_USER, MQTT_PW)) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("\nconnected!");
  client.subscribe("sensors/temp"); //SUBSCRIBE TO TOPIC /altitude_topic
}

void messageReceived(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);

  // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
  // You need to set the IP address directly.
  //
  // MQTT brokers usually use port 8883 for secure connections.
  client.begin(BROKER_IP, 1883, net);
  client.onMessage(messageReceived);
  connect();

  lcd.begin(16,2);   // initializing the LCD 16 x 2
  lcd.setBacklightPin(3,POSITIVE); // Enable or Turn On the backlight 
  lcd.setBacklight(HIGH);
}

void loop() {
  if (! baro.begin()) {
    Serial.println("Couldnt find sensor");
    return;
  }
  
  float tempC = baro.getTemperature();
  Serial.print(tempC); Serial.println("*C");
  
  float altm = baro.getAltitude();
  Serial.print(altm/-6); Serial.println(" meters");
  
  client.loop();
  if (!client.connected()) {
    connect();
  }

  String txt = "Hello World!";
  
  char data[6];
  dtostrf(tempC, 5, 2, data);
  
  // publish a message roughly every second.
  if (millis() - lastMillis > 1000) {
    lastMillis = millis();
    client.publish("  sensors/temp", data); //PUBLISH TO TOPIC /altitude_topic MSG world
  }
    
  lcd.home();
  lcd.print(altm/-6);
  lcd.setCursor(0,1);
  lcd.print("Meters");
  lcd.setCursor(9,0);
  lcd.print(tempC);
  lcd.setCursor(9,1);
  lcd.print("Celsius");
  lcd.setCursor(7,0);
  lcd.print("|");
  lcd.setCursor(7,1);
  lcd.print("|");
}

I have another question how do I edit how the msg.payload will be displayed? For example, I tried it before and it was like this:

Message:
Temperature: Hello World!ºC

One reason to use #define is to avoid embedding "magic" strings and numbers in the body of your code.
So you should define the MQTT topic you will publish to just once at the top of the program.

Your code has an MQTT topic defined (sensors/thermo) but you don't use it; down in your loop() you have

client.publish("  sensors/temp", data);

It should be client.publish( MQTT_TOPIC, data);

You have not followed my suggestion for the definition of MQTT_TOPIC as an automatic concatenation of MQTT_BASE and DEV_NAME. Your version should work fine.

Although I see where you define a string as "Hello World!", I can't see from your code how that or ºC gets into msg.payload.

Personally, I wouldn't include units in the mqtt message since I know I always use Celsius.

Ok thank you