String Manipulation giving unexpected results

My Arduino project is an electrical monitoring system using: Arduino One (plus Ethernet Shield), A Voltage and Ampere sensors (0-5V), and an external server in DigitalOcean. My sketch collect and posts the information to a Real Time database (InfluxDB) and representing the collected data using Grafana. These projects are really a good combination, easy to use and very produce a very nce result.

Mi first test has been collecting the voltage inside my house, and it is working like a charm. So it is time to expand the adquisition of a new variable (Amp), and including the computation of the instant power, and finally posting all the information to my server. At the end I have included the full Arduino sketch.

However with three variables, I am having big issues trying to build the JSON object that has to be HTTP Posted to the real time database server. Unfotunately I am not an expert in Processing/C++, I find Pyhon much more string friendly, and I am lost.

In my code I am trying to build a JSON object according InfluxDB specifications, so I tried the following code with two variables (Vac and Iac):

String data;
   data = "[{" 
           "\"name\":\"VoltAmpPot\","
           "\"columns\":[\"VacRMS\""
                      ",\"IacRMS\""
                     // ",\"Pac\""
                      "],"
           "\"points\":[[" + String(valueVolt) + ","
                           //+ String(valueAmp) + ","
                           //+ String(valuekW) + ","
                      "]]"
           "}]";
    
  Serial.println(data);

This is working, I can deal with up to two realtime variables. In the example above valueVolt, valueAmp and valuekW are float elements. The result in the serial console is more or less the following:

[{"name":"VoltAmpPot","columns":["VacRMS","IacRMS"],"points":[[168.91,]]}]
[{"name":"VoltAmpPot","columns":["VacRMS","IacRMS"],"points":[[145.60,]]}]
[{"name":"VoltAmpPot","columns":["VacRMS","IacRMS"],"points":[[139.88,]]}]
[{"name":"VoltAmpPot","columns":["VacRMS","IacRMS"],"points":[[135.92,]]}]
etc

However adding the three variables (Voltage, Intensity and Power), It is not working, when I print the object, the String data is empty, It has not been built.

String data;
   data = "[{" 
           "\"name\":\"VoltAmpPot\","
           "\"columns\":[\"VacRMS\""
                      ",\"IacRMS\""
                      ",\"Pac\""
                      "],"
           "\"points\":[[" + String(valueVolt) + ","
                                + String(valueAmp) + ","
                                + String(sensorAmp) + ","
                           "]]"
           "}]";
    
  Serial.println(data);

No results in the serial console. Why can I expand the JSON object to include the three varaibles?, any hint?. I know there are another ways to concatenate strings, as seen in the different examples in the IDE, however in my way I find everything more direct and clear to make later changes.

Thank you in advanced!

full code, mainly based mainly in a Thingspeak.com sketch :

// Librerias Ethernet + HTTP
#include <Ethernet.h>
#include <SPI.h>

// Definicion Variables Globales
unsigned long Tiempo_UpdatePOST = 1*1000; // 1 Sg para HTTP POST
unsigned long Tiempo_UpdateLED = 500;     // 0.5 Sg para LED
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;

int VoltPin = A0; 
int sensorVolt = 0; // Voltaje Integer (0-1023)
float valueVolt = 0.0; // Voltaje VacRMS (0-450V)   

int AmpPin = A1;   
int sensorAmp = 0 ; // Amperaje Integer (0-1023)
float valueAmp = 0.0; // Amperios (0-25A) 

float valuekW = 0.0; // kW = Volt (450V) x Amp (25A) = 11.250 kW

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0xFE, 0xED };

EthernetClient client;
String Server[] = "XXXXXX";

long int lastConnectionTime = 0;  
boolean lastConnected = false; 
int failedCounter = 0;        

void setup() { 
 
  // 1. Inicializacin de PINs Digitales
  //pinMode(ledPin, OUTPUT);
    
  // 2. Inicializacin puerto Serie
  Serial.begin(9600);
  Serial.println("--------------------- SETUP -------------------");   
  Serial.println("");
  Serial.println("--- 1. SETUP: Inicializacion Serial completada ---");
  Serial.println("");
    
  // 3. Iniciacion Ethernet Stack
  startEthernet();
  
  Serial.println("------------------ FIN SETUP ------------------");   
  Serial.println("");
}


