Problem HTTP GET newbie

hi all.
I've been tinkering with arduino's since a few years now. making easy stuff, mostly web based.

My next project is building a arduino who's getting some info over LAN and display it on LCD.
The arduino will need to get every 8 secs (for now, to be determined) my power consumption and my power production (PV panels) and display them on an LCD wich I want to place in my kitchen. So I can control my electrical consumption.

I have a home server running, wich has all that data. I've made 2 simple scripts (php) who give the power (in Watt) as respose.

But I think I'm running against a wall with reading it. It reads something, but I'm not sure I nedd a int, char, string, .... as response.

void loop() {
    Serial.println("I'm starting a HTTP request ...");
    client.connect(server, 80);
    client.println("GET /huisbesturing/getPpvlive.php HTTP/1.1");
    client.println("Host: 192.168.1.240");
    int c = client.read();
    Serial.print(c);
    client.println("Connection: close");
    client.println("");
    Serial.println("done...");
  delay(8000);
}

The webpage getPpvlive.php is just an empty page with for example now 599
as response. This is the power coming at this moment from the solar/PV panels.
I'll need to make a second request to get to power consumption. Same server, same stype but at getPnetlive.php . The I'll publish them to an LCD. Maybe I'll do some math with it (see how much I'm pulling of the power grid, or how many % I'm living 'on my own')

Serial monitor is giving me now:

I'm starting a HTTP request ...
-1done...
I'm starting a HTTP request ...
-1done...
I'm starting a HTTP request ...
-1done...

So the answer is -1 instead of 599
any help?
thanks !

Been surfing a lot to try to solve it.
I tried the examplel at Arduino Yún HTTP Client | Arduino Documentation | Arduino Documentation

then the serial plots this:

���������������������������������������

The -1 means that there were no characters available when you called client.read(). You should probably wait until you finish sending the GET request before you start looking for an answer.

Try printing out all of the characters you get after you end the request:

    client.connect(server, 80);
    client.println("GET /huisbesturing/getPpvlive.php HTTP/1.1");
    client.println("Host: 192.168.1.240");
    client.println("Connection: close");
    client.println("");
    delay(100);
    while (client.available())
      Serial.write(client.read());
}

If all you get is "599" you can just use
int c = client.parseInt();
to get that value. If you get a header first you will need to skip the header before looking for your number.
There should be an "HTTPclient" library to do a lot of this work for you. What board and network interface are you using?

hi thanks for the answer.

if I do :

void loop(){
  Serial.println("I'm starting a HTTP request ...");
  client.connect(server, 80);
  client.println("GET /huisbesturing/getPnetlive.php HTTP/1.1");
  client.println("Host: 192.168.1.240");
  client.println("Connection: close");
  client.println("");
  delay(100);
  int c = client.parseInt();
  Serial.print(c);
  delay(8000);
}

then I get

I'm starting a HTTP request ...
1I'm starting a HTTP request ...
1I'm starting a HTTP request ...
1I'm starting a HTTP request ...

so the answer went from -1 to 1 :slight_smile:

before I managed to get a 72 , but couldn't reproduce.

when I change to :

  delay(100);
  while (client.available())
    Serial.write(client.read());

It gave me the header too, only on one of the php pages

I'm starting a HTTP request ...
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Mon, 16 Jan 2023 15:59:03 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close

1
0
0

so I'm a bit lost ...

Using a clone board, with build in ethernet. have used it before without trouble (then it was a http server, not a client). It's keyestudio (aliexpress)

That's because the response to the client starts with:
HTTP/1.1 200 OK
The .parseInt() throws away the 'HTTP/' part because it doesn't look like a number. Then it takes in the '1' and sees the '.' as the end of the number. You will have to skip the headers to read the page contents.

In other bad news, looks like your 'getPpvlive.php' is returning:

1
0
0

and not 599 as you expect. What does a web browser show if you use it to read the page?

The 599 was the value then at the moment. But that value changes every second when there is more sun (getPpvlive.php) or there is more consuption (getPnetlive.php)
In my browser the values are showed correctly. Blank page - only that number.

I’m not posting the web accessible adress of theese scripts - Im afraid of a hacker/bots/…

but in firefox the inspector shows me:
Naamloos
so it's as clean as it can be.

(this is the consumption at this very moment at my home. 1705W)
Or do I need to publish it as a Json on my webserver?

You can do that, but you don't have to use json, or html either. You are in charge of the page contents. You can simply make it a list of values in a plain text body, that may be easiest.

