Problems with HTTPS POST requests

I made a program that can connect to Dexcoms shareservice (my own bloodsugar data) and get a session id to be able to fetch the data. But when i fetch the data i just get an empty string. I have made a code in Python which workes like a charm and i have applied the same logic to the arduino code, but i think that the problem is around where i read the answer from the server.

In the first two requests i do in the create_session() function, i receive a String and in the last request I'm respecting a json array. Something like this:

[
{
"DT" : "/Date(1636475404000+0000)/",
"ST" : "/Date(16366471804000)/",
"Trend" : 4,
"Value" : 101,
"WT": "/Date(1636471804000)/",
}
]

I think that is my problem.

Here is the code :

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>

const char *ssid = "-";
const char *password = "-";

const char fingerprint[] PROGMEM = "-";


const char *DEXCOM_AUTHENTICATE_ENDPOINT = "/ShareWebServices/Services/General/AuthenticatePublisherAccount";
const char *DEXCOM_LOGIN_ENDPOINT = "/ShareWebServices/Services/General/LoginPublisherAccountByName";

const char *DEXCOM_GLUCOSE_READINGS_ENDPOINT = "/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues";


const char *host = "shareous1.dexcom.com";
const char *DEXCOM_APPLICATION_ID = "d89443d2-327c-4a6f-89e5-496bbb0317db";

//=======================================================================
//                       Main Setup
//=======================================================================

void setup() {
  delay(1000);
  Serial.begin(115200);
  WiFi.mode(WIFI_OFF);
  delay(1000);
  WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);


  Serial.print("Connecting");
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

//=======================================================================
//                    Main Program Loop
//=======================================================================

void loop()
{
  String dexcom_session = create_session();

  Serial.println("Here is the dexcom_session: ");

  int minutes = 10;
  int max_count = 1;

  dexcom_session.remove(0, 1);
  dexcom_session.remove((dexcom_session.length() - 1), 1);


  String body = create_bg_json_sting(dexcom_session, minutes, max_count);

  Serial.println(body);

  String bg = request(host, body, DEXCOM_GLUCOSE_READINGS_ENDPOINT);


  Serial.println("---------------------------------");
  Serial.println(bg);
  Serial.println("---------------------------------");


}


//=======================================================================
//                         Functions
//=======================================================================

String create_bg_json_sting(String session_id, int minutes, int max_count)
{
  DynamicJsonDocument doc(2048);

  doc["sessionId"] = session_id;
  doc["minutes"] = minutes;
  doc["maxCount"] = max_count;

  String json;
  serializeJson(doc, json);
  return json;
}

String create_login_json_string(String accountName, String password)
{
  DynamicJsonDocument doc(2048);

  doc["accountName"] = accountName;
  doc["password"] = password;
  doc["applicationId"] = DEXCOM_APPLICATION_ID;

  String json;
  serializeJson(doc, json);
  return json;
}

String create_session()
{
  String session_id = "";

  String body = create_login_json_string("-", "-");
  session_id = request(host, body, DEXCOM_AUTHENTICATE_ENDPOINT);

  session_id = request(host, body, DEXCOM_LOGIN_ENDPOINT);

  return session_id;
}


String request(const char *host, String body, const char *endpoint)

{
  WiFiClientSecure httpsClient;


  httpsClient.setFingerprint(fingerprint);


  Serial.println("HTTPS Connecting");
  int r = 0;
  while ((!httpsClient.connect(host, 443)) && (r < 30)) {
    delay(100);
    Serial.print(".");
    r++;
  }
  if (r == 30) {
    Serial.println("Connection failed");
  }
  else {
    Serial.println("Connected to web");
    char postStr[120];
    sprintf(postStr, "POST %s HTTP/1.1", endpoint);

    httpsClient.println(postStr);
    httpsClient.print("Host: "); httpsClient.println(host);
    httpsClient.println("Access-Token: *************");
    httpsClient.println("Content-Type: application/json");
    httpsClient.print("Content-Length: "); httpsClient.println(body.length());
    httpsClient.println();
    httpsClient.println(body);

    Serial.println("request sent");

    while (httpsClient.connected()) {
      String line = httpsClient.readStringUntil('\n');
      if (line == "\r") {
        Serial.println("headers received");
        break;
      }
    }

    String returnString = "";

    while (httpsClient.available()) {
      char c = httpsClient.read();
      returnString.concat(c);
    }

    Serial.println("closing connection");

    httpsClient.stop();

    return returnString;
  }


}

