This is a bit cumbersome, and there’s a better way to do it, by re-structuring your MQTT topics
I tend to organise my topics by location (room), function and device - but it doesnt really matte how you do this so long as it makes sense to you.
At device level I tend to organise my topics in a way that allows me to use wildcards to subscribe to them. Take this example of a device that performs two functions - it has an IR transmitter and also a temperature and humidity sensor attached. This allows me top control an aircon unit and ceiling fans via Infra Red, and also send current temperature and humidity readings to MQTT to allow me to know if I need to change the settings of these devices relative to the target temperature I’ve defined.
My device is located in the upstairs lounge, so I start with this as my MQTT topic…
Upstairs/Lounge/IR_Tx_and_Temp_Sensor/
at this ‘root’ level for my device I get the device to publish lots of high-level info. Some of thsi is published once at start-up with a Retain flag, other info is updated at 5 second intervals…
- MQTT_Client_ID (has to be unique for each device, so uses the ESP chip ID)
- Status (Dead/Alive from the Last Will and Testament)
- Filename (Helps me find the .ino file again)
- Compiled_Date
- Compiled_Time
- MAC_Address
- IP_Address
- WiFi_Connect_Count
- MQTT_Connect_Count
- RSSI
- Free_RAM
- Uptime
Having these at this root
level for the device makes it easy to see the info at a glance.
I then have a CMD_IN topic, where all of my incoming commands will be directed, and this will have sub-topics that relate to the things that are controlled by the device - in your case it might look like this…
CMD_IN/Relay1/Power
CMD_IN/Relay2/Power
CMD_IN/Relay3/Power
CMD_IN/Relay4/Power
CMD_IN/Relay5/Power
CMD_IN/Relay6/Power
CMD_IN/Relay7/Power
CMD_IN/Relay8/Power
This allows you to use a wildcard to subscribe to the CMD_IN topic and all of its sub topics, like this…
client.subscribe("Upstairs/Lounge/IR_Tx_and_Temp_Sensor/CMD_IN/#");
where the #
wildcard means subscribe to all child topics.
I also have a DATA_OUT topic that I use to send feedback data about the current state of the thing I’m controlling (in your case a relay) that comes from actually reading the state of the pin I’m controlling. This tells me if the “on” command actually resulted in the device’s pin being pulled LOW.
I also use this part of the topic structure to add a memo note that gives me more info about the relay, like this
DATA_OUT/Relay_1/Power = 1
DATA_OUT/Relay_1/Memo = Relay used to control the lights near the TV
I adopt this structure because if you subscribe to a topic and then publish to that same topic the you’ll get an infinite feedback loop.
The CMD_IN topic and its children is only for incoming messages
The DATA_OUT topic is never subscribed to by the device, and this (or the ‘root’ topic) is where the device writes data to so that it is passed to Node-Red.
All of this might seem like a lot of effort just to avoid subscribing to eight topics instead of one, but at some point in future you’ll add a new topic to Node-Red and write the callback handler for it, then spend an hour or two trying to work-out why it isn’t working. Then you’ll eventually realise that you forgot to subscribe to the new topic!
I also take a different approach with my callback handler for incoming MQTT messages. I hate working with char
variables, so turn them into strings as quickly as possible. It’s then very easy to turn tehse into integers etc if you wat to, but it’s not always necessary.
I also split the message into topic and payload and use these variables in my if
statements later…
void MQTTcallback(char* topic, byte* payload, unsigned int length)
{
Serial.print(F("Message arrived ["));
Serial.print(topic);
Serial.print(F("] ["));
for (unsigned int i=0;i<length;i++)
{
Serial.print((char)payload[i]);
}
Serial.println(F("]"));
String msg_topic = String((char *)topic);
String msg_payload = String((char *)payload);
msg_payload.remove(length); // Trim any unwanted characters off the end of the string
// We now have two string variables, 'msg_topic' and 'msg_payload' that we can use in 'if' statements below...
if (msg_topic==base_mqtt_topic + "/CMD_IN/Fan/OnOff")
{
if (msg_payload == "Off")
{
Fan_Sender.sendRaw(rawFan_Off, 143, khz);
}
If you want to use the Last Will and Testament feature then you need to have a more sophisticated client connect command…
/*
MQTT Connection syntax:
boolean connect (client_id, username, password, willTopic, willQoS, willRetain, willMessage)
Connects the client with a Will message, username and password specified.
Parameters
client_id : the client ID to use when connecting to the server.
username : the username to use. If NULL, no username or password is used (const char[])
password : the password to use. If NULL, no password is used (const char[])
willTopic : the topic to be used by the will message (const char[])
willQoS : the quality of service to be used by the will message (int : 0,1 or 2)
willRetain : whether the will should be published with the retain flag (int : 0 or 1)
willMessage : the payload of the will message (const char[])
Returns
false - connection failed.
true - connection succeeded
*/
if(MQTTclient.connect(mqtt_client_id.c_str(), mqtt_username, mqtt_password, (base_mqtt_topic + "/Status").c_str(),0, 1, "Dead"))
{
// We get here if the connection was successful...
The “Dead” part at the end of the connect command, which really translates to “/Status/Dead”, tells the MQTT server server to write the word “Dead” to the “Status” topic if the device disappears off the radar.
I then publish “Alive” to the “/Status” topic when I’ve connected to the MQTT server with a Retained flag so that I can see at a glance whether the device is connected or not…
MQTTclient.publish((base_mqtt_topic + "/Status").c_str(),"Alive",true);
As you can see, I have a string variable called “base_mqtt_topic” that set once in the sketch and it’s used as the prefix for all of my subsequent MQTT Publish commands…
String base_mqtt_topic = "Upstairs/Lounge/IR_Tx_and_Temp_Sensor/";
Hope this helps.
Pete.