I did a similar project with my weather station and indoor sensors.

That would solve my next question. Because I’d like to retrieve 4 values in total:

  • current consumption (Watts)
  • current production (Watts)
  • total consumption today (kWh)
  • total production today (kWh)
    And this every 5sec or so.

And then I’ll show those on an 20x4 lcd, using BigNumners_I2C

Got another test. I used the example (and library) published here:
GitHub - amcewen/HttpClient: Arduino HTTP library

This gives the correct number, but some 'debris' too.

6
1024


0

6
1027


0

6
1019

0

The numbers 1024, 1027, 1019 are the ones that I need. That's my power consumption (every 5 sec).
If I change the script on my webserver to publish in one page the 4 numbers I need, it will be difficulet to read them all in arduino I guess?

Hard to know where that comes from without seeing your php script.

What do you see if you query the page with browser?

Why to you think that?

Here is the code for my display. Its much more complex than you need, so don't worry too much about that. It runs on a Wemos D1 mini. The display is a 128x64 graphic LCD display with i2c backpack, and a rotary encoder for input.


#include <ESP8266WiFiMulti.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <Wire.h>
#include <I2C_graphical_LCD_display.h>

I2C_graphical_LCD_display lcd;
WiFiClient client;

const byte clkPin = D7;
const byte dirPin = D6;
const byte btnPin = D5;

extern "C" {
#include "user_interface.h"
}

const unsigned long dataUpdatePeriod = 15 * 60 * 1000;
const unsigned long displayUpdatePeriod = 10 * 1000;

const char* ssid = "ssid";
const char* password = "password";
char myhostname[] = "DataDisplay";

static const char ntpServerName[] = "uk.pool.ntp.org";
int timeZone = 0;     // Central European Time

ESP8266WiFiMulti wifiMulti;
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
int prevDay, prevHour, prevMin;

time_t getNtpTime();
void sendNTPpacket(IPAddress &address);
time_t prevDisplay = 0; // when the digital clock was displayed

String sensorData[20][6];
String displayPageName[20];
int displayPages;

void setup() {
  pinMode(clkPin, INPUT_PULLUP);
  pinMode(dirPin, INPUT_PULLUP);
  pinMode(btnPin, INPUT_PULLUP);
  Serial.begin(115200);
  Wire.setClock(400000L);
  lcd.begin();

  //Connect to WiFi
  wifi_station_set_hostname(myhostname);
  wifiMulti.addAP("ssid1", "password1");
  wifiMulti.addAP("ssid2", "password2");
  Serial.println("Connecting");
  lcd.gotoxy (0, 0);
  lcd.print("Connecting");
  int i = 0;
  while (wifiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    lcd.print(".");
  }
  Serial.printf("\nConnected to '%s' IP address: %s RSSI: %d\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str(), WiFi.RSSI());
  lcd.clear();
  lcd.gotoxy (0, 0);
  lcd.printf("SSID: %s", WiFi.SSID().c_str());
  lcd.gotoxy (0, 8);
  lcd.printf("IP: %s", WiFi.localIP().toString().c_str());
  lcd.gotoxy (0, 16);
  lcd.printf("RSSI: %d", WiFi.RSSI());
  delay(3000);
  lcd.clear();
  wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); // Enable light sleep mode

  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(24UL * 60 * 60);

}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

bool isBST(int year, int month, int day, int hour) {

  // bst begins at 01:00 gmt on the last sunday of march
  // and ends at 01:00 gmt (02:00 bst) on the last sunday of october

  // january, february, and november are out
  if (month < 3 || month > 10) return false;
  // april to september are in
  if (month > 3 && month < 10) return true;

  // in march we are bst if its past 1am gmt on the last sunday in the month
  if (month == 3) {
    // last sunday of march
    int lastMarSunday =  (31 - (5 * year / 4 + 4) % 7);
    if (day > lastMarSunday) return true;
    if (day < lastMarSunday) return false;
    if (hour < 1) return false;
    return true;
  }

  // in october we must be before 1am gmt (2am bst) on the last sunday to be bst
  if (month == 10) {
    // last sunday of october
    int lastOctSunday = (31 - (5 * year / 4 + 1) % 7);
    if (day < lastOctSunday) return true;
    if (day > lastOctSunday) return false;
    if (hour >= 1) return false;
    return true;
  }
}