Here is the python code:

import requests


DEXCOM_BASE_URL_OUS = "https://shareous1.dexcom.com/ShareWebServices/Services"

# Dexcom Share API endpoints
DEXCOM_LOGIN_ENDPOINT = "General/LoginPublisherAccountByName"
DEXCOM_AUTHENTICATE_ENDPOINT = "General/AuthenticatePublisherAccount"
DEXCOM_VERIFY_SERIAL_NUMBER_ENDPOINT = (
    "Publisher/CheckMonitoredReceiverAssignmentStatus"
)
DEXCOM_GLUCOSE_READINGS_ENDPOINT = "Publisher/ReadPublisherLatestGlucoseValues"

DEXCOM_APPLICATION_ID = "d89443d2-327c-4a6f-89e5-496bbb0317db"

# Dexcom error strings
ACCOUNT_ERROR_USERNAME_NULL_EMPTY = "Username null or empty"
ACCOUNT_ERROR_PASSWORD_NULL_EMPTY = "Password null or empty"
ACCOUNT_ERROR_ACCOUNT_NOT_FOUND = "Account not found"
ACCOUNT_ERROR_PASSWORD_INVALID = "Password not valid"
ACCOUNT_ERROR_UNKNOWN = "Account error"

SESSION_ERROR_SESSION_ID_NULL = "Session ID null"
SESSION_ERROR_SESSION_ID_DEFAULT = "Session ID default"
SESSION_ERROR_SESSION_NOT_VALID = "Session ID not valid"
SESSION_ERROR_SESSION_NOT_FOUND = "Session ID not found"

ARGUEMENT_ERROR_MINUTES_INVALID = "Minutes must be between 1 and 1440"
ARGUEMENT_ERROR_MAX_COUNT_INVALID = "Max count must be between 1 and 288"
ARGUEMENT_ERROR_SERIAL_NUMBER_NULL_EMPTY = "Serial number null or empty"

# Other
DEXCOM_TREND_DESCRIPTIONS = [
    "",
    "rising quickly",
    "rising",
    "rising slightly",
    "steady",
    "falling slightly",
    "falling",
    "falling quickly",
    "unable to determine trend",
    "trend unavailable",
]
DEXCOM_TREND_ARROWS = ["", "↑↑", "↑", "↗", "→", "↘", "↓", "↓↓", "?", "-"]

DEFAULT_SESSION_ID = "00000000-0000-0000-0000-000000000000"

MMOL_L_CONVERTION_FACTOR = 0.0555


# Errors used in pydexcom.


class DexcomError(Exception):
    # Base class for all Dexcom errors.
    pass


class AccountError(DexcomError):
    # Errors involving Dexcom Share API credentials.
    pass


class SessionError(DexcomError):
    # Errors involving Dexcom Share API session.
    pass


class ArguementError(DexcomError):
    # Error involving arguements.
    pass


def Dexcom(username: str, password: str):
    # Initialize with JSON glucose reading from Dexcom Share API.
    session_id = None
    dexcom_session = create_session(
        DEXCOM_BASE_URL_OUS, username, password, session_id)
    return dexcom_session


def request(base_url: str, method: str, endpoint: str, params: dict = None, json: dict = {},) -> dict:
    # Send request to Dexcom Share API.
    try:
        url = f"{base_url}/{endpoint}"

        r = requests.request(method, url, params=params, json=json,)
        r.raise_for_status()

        return r.json()
    except requests.HTTPError:

        if r.status_code == 500:

            if r.json()["Code"] == "SessionNotValid":
                raise SessionError(SESSION_ERROR_SESSION_NOT_VALID)
            elif r.json()["Code"] == "SessionIdNotFound":
                raise SessionError(SESSION_ERROR_SESSION_NOT_FOUND)
            elif r.json()["Code"] == "SSO_AuthenticateAccountNotFound":
                raise AccountError(ACCOUNT_ERROR_ACCOUNT_NOT_FOUND)
            elif r.json()["Code"] == "SSO_AuthenticatePasswordInvalid":
                raise AccountError(ACCOUNT_ERROR_PASSWORD_INVALID)
            elif r.json()["Code"] == "InvalidArgument":
                if "accountName" in r.json()["Message"]:
                    raise AccountError(ACCOUNT_ERROR_USERNAME_NULL_EMPTY)
                if "password" in r.json()["Message"]:
                    raise AccountError(ACCOUNT_ERROR_PASSWORD_NULL_EMPTY)
            else:
                print(f'{r.json()["Code"]}: {r.json()["Message"]}')
        else:
            print(f"{r.status_code}: {r.json()}")
    except Exception:

        print("Unknown request error")
    return None


