Dynamic MQTT Topics


#1

I am loving this platform!
I feel extremely challenged as of late. But I swear by Particle Product and platform.

On to the problem I am desperately wanting to solve.
I am wanting to include System.deviceID(); in my MQTT Topics.
I have tried several ways to do this without luck. This would be my way of tracking what data is coming from what device. A unique Identifier if you will.
The format I am going for would be as follows: “System.deviceID();/monitorPub/status”.

I am using wildcards on mosquitto to gather all topics.


#2

What exactly is the problem?

Would this do?

  char topic[64];
  snprintf(topic, sizeof(topic), "%s/monitorPub/status", (const char*)System.deviceID());

#3

Would this format be relatively correct? It is not compiling properly.

client.publish(char D1_Value_Topic, D1_Value);

#4

What’s the error message?

I’d assume you don’t want char in that line of code.


#5

This might help. Below id my original code.

// Analog Input From Flight Sensor for Tank Level
        int D1_Int = sensor.readRangeSingleMillimeters();
        D1_Value = String (D1_Int);
        client.publish("280041000C47363330353437/fluidmonitorPub/D1_Value", D1_Value);

The “280041000C47363330353437” is the device ID


#6

Have you tried replaceing your literal topic with my topic variable?

Also have you tried applying this hint

When I asked for the error message I actually expected it to be in the next response - instead you provided some other code.


#7

I do apologize.
When I ran your code as it was written I did not integrate it into my client.subscribe yet.
It did return this error:

.ino:103:9: error: expected constructor, destructor, or type conversion before '(' token
 snprintf(topic, sizeof(topic), "%s/monitorPub/status", (const char*)System.deviceID());
         ^

#8

That code builds just fine the for me.
Without seeing more of your code, could it be that you already have an object called topic?

If you post a SHARE THIS REVISION link I can have a look at the actual reason for that error.


#9

I am not familiar with SHARE THIS REVISION. Please explain.


#10

I guess you are using Web IDE, right?
Then you should see this button

You click it and copy the provided link and post it here. This way someone can easily create a full copy of your project.


#11

Of course.
I am using the VSCode Workbench.


#12

In that case, you may need to copy/paste the source code into the post.


#13
/////////////////////////////////////// Everything Below This Point is Includes ///////////////////////////////////////


#include "VL53L0X.h"
#include <SoftAPLib.h>
#include "MQTT.h"


///////////////////////////////// Everything Below This Point is MQTT Broker Settings /////////////////////////////////


    void callback(char* topic, byte* payload, unsigned int length);
    //if want to use IP address,
    byte server[] = { 0,0,0,0 };
    MQTT client(server, 1883, callback);
    //want to use domain name,
    //exp) iot.eclipse.org is Eclipse Open MQTT Broker: https://iot.eclipse.org/getting-started
    //MQTT client("iot.eclipse.org", 1883, callback);


/////////////////////////////////////// Everything Below This Point is Functions ///////////////////////////////////////


    VL53L0X sensor;
    // Uncomment this line to use long range mode. This
    // increases the sensitivity of the sensor and extends its
    // potential range, but increases the likelihood of getting
    // an inaccurate reading because of reflections from objects
    // other than the intended target. It works best in dark
    // conditions.
    #define LONG_RANGE

    // Uncomment ONE of these two lines to get
    // - higher speed at the cost of lower accuracy OR
    // - higher accuracy at the cost of lower speed

    //#define HIGH_SPEED
    //#define HIGH_ACCURACY

// Initialize objects from the lib
STARTUP(softap_set_application_page_handler(SoftAPLib::getPage, nullptr));

void callback(char* topic, byte* payload, unsigned int length) {
    char p[length + 1];
    memcpy(p, payload, length);
    p[length] = NULL;

    //strcmp is String Compare. It compares two strings (p, "RED") p=payload
    if (!strcmp(p, "RED"))
        RGB.color(255, 0, 0);
    else if (!strcmp(p, "GREEN"))
        RGB.color(0, 255, 0);
    else if (!strcmp(p, "BLUE"))
        RGB.color(0, 0, 255);
    else if (!strcmp(p, "MAGENTA"))
        RGB.color(255, 0, 255);
    else if (!strcmp(p, "OFF"))
        RGB.color(0, 0, 0);
    else if (!strcmp(p, "0"))
        RGB.color(255, 255, 0);
    else if (!strcmp(p, "1"))
        RGB.color(0, 255, 255);
    else
        RGB.color(255, 255, 255);
}