void updateClockDisplay() {

  char weekdayStr[4];
  strcpy(weekdayStr, dayShortStr(weekday()));
  lcd.gotoxy (0, 56);
  lcd.setInv(true);
  lcd.printf("%02d:%02d:%02d   %s %02d %s", hour(), minute(), second(), weekdayStr, day(), monthShortStr(month()));
  lcd.setInv(false);
}

void updateSensorReadings() {
    
  if (!client.connect("www.myserver.co.uk", 80)) {
    Serial.println("Connection to host failed");
  }
  else {

    Serial.println("Reading page");

    client.println("GET /sensors_simple.php HTTP/1.1");
    client.println("Host: www.myserver.co.uk");
    client.println("Connection: close");
    client.println();

    displayPages = 0;
    int pageLine = 0;
    int lineCount = 0;
    String line;
    String lastPageName;
    const int linesPerPage = 6;

    while (!client.available()); //Wait for response
    while (client.readStringUntil('\n') != "\r"); //Discard header
    line = client.readStringUntil('\n'); //Discard unwanted line

    while (client.available() && client.peek() != '\r') {
      String newPageName = client.readStringUntil(',');
      String sensorName = client.readStringUntil(',');
      String sensorValue = client.readStringUntil('\n'); 

      if (newPageName != lastPageName || pageLine >= linesPerPage) {
        for (int pl = 0; pl < linesPerPage; pl++) sensorData[displayPages][pl] = "                     ";
        pageLine = 0;
        lastPageName = newPageName;
        Serial.printf("New page: %s\n", newPageName.c_str());
        displayPageName[displayPages++] = (newPageName + "                     ").substring(0, 21);
      }
      String spaces = String("______________________").substring(0, 21 - sensorName.length() - sensorValue.length());
      sensorData[displayPages - 1][pageLine] = sensorName + spaces + sensorValue;
      Serial.printf("Stored: %s\n", sensorData[displayPages - 1][pageLine++].c_str());
    }

    while (client.connected() && client.available()) {
      line = client.readStringUntil('\n'); //Discard unwanted line
    }
    
    Serial.println("Connection closed");
    client.stop();

  }
}

int currentPage;

void updateDisplay(int page) {

  lcd.gotoxy (0, 0);
  lcd.setInv(true);
  lcd.print(displayPageName[page]);
  lcd.setInv(false);
  for (int l = 0; l < 6; l++) {
    lcd.gotoxy (0, l * 8 + 8);
    lcd.print(sensorData[page][l]);
  }
}

void loop() {

  unsigned long timeNow = millis();
  static unsigned long lastSensorTime;
  static unsigned long lastDisplayTime;

  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
      updateClockDisplay();

      if (minute() != prevMin) {
        Serial.printf("Time is %02d:%02d:%02d\n", hour(), minute(), second());
        prevMin = minute();

        if (hour() != prevHour) {
          prevHour = hour();
          if (timeZone != (isBST(year(), month(), day(), hour()) ? 1 : 0)) {
            timeZone = 1 - timeZone;
            adjustTime(timeZone * 60 * 60);
            Serial.print("Time Zone is now ");
            Serial.println(timeZone);
          }
        }
      }
    }
  }

  if (lastSensorTime == 0 || (timeNow - lastSensorTime) > dataUpdatePeriod) {
    lastSensorTime = timeNow;
    updateSensorReadings();
  }

  if (lastDisplayTime == 0 || (timeNow - lastDisplayTime) > displayUpdatePeriod) {
    lastDisplayTime = timeNow;
    if (++currentPage >= displayPages) currentPage = 0;
    updateDisplay(currentPage);
 }

  static byte lastClkPin = HIGH;
  byte newClkPin = digitalRead(clkPin);
  byte newDirPin = digitalRead(dirPin);
  static byte lastBtnPin = HIGH;
  byte newBtnPin = digitalRead(btnPin);

  if (newClkPin != lastClkPin) {
    if (newClkPin == LOW) {
      if (newDirPin == HIGH) {
        lastDisplayTime = timeNow;
        if (++currentPage >= displayPages) currentPage = 0;
        updateDisplay(currentPage);
      }
      else {
        lastDisplayTime = timeNow;
        if (--currentPage < 0) currentPage = displayPages - 1;
        updateDisplay(currentPage);
      }
    }
    delay(20);
    lastClkPin = newClkPin;
  }

  
}

Here is my PHP script:

<?php 
$servername = "www.myserver.co.uk";
$username = "username";
$password = "password";
$dbname = "databasename";