def validate_session_id(session_id):
    # Validate session id.
    if not session_id:

        raise SessionError(SESSION_ERROR_SESSION_ID_NULL)
    if session_id == DEFAULT_SESSION_ID:

        raise SessionError(SESSION_ERROR_SESSION_ID_DEFAULT)


def validate_account(username: str, password: str):
    # Validate credentials.
    if not username:

        raise AccountError(ACCOUNT_ERROR_USERNAME_NULL_EMPTY)
    if not password:

        raise AccountError(ACCOUNT_ERROR_PASSWORD_NULL_EMPTY)


def create_session(base_url: str, username: str, password: str, session_id: str):

    validate_account(username, password)

    json = {
        "accountName": username,
        "password": password,
        "applicationId": DEXCOM_APPLICATION_ID,
    }

    endpoint1 = DEXCOM_AUTHENTICATE_ENDPOINT
    endpoint2 = DEXCOM_LOGIN_ENDPOINT

    session_id = request(base_url, "post", endpoint1, json=json)

    try:
        validate_session_id(session_id)
        session_id = request(base_url, "post", endpoint2, json=json)
        validate_session_id(session_id)
    except SessionError:
        raise AccountError(ACCOUNT_ERROR_UNKNOWN)
    return session_id


def get_glucose_readings(base_url, session_id, minutes: int = 1440, max_count: int = 288):
    # Get max_count glucose readings within specified minutes.
    validate_session_id(session_id)
    if minutes < 1 or minutes > 1440:

        raise ArguementError(ARGUEMENT_ERROR_MINUTES_INVALID)
    if max_count < 1 or max_count > 288:

        raise ArguementError(ARGUEMENT_ERROR_MAX_COUNT_INVALID)

    params = {
        "sessionId": session_id,
        "minutes": minutes,
        "maxCount": max_count,
    }

    try:
        json_glucose_readings = request(
            base_url, "post", DEXCOM_GLUCOSE_READINGS_ENDPOINT, params=params
        )
    except SessionError:

        create_session()
        json_glucose_readings = request(
            base_url, "post", DEXCOM_GLUCOSE_READINGS_ENDPOINT, params=params
        )

    if not json_glucose_readings:
        return None

    return json_glucose_readings


def get_latest_glucose_reading():
    # Get latest available glucose reading.
    glucose_readings = get_glucose_readings(max_count=1)
    if not glucose_readings:
        return None
    return glucose_readings[0]


def get_current_glucose_reading(base_url, session_id):
    # Get current available glucose reading.
    glucose_readings = get_glucose_readings(
        base_url, session_id, minutes=10, max_count=1)
    if not glucose_readings:
        return None
    return glucose_readings[0]


dexcom_session = Dexcom("-----", "-----")
bg = get_current_glucose_reading(DEXCOM_BASE_URL_OUS, dexcom_session)
print(bg)

I did not include any credentials, but i'm certain that they work.

What shows up in Serial Monitor?

Just an empty new row (empty string)

So the print in setup() that says "Connecting." isn't working?!?

Everything shows up in the Serial monitor except the print I do at the last request in the loop. (bg)

I can't see your Serial Monitor output from here.

The json object in the monitor is what i send to the server to receive the data and it works if i put it in Postman. The response should be between the lines in the end.

It would be good to print out the headers in 'request()' as they are received.

    while (httpsClient.connected()) {
      String line = httpsClient.readStringUntil('\n');
      Serial.println(line);  // TRY ADDING THIS LINE
      if (line == "\r") {
        Serial.println("headers received");
        break;
      }
    }


