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.