$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
    echo "Sorry, the website is experiencing problems: " . $conn->connect_error();
    exit;
}

$sql = "SELECT   s.sensor_time, TIMESTAMPDIFF(MINUTE, s.sensor_time, NOW()) as sensor_age,
                 s.sensor_name, s.sensor_value, i.sensor_desc, i.sensor_units
        FROM     granary_sensors s
        JOIN     (   SELECT   sensor_name, MAX(sensor_time) AS max_time
                     FROM     granary_sensors
                     GROUP BY sensor_name) AS m
        ON       m.sensor_name = s.sensor_name 
        AND      m.max_time = s.sensor_time
        JOIN     sensor_info i
        ON       i.sensor_name = s.sensor_name
        WHERE    TIMESTAMPDIFF(MINUTE, s.sensor_time, NOW()) < 60*24*90
        ORDER BY s.sensor_name;";

if (!$result = $conn->query($sql)) {
    echo "Sorry, the website is experiencing problems.";
    exit;
}

// List sensors, values and last updates with links to graphs
while ($reading = $result->fetch_assoc()) {
    echo $reading['sensor_name'] . ":" . $reading['sensor_value'] . $reading['sensor_units'];
    if ($reading['sensor_age'] > 20) echo "*";
    echo "\n";
}

$result->free();
$conn->close();
?>

Here is the output of the php script as seen on browser:

Conservatory,Abs Humid,7.5g/m3
Conservatory,Battery,3.76V
Conservatory,Rel Humid,77.33%
Conservatory,Temp,10.51C
Greenhouse,Abs Humid,4.58g/m3
Greenhouse,Battery,3.85V
Greenhouse,Rel Humid,96.31%
Greenhouse,Soil,0
Greenhouse,Temp,-0.28C
Ground Floor,Abs Humid,5.5g/m3
Ground Floor,Battery,3.23V
Ground Floor,Rel Humid,27.51%
Ground Floor,Temp,22.51C
Indoor,Battery,3.83V
Indoor,Pressure,953.7mb
Indoor,Rel Humid,34.24% 
Indoor,Temp,22.14C 
Middle Floor,Abs Humid,7.08g/m3 
Middle Floor,Battery,3.61V 
Middle Floor,Rel Humid,35.47% 
Middle Floor,Temp,22.49C 
Outdoor,Abs Humid,4.04g/m3 
Outdoor,Battery,4.08V 
Outdoor,Light,0Lux 
Outdoor,Rel Humid,108.62% 
Outdoor,Temp,-3.79C 
Top Floor,Abs Humid,6.02g/m3 
Top Floor,Battery,3.92V 
Top Floor,Pressure,952.34mb 
Top Floor,Rel Humid,34.64% 
Top Floor,Temp,20.09C 
Water Tank Room,Battery,3.92V 
Water Tank Room,Temp,21C 
Weather Station,Abs Humid,0.04g/m3 
Weather Station,Battery,3.44V 
Weather Station,Rain,0mm/hr 
Weather Station,Rel Humid,1% 
Weather Station,Temp,-2.1C 
Weather Station,Wind Dir,45Degrees 
Weather Station,Wind Gust,7Km/hr 
Weather Station,Wind Speed,1.42Km/hr 

As you can see, each line has 3 fields: location of sensor, name of sensor, value of sensor.

That 'debris' comes from Transfer-Encoding: chunked.
You can avoid it by providing a Content-Length header.

<?php
  header("Content-Type: text/plain");
  $s = "";
  for ($i = 0; $i < 10; $i++) {
    $s = "{$s}i=$i\r\n";
  }
  $len = strlen($s);
  header("Content-Length: $len");
  print($s);
?>

as simple as it gets?

<?php
$result = shell_exec('sh /usr/local/bin/PnetLCD.sh');
echo "$result\n";
?>

okay .... for future reference. I'm using Dweet now. Looks good and is goin to work. There is an official dweet example in ArduinoHttpClient.h

It's a workaround. Im posting to the interweb and pulling it back in my hous. But hey .. that few bits every 5-10sec's should be no prob.

Okay. Even with dweet i’m not getting there.
I found this nice library. Simple use - I can get the Json from dweet with my latest numbers. Exactly same layout as on the web.

Simple dweet

This is my (example) dweet ::
https://dweet.io/get/latest/dweet/for/bartmandweet

{"this":"succeeded","by":"getting","the":"dweets","with":[{"thing":"bartmandweet","created":"2023-01-18T09:10:08.033Z","content":{"netlive":2747,"pvlive":820,"nettot":12345,"pvtot":7896}}]}