This is the headers i receive from the second and third request. In the third request, it says that content length is 0.

It looks like you get a session ID valid for ten minutes and one request. I would expect that the destination system knows the limitations and that all you need pass is the ID, but you don't.

What does the Python code do?

The python code does exactly the same thing as the arduino code. And I have also used postman and there i got a response. The minutes and the max_count has nothing to do with the session. Those variables just change what time and how many datapoints i request.

You might try posting the Python version - someone with those skills may see the issue.

import requests


DEXCOM_BASE_URL_OUS = "https://shareous1.dexcom.com/ShareWebServices/Services"

# Dexcom Share API endpoints
DEXCOM_LOGIN_ENDPOINT = "General/LoginPublisherAccountByName"
DEXCOM_AUTHENTICATE_ENDPOINT = "General/AuthenticatePublisherAccount"
DEXCOM_VERIFY_SERIAL_NUMBER_ENDPOINT = (
    "Publisher/CheckMonitoredReceiverAssignmentStatus"
)
DEXCOM_GLUCOSE_READINGS_ENDPOINT = "Publisher/ReadPublisherLatestGlucoseValues"

DEXCOM_APPLICATION_ID = "d89443d2-327c-4a6f-89e5-496bbb0317db"

# Dexcom error strings
ACCOUNT_ERROR_USERNAME_NULL_EMPTY = "Username null or empty"
ACCOUNT_ERROR_PASSWORD_NULL_EMPTY = "Password null or empty"
ACCOUNT_ERROR_ACCOUNT_NOT_FOUND = "Account not found"
ACCOUNT_ERROR_PASSWORD_INVALID = "Password not valid"
ACCOUNT_ERROR_UNKNOWN = "Account error"

SESSION_ERROR_SESSION_ID_NULL = "Session ID null"
SESSION_ERROR_SESSION_ID_DEFAULT = "Session ID default"
SESSION_ERROR_SESSION_NOT_VALID = "Session ID not valid"
SESSION_ERROR_SESSION_NOT_FOUND = "Session ID not found"

ARGUEMENT_ERROR_MINUTES_INVALID = "Minutes must be between 1 and 1440"
ARGUEMENT_ERROR_MAX_COUNT_INVALID = "Max count must be between 1 and 288"
ARGUEMENT_ERROR_SERIAL_NUMBER_NULL_EMPTY = "Serial number null or empty"

# Other
DEXCOM_TREND_DESCRIPTIONS = [
    "",
    "rising quickly",
    "rising",
    "rising slightly",
    "steady",
    "falling slightly",
    "falling",
    "falling quickly",
    "unable to determine trend",
    "trend unavailable",
]
DEXCOM_TREND_ARROWS = ["", "↑↑", "↑", "↗", "→", "↘", "↓", "↓↓", "?", "-"]

DEFAULT_SESSION_ID = "00000000-0000-0000-0000-000000000000"

MMOL_L_CONVERTION_FACTOR = 0.0555


# Errors used in pydexcom.


class DexcomError(Exception):
    # Base class for all Dexcom errors.
    pass


class AccountError(DexcomError):
    # Errors involving Dexcom Share API credentials.
    pass


class SessionError(DexcomError):
    # Errors involving Dexcom Share API session.
    pass


class ArguementError(DexcomError):
    # Error involving arguements.
    pass


def Dexcom(username: str, password: str):
    # Initialize with JSON glucose reading from Dexcom Share API.
    session_id = None
    dexcom_session = create_session(
        DEXCOM_BASE_URL_OUS, username, password, session_id)
    return dexcom_session