////////////////////// Everything Below This Point is Global Variables, Definitions, Declarations //////////////////////


// Digital Input Integers
int D0_Int; // SCL Pin for Flight Sensor
int D1_Int; // SDA Pin for Flight Sensor
int D2_Int = 0; // Initial Button State of "0"
int D3_Int; // Button Input
// Digital Output Integers
int D4_Int;
int D5_Int;
int D6_Int;
int D7_Int;

// Analog Input Integers
int A1_Int; // Analog Input for Photoresistor
int A2_Int; // Analog Output for Photoresistor
int A3_Int;
// Analog Output Integers
int A4_Int;
int A5_Int;
int A6_Int;

unsigned long old_time = 0;

char topic[64];
snprintf(topic, sizeof(topic), "%s/monitorPub/D1_Value", (const char*)System.deviceID());

    // Digital Input String Values
    String D0_Value;
    String D1_Value;
    String D2_Value;
    String D3_Value;
    // Digital Output String Values
    String D4_Value;
    String D5_Value;
    String D6_Value;
    String D7_Value;

    // Analog Input String Values
    String A1_Value;
    String A2_Value;
    String A3_Value;
    // Analog Output String Values
    String A4_Value;
    String A5_Value;
    String A6_Value;

    String myID = System.deviceID();

        // Digital Input Pins
        #define D0Pin D0
        #define D1Pin D1
        #define D2Pin D2
        #define D3Pin D3
        // Digital Output Pins
        #define D4Pin D4
        #define D5Pin D5
        #define D6Pin D6
        #define D7Pin D7

        // Analog Input Pins
        #define A0Pin A0
        #define A1Pin A1
        #define A2Pin A2
        // Analog Output Pins
        #define A3Pin A3
        #define A4Pin A4
        #define A5Pin A5


/////////////////////////////////////// Everything Below This Point is Setup ///////////////////////////////////////


void setup() {

    Serial.begin(9600);

    // Digital Input Pins
    pinMode(D0, INPUT);
    pinMode(D1, INPUT);
    pinMode(D2, INPUT);
    pinMode(D3, INPUT);
    // Digital Output Pins
    pinMode(D4, OUTPUT);
    pinMode(D5, OUTPUT);
    pinMode(D6, OUTPUT);
    pinMode(D7, OUTPUT);

    // Analog Input Pins
    pinMode(A0, INPUT);
    pinMode(A1, INPUT);
    pinMode(A2, OUTPUT);
    // Analog Output Pins
    pinMode(A3, OUTPUT);
    pinMode(A4, OUTPUT);
    pinMode(A5, OUTPUT);

        // Digital Input Pins
        digitalWrite(D0, LOW);
        digitalWrite(D1, LOW);
        digitalWrite(D2, LOW);
        digitalWrite(D3, LOW);
        digitalWrite(D4, LOW);
        // Digital Output Pins
        digitalWrite(D5, LOW);
        digitalWrite(D6, LOW);
        digitalWrite(D7, LOW);

        // Analog Input Pins
        digitalWrite(A0, LOW);
        digitalWrite(A1, LOW);
        digitalWrite(A2, HIGH);
        // Analog Output Pins
        digitalWrite(A3, LOW);
        digitalWrite(A4, LOW);
        digitalWrite(A5, LOW);

    // Allow RGB LED Control
    RGB.control(true);

    // connect to the server
    client.connect("client");

    // publish/subscribe
    if (client.isConnected()) {
        client.publish("280041000C47363330353437/fluidmonitorPub/status", "Connected to Mosquitto");
        client.publish("280041000C47363330353437/fluidmonitorPub/deviceid", myID);
        client.subscribe("280041000C47363330353437/fluidmonitorSub/control");
        client.subscribe("280041000C47363330353437/fluidmonitorSub/schedule");
    }
}


/////////////////////////////////////// Everything Below This Point is Loop ///////////////////////////////////////