I can hard-code the location of the values, but if the values change from 999W to 1001Watt I’m fucked. So no hard-coding.

So I tried the json decoders.

I tried putting the recieved json from dweet into the json parser (arduinojson.org). But that doesn’t work like that apparently.

So I’ve got a json in
char* res = dweet.get_latest("bartmandweet");
But how to get nicely my 4 values out? (Without writing 1000lines?

The ArduinoJson.org 'Assistant' says this:

StaticJsonDocument<384> doc;

DeserializationError error = deserializeJson(doc, res);

if (error) {
  Serial.print("deserializeJson() failed: ");
  Serial.println(error.c_str());
  return;
}

const char* this = doc["this"]; // "succeeded"
const char* by = doc["by"]; // "getting"
const char* the = doc["the"]; // "dweets"

JsonObject with_0 = doc["with"][0];
const char* with_0_thing = with_0["thing"]; // "bartmandweet"
const char* with_0_created = with_0["created"]; // "2023-01-18T09:10:08.033Z"

JsonObject with_0_content = with_0["content"];
int with_0_content_netlive = with_0_content["netlive"]; // 2747
int with_0_content_pvlive = with_0_content["pvlive"]; // 820
int with_0_content_nettot = with_0_content["nettot"]; // 12345
int with_0_content_pvtot = with_0_content["pvtot"]; // 7896

yes... I googled that, and I've put that into my code. but I think there is a problem about the format (sorry, I'm not a preogrammer and this is where I get stuck).

I have this:

void loop() {

char* input = dweet.get_latest("bartmandweet");
Serial.println(input);

StaticJsonDocument<256> doc;

DeserializationError error = deserializeJson(doc, input);

if (error) {
  Serial.print(F("deserializeJson() failed: "));
  Serial.println(error.f_str());
  return;
}

const char* this = doc["this"]; // "succeeded"
const char* by = doc["by"]; // "getting"
const char* the = doc["the"]; // "dweets"

JsonObject with_0 = doc["with"][0];
const char* with_0_thing = with_0["thing"]; // "bartmandweet"
const char* with_0_created = with_0["created"]; // "2023-01-18T09:10:08.033Z"

JsonObject with_0_content = with_0["content"];
int with_0_content_netlive = with_0_content["netlive"]; // 2747
int with_0_content_pvlive = with_0_content["pvlive"]; // 820
int with_0_content_nettot = with_0_content["nettot"]; // 12345
int with_0_content_pvtot = with_0_content["pvtot"]; // 7896

  delay(7000);
}


I get this. So It can grab the json from the web. But cant be serialized?

{"this":"succeeded","by":"getting","the":"dweets","with":[{"thing":"bartmandweet","created":"2023-01-18T09:10:08.033Z","content":{"netlive":2747,"pvlive":820,"nettot":12345,"pvtot":7896}}]
deserializeJson() failed: IncompleteInput
{"this":"failed","because":"Rate limit exceeded, try again in 1 second(s)."
deserializeJson() failed: IncompleteInput
{"this":"succeeded","by":"getting","the":"dweets","with":[{"thing":"bartmandweet","created":"2023-01-18T09:10:08.033Z","content":{"netlive":2747,"pvlive":820,"nettot":12345,"pvtot":7896}}]
deserializeJson() failed: IncompleteInput
{"this":"failed","because":"Rate limit exceeded, try again in 1 second(s)."
deserializeJson() failed: IncompleteInput
{"this":"succeeded","by":"getting","the":"dweets","with":[{"thing":"bartmandweet","created":"2023-01-18T09:10:08.033Z","content":{"netlive":2747,"pvlive":820,"nettot":12345,"pvtot":7896}}]
deserializeJson() failed: IncompleteInput
{"this":"failed","because":"Rate limit exceeded, try again in 1 second(s)."

It looks like all of your JSON input is missing the closing '}'. It worked before, in your previous example. I don't know why that last character is now missing.

I added some delay(500); here and there to give the board the time to read all incoming data.
I added that too in the dweet.cpp in the library to give it the time.
I alse shortened the dweet (its now called bartman in stead of bartmandweet)

but helas... no final } so no deserialization possible.

Both https://dweet.io/get/latest/dweet/for/bartman and https://dweet.io/get/latest/dweet/for/bartmandweet produce a browser page that includes the closing '}' so I think the problem must be on the Arduino. Possibly in the dweet library.