def request(base_url: str, method: str, endpoint: str, params: dict = None, json: dict = {},) -> dict:
    # Send request to Dexcom Share API.
    try:
        url = f"{base_url}/{endpoint}"

        r = requests.request(method, url, params=params, json=json,)
        r.raise_for_status()

        return r.json()
    except requests.HTTPError:

        if r.status_code == 500:

            if r.json()["Code"] == "SessionNotValid":
                raise SessionError(SESSION_ERROR_SESSION_NOT_VALID)
            elif r.json()["Code"] == "SessionIdNotFound":
                raise SessionError(SESSION_ERROR_SESSION_NOT_FOUND)
            elif r.json()["Code"] == "SSO_AuthenticateAccountNotFound":
                raise AccountError(ACCOUNT_ERROR_ACCOUNT_NOT_FOUND)
            elif r.json()["Code"] == "SSO_AuthenticatePasswordInvalid":
                raise AccountError(ACCOUNT_ERROR_PASSWORD_INVALID)
            elif r.json()["Code"] == "InvalidArgument":
                if "accountName" in r.json()["Message"]:
                    raise AccountError(ACCOUNT_ERROR_USERNAME_NULL_EMPTY)
                if "password" in r.json()["Message"]:
                    raise AccountError(ACCOUNT_ERROR_PASSWORD_NULL_EMPTY)
            else:
                print(f'{r.json()["Code"]}: {r.json()["Message"]}')
        else:
            print(f"{r.status_code}: {r.json()}")
    except Exception:

        print("Unknown request error")
    return None


def validate_session_id(session_id):
    # Validate session id.
    if not session_id:

        raise SessionError(SESSION_ERROR_SESSION_ID_NULL)
    if session_id == DEFAULT_SESSION_ID:

        raise SessionError(SESSION_ERROR_SESSION_ID_DEFAULT)


def validate_account(username: str, password: str):
    # Validate credentials.
    if not username:

        raise AccountError(ACCOUNT_ERROR_USERNAME_NULL_EMPTY)
    if not password:

        raise AccountError(ACCOUNT_ERROR_PASSWORD_NULL_EMPTY)


def create_session(base_url: str, username: str, password: str, session_id: str):

    validate_account(username, password)

    json = {
        "accountName": username,
        "password": password,
        "applicationId": DEXCOM_APPLICATION_ID,
    }

    endpoint1 = DEXCOM_AUTHENTICATE_ENDPOINT
    endpoint2 = DEXCOM_LOGIN_ENDPOINT

    session_id = request(base_url, "post", endpoint1, json=json)

    try:
        validate_session_id(session_id)
        session_id = request(base_url, "post", endpoint2, json=json)
        validate_session_id(session_id)
    except SessionError:
        raise AccountError(ACCOUNT_ERROR_UNKNOWN)
    return session_id


def get_glucose_readings(base_url, session_id, minutes: int = 1440, max_count: int = 288):
    # Get max_count glucose readings within specified minutes.
    validate_session_id(session_id)
    if minutes < 1 or minutes > 1440:

        raise ArguementError(ARGUEMENT_ERROR_MINUTES_INVALID)
    if max_count < 1 or max_count > 288:

        raise ArguementError(ARGUEMENT_ERROR_MAX_COUNT_INVALID)

    params = {
        "sessionId": session_id,
        "minutes": minutes,
        "maxCount": max_count,
    }

    try:
        json_glucose_readings = request(
            base_url, "post", DEXCOM_GLUCOSE_READINGS_ENDPOINT, params=params
        )
    except SessionError:

        create_session()
        json_glucose_readings = request(
            base_url, "post", DEXCOM_GLUCOSE_READINGS_ENDPOINT, params=params
        )

    if not json_glucose_readings:
        return None

    return json_glucose_readings


def get_latest_glucose_reading():
    # Get latest available glucose reading.
    glucose_readings = get_glucose_readings(max_count=1)
    if not glucose_readings:
        return None
    return glucose_readings[0]


def get_current_glucose_reading(base_url, session_id):
    # Get current available glucose reading.
    glucose_readings = get_glucose_readings(
        base_url, session_id, minutes=10, max_count=1)
    if not glucose_readings:
        return None
    return glucose_readings[0]


dexcom_session = Dexcom("-----", "-----")
bg = get_current_glucose_reading(DEXCOM_BASE_URL_OUS, dexcom_session)
print(bg)

This code is basically a simplyfied version of a python library called pyDexcom

I have posted the python code now

Other post/duplicate DELETED
Please do NOT cross post / duplicate as it wastes peoples time and efforts to have more than one post for a single topic.

Continued cross posting could result in a time out from the forum.

Could you also take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum in the future.