void loop () {
  
  // 1. Lectura RED ETHERNET
  if (client.available())   
  {
     char c=client.read();
     Serial.print(c);
  }
  
  if (!client.connected() && lastConnected) { 
     Serial.println("...Desconectado de Red Ethernet");
     client.stop();
  }
  
  // 2. Calculo de Variables / datos a POSTear
  sensorVolt = analogRead(VoltPin);
  valueVolt = (float(sensorVolt) / 1023.0 ) * 450.0 ; // Rango Sensor 0-450V
  sensorAmp = analogRead(AmpPin);
  valueAmp = (float(sensorAmp) / 1023.0 ) * 25.0 ; // Rango Sensor 0-50A
  valuekW = valueAmp * valueVolt;
  
   String data;
   data = "[{" 
           "\"name\":\"VoltAmpPot\","
           "\"columns\":[\"VacRMS\""
                      ",\"IacRMS\""
                     // ",\"Pac\""
                      "],"
           "\"points\":[[" + String(valueVolt) + ","
                           //+ String(valueAmp) + ","
                           //+ String(sensorAmp) + ","
                      "]]"
           "}]";
    
  Serial.println(data);
  
  // 3. HTTP POST :
  
  if (!client.connected() && ((millis() - lastConnectionTime) > Tiempo_UpdatePOST))
  {
    updateHTTPPOST(data);  // Funcion POSTEA datos a servidor
  }
   
  // Resetear la conexion a Internet si ha habido 3 fallos de conexion
  if (failedCounter > 3) {
    failedCounter = 0;
    startEthernet();
  } 
  
  lastConnected = client.connected();

}

  
/////////////////////////////
// FUNCION:startEthernet() //
/////////////////////////////
void startEthernet()
{
  client.stop();
  Serial.println("--- 2. SETUP: startethernet(): Conectando red Ethernet ...");
  delay(1000);
  
   
  if (Ethernet.begin(mac) == 0) {
    Serial.println("--- 2. SETUP: startethernet(): Fallo Configuracion Ethernet ---");
    Serial.println("");
  }
  else{
    Serial.print("--- 2. SETUP: startethernet(): Conexion DHCP, IP: "); 
    Serial.print(Ethernet.localIP());
    Serial.println(" ---"); 
    Serial.println();
  }
}


/////////////////////////////
//   FUNCION: HTTPpost()   //
/////////////////////////////
void updateHTTPPOST(String POSTdata)
{
  
  if (client.connect("XXXXXXX", 8086)) // Conexion a Servidor
  {
    Serial.println("--------------START POST-------------------------");
    // Cabeceras (HTTP POST Headers)
    client.print("POST /db/test_voltios/series?u=root&p=root HTTP/1.1\n");  
          Serial.print("POST /db/test_VAkW/series?u=root&p=root HTTP/1.1\n");  
    client.println("Host: XXXXXX");  
          Serial.println("Host: XXXXX");  
    client.print("User-Agent: Arduino/1.0\n");  
          Serial.print("User-Agent: Arduino/1.0\n");  
    client.print("Connection: Close\n"); 
          Serial.print("Connection: Close\n");
    client.print("Content-Type: application/x-www-form-urlencoded\n");  
          Serial.print("Content-Type: application/x-www-form-urlencoded\n");  
    client.print("Content-Length: ");
          Serial.print("Content-Length: ");
    client.print(POSTdata.length());
          Serial.print(POSTdata.length());
    client.print("\n\n");   // Se necesitan dos Line breaker para cerrar header
          Serial.print("\n\n");
          
    // Cuerpo del Mensaje (HTTP POST Body)
    client.print(POSTdata);
          Serial.print(POSTdata);
    
    lastConnectionTime = millis();

    // Chequeo sigo conectado -> POST con exito
    if (client.connected()) 
    { 
      Serial.println();
      Serial.println("----------------FIN POST-------------------------");
      Serial.println();
      Serial.println();
      failedCounter=0;
    } else {
      failedCounter++;
      Serial.println("Conexion con Servidor perdida");
      Serial.print("Conexion perdida Nº ");
      Serial.println(String(failedCounter));
      Serial.println();
    }
    
  } 
  else  // No he sido capaz de conectar con el Servidor
  {
     failedCounter++;
     Serial.println("No puedo Conectarme a Servidor");
     Serial.print("Conexion perdida Num. ");
     Serial.println(String(failedCounter));
     Serial.println();
     lastConnectionTime = millis();
  }
}

How much memory do you have available? I'm guessing that all that String usage is killing your memory. There is no reason to use Strings. Everything you are trying to do can be done using strings, with no dynamic memory allocation fragmenting memory.

http://playground.arduino.cc/Code/AvailableMemory

PaulS: How much memory do you have available? I'm guessing that all that String usage is killing your memory. There is no reason to use Strings. Everything you are trying to do can be done using strings, with no dynamic memory allocation fragmenting memory.

http://playground.arduino.cc/Code/AvailableMemory

Thank you for this hint. It seems you are right, I stripped the whole program leaving only the String concatenations and it works!.

The solution seems to be working with char types, instead of String types. I have been working with this new approach and I found that working with lower level char type is really a pain. I have had issues with converting a float to a char, but I solved it with the dtostrf function.

However I can not add/concatenate a fiexed char, with a variable char (Voltios), the following sketch does not work:

void loop() {

 sensorVolt = analogRead(VoltPin);
 valueVolt = (float(sensorVolt) / 1023.0 ) * 450.0 ; // Rango Sensor 0-450V
 dtostrf(valueVolt, 5, 2, Voltios);
 Serial.println(Voltios);
 
 char data[] = "[{"
                 "\"name\":\"Test_elec\","
                  "\"columns\":[\"VacRMS\","
                   "\"Iac\","
                   "\"Pac\"]," 
                  "\"points\":[[" + Voltios; //+ "," ; ///
 
 Serial.println(data);

The Compilation result is the following:

kk1.ino: In function 'void loop()':
kk1.ino:49:38: error: invalid operands of types 'const char [66]' and 'char [7]' to binary 'operator+'
Error de compilación

Error is clear, and there are plenty of Posts with this kind of problems, but I don not know how to go ahead...

You CAN allocate an array of the proper size. YOU count the elements of the array, instead of expecting the compiler to.

char crapToSend[100] = "[{"
                 "\"name\":\"Test_elec\","
                  "\"columns\":[\"VacRMS\","
                   "\"Iac\","
                   "\"Pac\"]," 
                  "\"points\":[[";
strcat(crapToSend, Voltios);
strcat(crapToSend, "whatever else you need");

If you use char pointers and arrays, then yes you have to start thinking about memory and what the character functions actually do. It's these details that the String class takes care of for you.

Since JSON ignores white space, what I would suggest is building your JSON string with enough blank space to accommodate the numbers that you want to print into it. Then use itoa to convert the number into a string (in some scrap space), and memcpy to paste that number into the JSON string at the correct spot. Be sure to blank out the location to handle the case where the number you want to put there is shorter than the previous time you did this!

Yes, this involves counting characters and lining up things properly. It's a pain, and that's low-level close-to-the-metal programming for you.

Ok, dude - here is a working example. I stress that using the libc string functrions is not the same as using the C++ string class.

// this sketch relies on the fact that dtostre() will 
/// always produce a number string <20 bytes long.

char data[] = "[{"
              "\"name\":\"VoltAmpPot\","
              "\"columns\":[\"VacRMS\""
              ",\"IacRMS\""
              ",\"Pac\""
              "],"
              "\"points\":[[ "
              "******VALUE_VOLT****, " // 20 characters
              "******VALUE_AMP*****, " // 20 characters
              "******VALUE_KW******" // 20 characters
              "]]"
              "}]";

char *VALUE_VOLT_TEMPLATE;
char *VALUE_AMP_TEMPLATE;
char *VALUE_KW_TEMPLATE;
const int TEMPLATE_LEN = 20;

void setup() {
  Serial.begin(9600);
  Serial.println("5 second delay to give me time to open the serial monitor");
  for (int i = 5; i >= 0; i--) {
    delay(1000);
    Serial.print(i);
    Serial.print(' ');
  }
  Serial.println();
  
  // end of 5 second delay, start of demo
  
  // Setup code. Use strstr to find the template strings. This allows us
  // to bypass the messy business of counting the characters

  VALUE_VOLT_TEMPLATE = strstr(data, "******VALUE_VOLT****");
  VALUE_AMP_TEMPLATE = strstr(data, "******VALUE_AMP*****");
  VALUE_KW_TEMPLATE = strstr(data, "******VALUE_KW******");

  // template code. I will just use the function to spit out some sample
  // values. This also demonstrates the limits of the precision
  // of the output

  // ok!
  setupJSON(0, 10, -1);
  Serial.println(data);

  setupJSON(0, -1, 0);
  Serial.println(data);

  setupJSON(123456.56789, -5678.3, 3e-9);
  Serial.println(data);

}

void setupJSON(float volts, float amps, float kw) {
  // clear
  memset(VALUE_VOLT_TEMPLATE, ' ', TEMPLATE_LEN);
  memset(VALUE_AMP_TEMPLATE, ' ', TEMPLATE_LEN);
  memset(VALUE_KW_TEMPLATE, ' ', TEMPLATE_LEN);

  // the JSON spec permits exponent form for numbers, so we'll use that
  // the advantage is that it always has a consistent length
  dtostre(volts, VALUE_VOLT_TEMPLATE, TEMPLATE_LEN - 5, DTOSTR_ALWAYS_SIGN);
  dtostre(amps, VALUE_AMP_TEMPLATE, TEMPLATE_LEN - 5, DTOSTR_ALWAYS_SIGN);
  dtostre(kw, VALUE_KW_TEMPLATE, TEMPLATE_LEN - 5, DTOSTR_ALWAYS_SIGN);

  // zap the trailing '\0' by replacing it with a space
  // we could also do this by knowing the length,
  // but this way is safer.
  
  VALUE_VOLT_TEMPLATE[strlen(VALUE_VOLT_TEMPLATE)] = ' ';
  VALUE_AMP_TEMPLATE[strlen(VALUE_AMP_TEMPLATE)] = ' ';
  VALUE_KW_TEMPLATE[strlen(VALUE_KW_TEMPLATE)] = ' ';
  
}

void loop() {
  // put your main code here, to run repeatedly:

}

My sketch is now working and now I can save real time information in my influxdb, and have access to nice graphics using Grafana.

|500x281

Thank you very much for all these answers and tips. I learnt indeed that programming an Arduino (Microcontroleer) is not the same as programming a desktop computer...

You are in a more constrained, lower level programming environment, the opposite as python for example where you can be more "higher level" and let the compiler makes most of the optimization job.