void loop() {
    flightbegin();
    if (client.isConnected())
        client.loop();

    if(millis() - old_time >= 60000){
        // Analog Input From Flight Sensor for Tank Level
        int D1_Int = sensor.readRangeSingleMillimeters();
        D1_Value = String (D1_Int);
        client.publish(topic, D1_Value);
        // Analog Input From Photoresistor for Light Brightness
        int A1_Int = analogRead(A1Pin);
        A1_Value = String (A1_Int);
        client.publish("280041000C47363330353437/fluidmonitorPub/A1_Value", A1_Value);
    old_time = millis(); //update old_time to current millis()
    }

    // Digital Input from Push Buton
    D2_Int = digitalRead(D2Pin);
        if (D2_Int == HIGH) {
        client.publish("280041000C47363330353437/fluidmonitorPub/D2_Value","1");
        }

        // Deep sleep for 60 minutes to save battery
        //System.sleep(SLEEP_MODE_DEEP,1 * 60);
}


  void flightbegin() {// VL53L0X Sensor is powered on and initialized.
    Wire.begin();
    sensor.init();
    sensor.setTimeout(2000);

    #if defined LONG_RANGE
    // lower the return signal rate limit (default is 0.25 MCPS)
    sensor.setSignalRateLimit(0.1);
    // increase laser pulse periods (defaults are 14 and 10 PCLKs)
    sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
    sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);
    #endif

    #if defined HIGH_SPEED
    // reduce timing budget to 20 ms (default is about 33 ms)
    sensor.setMeasurementTimingBudget(20000);
    #elif defined HIGH_ACCURACY
    // increase timing budget to 200 ms
    sensor.setMeasurementTimingBudget(200000);
    #endif
  }

#14

OK, now I see the problem.
The snprintf() line is an active line of code and as such it has no business outside of a containing function.
On the “root level” of your code only declarations/definitions are allowed but no active code.

You need to move that line into setup() (or any other function that will be allowed to execute it).

BTW, seeing your other topics, it might be worth changing my code to this

snprintf(topic, sizeof(topic), "%s/fluidmonitor%s/%s", (const char*)System.deviceID(), "Pub", "A1_Value");

The latter two parameters can be any of your options

  • [ Pub | Sub | ... ]
  • [ status | deviceid | control | schedule | A1_Value | D2_Value | ... ]

This way you can “build” the desired topic on-the-fly and provide it to your function call like

  snprintf(topic, sizeof(topic), "%s/fluidmonitor%s/%s", (const char*)System.deviceID(), "Pub", "A1_Value");
  client.publish(topic, A1_Value);
  // instead of
  // client.publish("280041000C47363330353437/fluidmonitorPub/A1_Value", A1_Value);

#15

This is miraculous! Thank you so much @ScruffR. I am fairly a newbie as you can tell. But I literally jump for joy when I run across your posts because I know I’ll make head way and learn!

Thanks a ton !!!


#16

Good evening. Have you tested this with an MQTT Broker before? I am using your code, which compiled well. Although it does not seem to transmit the messages to Mosquitto.

May you have an idea? I am not sure of other ways to accomplish this.


#17

Corrected! I put the below code in setup. It runs amazingly!

snprintf(topic, sizeof(topic), "%s/fluidmonitor%s/%s", (const char*)System.deviceID(), "Pub", "A1_Value");
  

I suppose each “topic” needs to be unique. They did not run on-the-fly in the loop. Your code was amazing!


#18

Just to clarify
char topic[64] is just a global array that can hold a string (temporarily or permanently).
If you put above snprintf() line in setup() and don’t call any snprintf(topic, ...) line again, the one topic (name) you “created” in setup() will stay there for the entire duration of your code running.
But if you call it again snprintf(topic, ...), the topic string will be overwritten to be re-used for any other client.xxx() call.

Alternatively you could use several topicXXXX[64] variables (one per topic) or an array of strings (topic[6][64]) and populate all of them in setup() and then use them as needed.

However, what exactly did you mean by: “did not run on-the-fly in loop”?
Can you post the code where you tried it?
You may add a Serial.println(topic) after the snprintf() statement to check what the resulting topic string was.