Convert any data type to char to publish via MQTT

I am pulling my hair out on this one...

I have written Modbus reader for an Arduino MEGA2560 board; and to see what I am reading, I Serial.print-ed some 106 values.
So far so good.

The values represent anything from single digit to uint16_t to float.

The MQTT publish function requires (const char *topic, const char *msg).

How do I convert these different values to the required char array?

For example a bunch of registers being read:

void read_ac_load ()
{
    static uint8_t result = 1;

    result = modbus.readHoldingRegisters(0x1f44, 4);

    if (result == modbus.ku8MBSuccess)
    {
        Serial.print(F("AC_Load_Power...........: "));
        Serial.println((int)modbus.getResponseBuffer(0x00) * 10);
        Serial.print(F("AC_Load_Voltage.........: "));
        Serial.println((int)modbus.getResponseBuffer(0x01) / 10.0, 1);
        Serial.print(F("AC_Load_Frequency.......: "));
        Serial.println((int)modbus.getResponseBuffer(0x02) / 10.0, 1);
        Serial.print(F("AC_Load_Energy..........: "));
        Serial.println((int)modbus.getResponseBuffer(0x03) / 10.0, 1);

    }
    else
    {
        display_error(result);
    }

    return;
}

I want to publish the topic (modbus1/data), the register address (8123), then the value (-350.4); like so:

modbus1/data/8123 -350.4

Do I really need to do (repeat) acrobatics like:

dtostrf(((int)modbus.getResponseBuffer(0x02) / 10.0, 1), 6, 1, g_tmp_buffer);

... and then copy the register to the topic, then the value..

This is cumbersome at best, more so for 106 registers.

I pondered over the Serial.print() function; it takes any data type. Can this be replicated?

In essence: now that I have tested that I get the values, I will get rid of the Serial.print() statements and replace these with MQTT publish commands. But building the strings, without using the String function, seems very cumbersome.

I thought I have one function and loop through the register. But this is not possible do to the different data conversions; e.g. some values need to be multiplied, others need to be divided.

Any hints appreciated.

Which MQTT library are you using ? It’s likely supporting sending a binary buffer

size_t write(const uint8_t *buf, size_t size);

you might consider using sprintf(), strcat() and unavoidably dtostrf() to construct a complete string that can be display on the serial monitor for debugging as well as transmitted over the MQTT interface.

surprised to think that registers are floats. but a sub-function with a register argument can be used to construct a sub-string which is strcat'd() to tx buffer. That sub-function could specify the register, a label and possibly an output buffer to which the generated sub-string is concatenated to

1 Like

Failing that, you could create a class that inherits from Print. So, it would know how to "print" everything that the Print class does. Its implementation of write() would store the characters in an internal buffer. You could then pass a pointer to that buffer to the MQTT publish function.

is one way I do the thing,


void fDoBME ( void *pvParameters )
{
  if (!bme.begin()) {
    log_i("Could not find a valid BME680 sensor, check wiring!");
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms
  TickType_t xLastWakeTime    = xTaskGetTickCount();
  const TickType_t xFrequency = 1000 * 15;
  //wait for a mqtt connection
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  String bmeInfo = "";
  bmeInfo.reserve( 100 );
  int Count = 0;
  for ( ;; )
  {
    if ( Count < 2 )
    {
      bmeInfo.concat( String((bme.readTemperature() * 1.8f) + 32.0f, 4) ); // (Celsius x 1.8) + 32
      bmeInfo.concat( "," );
      bmeInfo.concat( String( bme.readPressure() / 133.3223684f, 4) ); //mmHg
      bmeInfo.concat( "," );
      bmeInfo.concat( String(bme.readHumidity(), 3) );
      bmeInfo.concat( "," );
      bmeInfo.concat( String(bme.readGas()) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicOutside, bmeInfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      Count++;
      //log_i( "Tick count is %d", Count );
      bmeInfo = "";
    } else {
       // log_i ( "sleepy time");
      xEventGroupSetBits( eg, evtSleepyTime );
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  } // for loop
  vTaskDelete ( NULL );
} // void fDoBME ( void *pvParameters )

Why unavoidably ?

if you're trying to format a float in a c-string, (e.g. %f) instead of simply outputting it (e.g. Serial.print (x, 4) i know of no alternative.

as i mentioned, i believe there's a benefit to creating the string that can be output both on the serial monitor as well as MQTT transmitted

What about sprintf(), bearing in mind we have no idea what board is being used

my understanding is the "%f" option is not supported on Arduino

But it is supported on ESP32, hence me saying

yes it is. i use it.

but i wasn't aware that the OP was using an esp32 and assumed he was using dtostr() because he couldn't use it in sprintf()

I don't think we know what board @MaxG is using, but it would be helpful to know

@MaxG which board are you using, because it may have a bearing on the help given

I am writing this for an Arduino MEGA2560 board.
... and am using:

  PubSubClient.cpp - A simple client for MQTT.
  Nick O'Leary
  http://knolleary.net

The PubSubClient class inherits from Print:

class PubSubClient : public Print {

It can handle any data type that the Print class can.

So can you send binary data or do you need text?

I am in a different time zone. After I wrote my O.P. I went to bed; this morning I answered the board question... and on second thought, I lost it yesterday. Wondering why I had to write so much code to handle these different data types.

There was the questions about the data types. Well in essence, the Modbus returns everything as uint16_t or int16_t. However, it requires either a multiplication or division, mostly by factor/divisor of 10, with 0, 1, or 2 decimal places to present the final value.

I am the occasional programmer; I may program something over three days and get back into the swing of things, but data type conversions or more so string operations are doing my head in, pointers is something I understood once; and then I program nothing for a year or many months. Hence, the lack of fluency and understanding for more complex constructs.

I reckon I could do away with a function where I pass in the 16bit (u)int, a boolean for multiplication/division, and the decimal places (0, 1, 2)... and build the char/string to publish with MQTT.

Hmm... I need to send the topic (text) and the value (which is a char/string).
Like: modbus/data/8123 -350.3

The receivers are different subscribers to the whatever topic (represented as the modbus register address.

As a general comment: I have some 20 controllers (Arduinos) doing all sorts of chores.
All speak MQTT. In clear text, as it allows for easy troubleshooting and is human-readable.
Hence, this architecture I have to (and want to) stick with.

This, I will think about.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.