API requests issues

Hi everyone,

I'm working on implementing an API request to fetch specific variables from my solar inverter. As a hardware engineer with limited software experience, I've relied on AI assistance to write the code. Please bear with me as this has been quite a challenge.

I've spent hours trying various approaches, including different libraries, but my code still doesn't compile. The latest issue I'm encountering is a compilation error: "MD5 has not been declared." I have gone back to AI and said you haven't declared MD5, so it tries again with "Given the issues with using the MD5 library and declarations, let's approach this with an alternative method that should be reliable and straightforward. We will use a simpler library setup and ensure that we include the correct libraries and methods." using the GitHub - tzikis/ArduinoMD5: The first easily-embeddable MD5 library for Arduino method doesn't work either.

Could someone help me identify where things are going wrong?

Thank you!
G

#include <WiFi.h>
#include <ArduinoHttpClient.h>
#include <ArduinoJson.h>
#include <MD5.h> // Include the ArduinoMD5 library

// WiFi credentials
const char* ssid = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char* password = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";

// SolisCloud API credentials
const char* apiUrl = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char* keyID = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char* keySecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";

// Inverter details
const char* inverterID = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Replace with your inverter ID
const char* inverterSN = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Replace with your inverter SN

WiFiClient wifi;
HttpClient client = HttpClient(wifi, "www.xxxxxxxxxxxxxxxxxxxxxxxxxxx.com", 13333);

void setup() {
  Serial.begin(115200);
  connectToWiFi();
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    String batterySOC = getInverterData("batteryCapacitySoc");
    String pvPower = getInverterData("pac");

    Serial.print("Battery SOC: ");
    Serial.println(batterySOC);
    Serial.print("PV Power: ");
    Serial.println(pvPower);
  } else {
    Serial.println("WiFi Disconnected");
  }

  // Wait for 60 seconds before the next request
  delay(60000);
}

void connectToWiFi() {
  Serial.print("Connecting to WiFi...");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  Serial.println("\nWiFi connected");
}

String getInverterData(String dataField) {
  // Create request URL
  String requestUrl = String(apiUrl) + "/v1/api/inverterDetail";
  
  // Create JSON body
  String body = "{\"id\":\"" + String(inverterID) + "\",\"sn\":\"" + String(inverterSN) + "\"}";
  
  // Create MD5 of the body
  uint8_t* hash = MD5::make_hash(body);
  char md5String[33];
  MD5::make_digest(md5String, hash, 16);
  String contentMD5 = String(md5String);
  
  // Get the current GMT time
  time_t now;
  struct tm timeinfo;
  char timeStr[30];
  time(&now);
  gmtime_r(&now, &timeinfo);
  strftime(timeStr, sizeof(timeStr), "%a, %d %b %Y %H:%M:%S GMT", &timeinfo);

  // Create authorization header
  String canonicalizedResource = "/v1/api/inverterDetail";
  String stringToSign = "POST\n" + contentMD5 + "\napplication/json;charset=UTF-8\n" + String(timeStr) + "\n" + canonicalizedResource;
  String sign = hmacSha1(keySecret, stringToSign);
  String authorization = "API " + String(keyID) + ":" + base64Encode(sign);

  // Configure HTTP headers
  client.beginRequest();
  client.post("/v1/api/inverterDetail");
  client.sendHeader("Content-MD5", contentMD5);
  client.sendHeader("Content-Type", "application/json;charset=UTF-8");
  client.sendHeader("Date", timeStr);
  client.sendHeader("Authorization", authorization);
  client.sendHeader("Content-Length", body.length());
  client.beginBody();
  client.print(body);
  client.endRequest();

  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  if (statusCode != 200) {
    Serial.print("Error on sending POST: ");
    Serial.println(statusCode);
    return "";
  }

  // Parse JSON response to get the required data field
  DynamicJsonDocument doc(1024);
  deserializeJson(doc, response);
  return doc["data"][dataField].as<String>();
}

String hmacSha1(String key, String data) {
  const int BLOCK_SIZE = 64;
  uint8_t keyBlock[BLOCK_SIZE];
  memset(keyBlock, 0, BLOCK_SIZE);
  
  if (key.length() > BLOCK_SIZE) {
    uint8_t* hash = MD5::make_hash(key);
    memcpy(keyBlock, hash, 16);
  } else {
    memcpy(keyBlock, key.c_str(), key.length());
  }

  uint8_t o_key_pad[BLOCK_SIZE];
  uint8_t i_key_pad[BLOCK_SIZE];

  for (int i = 0; i < BLOCK_SIZE; i++) {
    o_key_pad[i] = keyBlock[i] ^ 0x5c;
    i_key_pad[i] = keyBlock[i] ^ 0x36;
  }

  uint8_t* innerHash = MD5::make_hash(i_key_pad, BLOCK_SIZE, data.c_str(), data.length());
  uint8_t* finalHash = MD5::make_hash(o_key_pad, BLOCK_SIZE, innerHash, 16);

  char hashOutput[33];
  MD5::make_digest(hashOutput, finalHash, 16);
  return String(hashOutput);
}

String base64Encode(String data) {
  const char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  String encodedString;
  int i = 0;
  int j = 0;
  uint8_t charArray3[3];
  uint8_t charArray4[4];

  for (int pos = 0; pos < data.length(); pos++) {
    charArray3[i++] = data[pos];
    if (i == 3) {
      charArray4[0] = (charArray3[0] & 0xfc) >> 2;
      charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
      charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
      charArray4[3] = charArray3[2] & 0x3f;

      for (i = 0; i < 4; i++) {
        encodedString += base64Chars[charArray4[i]];
      }
      i = 0;
    }
  }

  if (i) {
    for (j = i; j < 3; j++) {
      charArray3[j] = '\0';
    }

    charArray4[0] = (charArray3[0] & 0xfc) >> 2;
    charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
    charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
    charArray4[3] = charArray3[2] & 0x3f;

    for (j = 0; j < i + 1; j++) {
      encodedString += base64Chars[charArray4[j]];
    }

    while ((i++ < 3)) {
      encodedString += '=';
    }
  }

  return encodedString;
}

that's your challenge. AI is good for assisting but very poor at debugging when things go wrong. It tends to pile up more exploratory ideas on the previous one and making a mess and if you don't get it, you don't see it...

Can you provide documentation on the exact API of your solar inverter?

I get where you're coming from. AI can definitely be more about throwing out ideas rather than pinpointing exact issues, which can be frustrating as I am finding out!

Honestly, it led me into a false sense of security because the first code I got it to write worked perfectly. It connected to my WiFi, printed its IP address, even played the Star Wars Imperial March through my piezo buzzer when successful (lol), and then synced the date and time every 15 minutes. Here’s the code, just for interest:

And here’s the API documentation for our solar inverter: API Document. Hope this helps!

#include <WiFi.h>       // Include the WiFi library
#include <WiFiUdp.h>    // Include the WiFi UDP library
#include <NTPClient.h>  // Include the NTP Client library
#include "pitches.h"    // Include the pitches header file

// WiFi credentials
const char* ssid = "xxxxx";
const char* password = "xxxxx";

// LED pin
const int builtInLED = LED_BUILTIN; // Use the built-in LED

// Buzzer pin
const int buzzerPin = 8;

// NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000); // Sync every 60 seconds for demonstration

unsigned long previousMillis = 0;
const long interval = 900000; // 15 minutes in milliseconds

void setup() {
  Serial.begin(115200);

  // Initialize the built-in LED pin
  pinMode(builtInLED, OUTPUT);

  // Initialize the buzzer pin
  pinMode(buzzerPin, OUTPUT);

  // Attempt to connect to WiFi
  if (connectToWiFi()) {
    // Successfully connected to WiFi
    successPattern();
    playImperialMarch();
    Serial.println("Successfully connected to WiFi");

    // Print MAC address and IP address
    Serial.print("MAC address: ");
    Serial.println(WiFi.macAddress());
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    timeClient.begin();
    timeClient.forceUpdate(); // Get the time immediately
    adjustTimeForBST();
    printCurrentDateTime();
  } else {
    // Failed to connect to WiFi
    failurePattern();
    Serial.println("Failed to connect to WiFi");
  }
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    if (WiFi.status() == WL_CONNECTED) {
      timeClient.update();
      adjustTimeForBST();
      printCurrentDateTime();
    }
  }
}

bool connectToWiFi() {
  for (int attempt = 0; attempt < 3; attempt++) {
    // Show connection attempt pattern
    attemptPattern();
    
    Serial.print("Connecting to WiFi, attempt ");
    Serial.println(attempt + 1);

    WiFi.begin(ssid, password);
    unsigned long startAttemptTime = millis();

    // Wait for connection or timeout after 10 seconds
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
      delay(500);
      Serial.print(".");
    }

    Serial.println(); // New line after dots

    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("Connected to WiFi");

      // Delay to ensure network settings are updated
      delay(1000);

      // Print MAC address and IP address
      Serial.print("MAC address: ");
      Serial.println(WiFi.macAddress());
      Serial.print("IP address: ");
      Serial.println(WiFi.localIP());

      return true; // Successfully connected
    } else {
      Serial.print("Failed to connect on attempt ");
      Serial.println(attempt + 1);
      Serial.print("Status: ");
      Serial.println(WiFi.status());
    }
  }
  return false; // Failed to connect after 3 attempts
}

void adjustTimeForBST() {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = gmtime(&rawTime);

  // Calculate last Sunday in March
  struct tm lastSundayInMarch = {0, 0, 1, 31, 2, timeInfo->tm_year, 0, 0, -1};
  mktime(&lastSundayInMarch);
  lastSundayInMarch.tm_mday -= (lastSundayInMarch.tm_wday + 1) % 7;
  mktime(&lastSundayInMarch);

  // Calculate last Sunday in October
  struct tm lastSundayInOctober = {0, 0, 1, 31, 9, timeInfo->tm_year, 0, 0, -1};
  mktime(&lastSundayInOctober);
  lastSundayInOctober.tm_mday -= (lastSundayInOctober.tm_wday + 1) % 7;
  mktime(&lastSundayInOctober);

  time_t startBST = mktime(&lastSundayInMarch);
  time_t endBST = mktime(&lastSundayInOctober);

  if (rawTime >= startBST && rawTime < endBST) {
    // Adjust to BST (GMT+1)
    timeClient.setTimeOffset(3600);
  } else {
    // Adjust to GMT
    timeClient.setTimeOffset(0);
  }
}

void printCurrentDateTime() {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = gmtime(&rawTime);

  char buffer[30];
  strftime(buffer, 30, "%Y-%m-%d %H:%M:%S", timeInfo);

  Serial.print("Current date and time: ");
  Serial.println(buffer);
}

void attemptPattern() {
  blinkLED(250, 2);
}

void successPattern() {
  blinkLED(1000, 1);
}

void failurePattern() {
  blinkLED(100, 10);
}

void blinkLED(int duration, int count) {
  for (int i = 0; i < count; i++) {
    digitalWrite(builtInLED, HIGH);
    delay(duration);
    digitalWrite(builtInLED, LOW);
    delay(duration);
  }
}

void playImperialMarch() {
  // Notes of the Imperial March
  int melody[] = {
    NOTE_A4, NOTE_A4, NOTE_F4, NOTE_C5,
    NOTE_A4, NOTE_F4, NOTE_C5, NOTE_A4,
    NOTE_E5, NOTE_E5, NOTE_E5, NOTE_F5, NOTE_C5, NOTE_GS4, NOTE_F4, NOTE_C5, NOTE_A4
  };

  // Note durations: 4 = quarter note, 8 = eighth note, etc.
  int noteDurations[] = {
    4, 4, 4, 8,
    8, 8, 8, 8,
    4, 4, 4, 4, 8, 8, 8, 8, 4
  };

  // Iterate over the notes of the melody
  for (int thisNote = 0; thisNote < sizeof(melody) / sizeof(melody[0]); thisNote++) {
    // Calculate the note duration
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(buzzerPin, melody[thisNote], noteDuration);
    // To distinguish the notes, set a minimum time between them
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // Stop the tone playing
    noTone(buzzerPin);
  }
}

if you look at that library you'll see

➜ you call the function using a String instance not a cString

so it won't do what you want.

possibly this would be better

  uint8_t* hash = MD5::make_hash(body.c_str());

but the compiler might complain you can't change a const char * into a char *

What do you plan to do with the data once you can pull it from the API?

I decided to start again, but this time, instead of giving everything to the AI at once, I decided to approach it piece by piece. First, I walked it through the global description from the API interface document, followed by the detailed interface information.

Next, I provided the MD5 library so the AI could review the code and ensure it aligned with the function calls. I even included the example code from the README file, and the AI confirmed that the function calls MD5::make_hash and MD5::make_digest were correctly called as static methods of the MD5 class.

However, when I ran my code, I encountered a compilation error: 'MD5' is not a class, namespace, or enumeration. Perplexed, the AI suggested I run the example code on its own. I did, but this also resulted in an error: Compilation error: 'MD5' has not been declared.

I am at a loss. I have compared my AI-generated MD5 computation code with the example code, but I cannot identify any fundamental differences. Yet, neither works for different reasons.

My new code:

 // Calculate MD5 of the body
  unsigned char *hash = MD5::make_hash(const_cast<char*>(jsonBody.c_str()));
  char *md5str = MD5::make_digest(hash, 16);
  String contentMD5 = String(md5str);
  free(hash);
  free(md5str);

Example code from the Readme:

 Serial.begin(9600);
  //give it a second
  delay(1000);
  //generate the MD5 hash for our string
  unsigned char* hash=MD5::make_hash("hello world");
  //generate the digest (hex encoding) of our hash
  char *md5str = MD5::make_digest(hash, 16);
  free(hash);
  //print it on our serial monitor
  Serial.println(md5str);
  //Give the Memory back to the System if you run the md5 Hash generation in a loop
  free(md5str);

I can't help much as there is no public access to the API to test things out.
The AI is just not getting the steps needed correctly apparently.

I quickly browsed through the API doc and it seems that the MD5/Base64 encoding refers to the id and sn fields which do not change. So you could use any online tool to generate that statically and hardcode your information.

Also I would suggest you work out first what to actually send using curl at command line level to test the API or in python from your computer and once you master the exact syntax then it will be easier to port to C++ / your Arduino.

1 Like

Can you post the entire program for the example?

Thanks mate. I just find it strange that I cant even compile the example code from the tzikis/ArduinoMD5 library.

I am starting to wonder if my IDE isn't loading the library correctly? is that possible?

I tested this on Wokwi

#include "MD5.h"
#include "mbedtls/base64.h"
const byte md5Len = 16; // 16 bytes hash
const byte b64Len = ((md5Len + 2) / 3) * 4 + 1; // max length for the base64 encode

void setup() {
  Serial.begin(115200);
  //generate the MD5 hash for our string
  unsigned char* hash = MD5::make_hash("Hello World");  // 128 bits hash = 16 bytes

  Serial.print("Hash = ");
  for (byte i = 0; i < md5Len; i++) {
    if (hash[i] < 0x10) Serial.write('0');
    Serial.print(hash[i], HEX);
    Serial.write(' ');
  }
  Serial.println();

  unsigned char b64Out[b64Len];
  size_t b64OutLen;
  mbedtls_base64_encode(b64Out, b64Len, &b64OutLen, hash, md5Len);
  Serial.print("Base64 = ");
  Serial.println((char*) b64Out);
  free(hash);
}

void loop() {}

it generates the MD5 hash for "Hello World" and then encodes the 128 bits output using Base64

I get

Hash = B1 0A 8D B1 64 E0 75 41 05 B7 A9 9B E7 2E 3F E5 
Base64 = sQqNsWTgdUEFt6mb5y4/5Q==

Now if I go to https://cryptii.com to test the same workflow

I get the exact same thing:

  • the hash is b1 0a 8d b1 64 e0 75 41 05 b7 a9 9b e7 2e 3f e5
  • the base64 encoding sQqNsWTgdUEFt6mb5y4/5Q==

seems the library's code works at least on an ESP32.

I am at work just now so cant test this on my own board. But I can see it running on the ESP32 Wowki simulation you created. Thanks for doing that. I tried to create the same thing on a STM32 board (for curiosity). Silly question, but where is base64.h file in your project? I see it’s included at the top of your sketch as mbedtls/base64.h.

And have you created your own example code I take it, rather than using his?

base64 is one of the standard library on the ESP32, so that's what I'm using.

but there are tons of base64 libraries, here is the same example with the code for one of them embedded and running on a UNO

you'll see the same output

Hash = B1 0A 8D B1 64 E0 75 41 05 B7 A9 9B E7 2E 3F E5 
Base64 = sQqNsWTgdUEFt6mb5y4/5Q==

I think the library might not be installed correctly on my machine then as I too can get that example code to work with on a STM32 board in Woki, which as I understand is the same type as in the Giga.
Just to check something. I have the MD5.cpp and Md5.h files contained within a folder called MD5. This folder is stored at the route of the \Documents\Arduino\libraries. Is this correct configuration?

it's not the typical format but it could work.

Just for peace of mind, I would add the MD5.cpp and Md5.h files in the folder holding your sketch (the .ino) and instead of using the <> in #include <MD5.h> // Include the ArduinoMD5 library use double quotes to tell the compiler to use the local version #include "MD5.h" // Include the ArduinoMD5 library

I did that compiling for an Uno and it compiles, albeit with a couple of warnings about overflow in the library code.

1 Like

What’s the exact error message ?

Good news! I got the example code to run. I guess I didn't have the library configured correctly, not withstanding the other errors to begin with too! thanks for the debug time and education! Now to crack on with my main code. :slight_smile:

OK you have one building block ready
now you need to build the right POST request

I have been at this all morning! I started again, I think I am on my 6th attempt. I tried a different approach with the AI. This is what I have thus far. It still does not compile. However, is it at least starting to look more like a properly constructed request and respond POST, yet? current errors are NINA_GPIO0' was not declared in this scope, note: in definition of macro 'NINA_GPIOIRQ

#include <WiFiNINA.h>
#include <WiFiSSLClient.h>
#include <ArduinoHttpClient.h>
#include "MD5.h"
#include <sha1.h>

// Base64 encoding function
String base64Encode(const unsigned char *input, int length) {
  const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  String encoded = "";
  int i = 0;
  unsigned char char_array_3[3], char_array_4[4];

  while (length--) {
    char_array_3[i++] = *(input++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for (i = 0; i < 4; i++)
        encoded += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i) {
    for (int j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (int j = 0; (j < i + 1); j++)
      encoded += base64_chars[char_array_4[j]];

    while ((i++ < 3))
      encoded += '=';
  }

  return encoded;
}

const char ssid[] = "xxxxx";
const char pass[] = "xxxxx";

const char serverAddress[] = "www.soliscloud.com"; // server address
const int port = 13333; // server port

const char apiId[] = "xxxxx";
const char apiSecret[] = "xxxxx";
const char inverterSerialNo[] = "xxxxx"; // Inverter Serial Number

WiFiSSLClient wifi;
HttpClient client = HttpClient(wifi, serverAddress, port);

String getGMTTime() {
  time_t now;
  struct tm* tm_info;
  char buffer[30];

  time(&now);
  tm_info = gmtime(&now);

  strftime(buffer, 30, "%a, %d %b %Y %H:%M:%S GMT", tm_info);
  return String(buffer);
}

String calculateMD5Base64(String body) {
  unsigned char* hash = MD5::make_hash((char*)body.c_str(), body.length()); // Generate MD5 hash
  String result = base64Encode(hash, 16); // Base64 encode the binary hash
  free(hash); // Free allocated memory
  return result; // Return Base64 encoded MD5 hash
}

String calculateHMAC_SHA1(String data, String key) {
  int blockSize = 64; // 64 bytes
  if (key.length() > blockSize) {
    key = calculateMD5Base64(key); // If key is longer than blockSize, hash it
  }

  while (key.length() < blockSize) {
    key += char(0x00); // Pad key to blockSize with zeros
  }

  String oKeyPad = String();
  String iKeyPad = String();
  for (int i = 0; i < blockSize; i++) {
    oKeyPad += String(key[i] ^ 0x5c); // XOR key with 0x5c
    iKeyPad += String(key[i] ^ 0x36); // XOR key with 0x36
  }

  unsigned char hmac[20]; // HMAC-SHA1 produces a 20-byte hash
  sha1_context ctx;
  sha1_starts(&ctx);
  sha1_update(&ctx, (const unsigned char*)oKeyPad.c_str(), oKeyPad.length());
  sha1_update(&ctx, (const unsigned char*)data.c_str(), data.length());
  sha1_finish(&ctx, hmac);

  return base64Encode(hmac, 20); // Base64 encode the HMAC-SHA1 hash
}

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect.
  }

  Serial.print("Connecting to ");
  Serial.println(ssid);

  int status = WiFi.begin(ssid, pass);
  if (status != WL_CONNECTED) {
    Serial.println("Couldn't get a wifi connection");
    while (true);
  }
  Serial.println("Connected to WiFi");

  String body = "{\"id\": \"" + String(apiId) + "\",\"sn\": \"" + String(inverterSerialNo) + "\"}";
  String contentMD5 = calculateMD5Base64(body); // Calculate Base64 encoded MD5 of body
  String date = getGMTTime();
  String canonicalizedResource = "/v1/api/inverter"; // API interface path

  // Construct the string to sign based on typical API signing practices
  String stringToSign = "POST\n" + contentMD5 + "\napplication/json;charset=UTF-8\n" + date + "\n" + canonicalizedResource;
  String signature = calculateHMAC_SHA1(stringToSign, apiSecret); // Calculate HMAC-SHA1 signature
  String authorization = "API " + String(apiId) + ":" + signature;

  client.beginRequest();
  client.post(canonicalizedResource); // API interface path
  client.sendHeader("Content-Type", "application/json;charset=UTF-8"); // Specify JSON content with UTF-8 encoding
  client.sendHeader("Content-MD5", contentMD5); // Include Base64 encoded MD5 hash of the body
  client.sendHeader("Date", date); // Include current GMT date
  client.sendHeader("Authorization", authorization); // Include the Authorization header
  client.sendHeader("Content-Length", body.length()); // Specify the length of the body
  client.beginBody();
  client.print(body);
  client.endRequest();

  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status code: ");
  Serial.println(statusCode);
  Serial.print("Response: ");
  Serial.println(response);
}

void loop() {
  // Do nothing here
}


I asked the AI to review the standard format for requests and returns, to check its correctly implemented the format into its code, and then to confirm or deny conformance to the format. This is what it did!

Let's go through each part of the format requirements specified in the document and confirm whether the code conforms to the format procedures for each name.

Standard Format for Requests

1. API URL

Description: Specific address of each interface. Example: https://www.soliscloud.com:13333/v1/api/inverter

Code:

cpp

Copy code

const char serverAddress[] = "www.soliscloud.com"; // server address
const int port = 13333; // server port
client.post("/v1/api/inverter"); // API interface path

Conformance: Yes

2. Content-MD5

Description:

  1. Perform MD5 encryption on the body;
  2. Convert encrypted content into a 128-bit binary array;
  3. Base64 encoding of binary arrays.

Example:

java

Copy code

public static String getDigest(String test) {
  String result = "";
  try {
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(test.getBytes());
    byte[] b = md.digest();
    result = Base64.encodeBytes(b);
  } catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
  }
  return result;
}

Code:

cpp

Copy code

String calculateMD5Base64(String body) {
  unsigned char* hash = MD5::make_hash((char*)body.c_str(), body.length()); // Generate MD5 hash
  String result = base64Encode(hash, 16); // Base64 encode the binary hash
  free(hash); // Free allocated memory
  return result; // Return Base64 encoded MD5 hash
}
String contentMD5 = calculateMD5Base64(body);
client.sendHeader("Content-MD5", contentMD5);

Conformance: Yes

3. Content-Type

Description: Fixed value application/json;charset=UTF-8

Code:

cpp

Copy code

client.sendHeader("Content-Type", "application/json;charset=UTF-8");

Conformance: Yes

4. Date

Description:

  1. Obtain the current time in the GMT time zone;
  2. Convert the current time to a string in the following format: EEE d MMM yyyy HH:mm:ss 'GMT' Note: The Date time cannot exceed the current time by more than plus or minus 15 minutes.

Example:

java

Copy code

public static String getGMTTime(){
  Calendar cd = Calendar.getInstance();
  SimpleDateFormat sdf = new SimpleDateFormat("EEE d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
  sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
  String str = sdf.format(cd.getTime());
  return str;
}

Code:

cpp

Copy code

String getGMTTime() {
  time_t now;
  struct tm* tm_info;
  char buffer[30];
  time(&now);
  tm_info = gmtime(&now);
  strftime(buffer, 30, "%a, %d %b %Y %H:%M:%S GMT", tm_info);
  return String(buffer);
}
String date = getGMTTime();
client.sendHeader("Date", date);

Conformance: Yes

5. Authorization

Description: The legitimacy authentication information of the interface request, in the format shown in the example, is explained as follows:

  • apiId represents the visitor's identity ID.
  • apiSecret represents the key required for signing.
  • CanonicalizedResource represents the API interface path that you want to access, such as /v1/api/inverterDetail
  • Sign represents a digital signature, which is obtained by encrypting the HmacSHA1 key with base64 encoding.
  • \n represents a line break.

Example:

java

Copy code

Authorization : "API " + apiId + ":" + Sign
Sign = base64(HmacSHA1(apiSecretPOST + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedResource))

Code:

cpp

Copy code

String calculateHMAC_SHA1(String data, String key) {
  int blockSize = 64; // 64 bytes
  if (key.length() > blockSize) {
    key = calculateMD5Base64(key); // If key is longer than blockSize, hash it
  }

  while (key.length() < blockSize) {
    key += char(0x00); // Pad key to blockSize with zeros
  }

  String oKeyPad = String();
  String iKeyPad = String();
  for (int i = 0; i < blockSize; i++) {
    oKeyPad += String(key[i] ^ 0x5c); // XOR key with 0x5c
    iKeyPad += String(key[i] ^ 0x36); // XOR key with 0x36
  }

  unsigned char hmac[20]; // HMAC-SHA1 produces a 20-byte hash
  sha1_context ctx;
  sha1_starts(&ctx);
  sha1_update(&ctx, (const unsigned char*)oKeyPad.c_str(), oKeyPad.length());
  sha1_update(&ctx, (const unsigned char*)data.c_str(), data.length());
  sha1_finish(&ctx, hmac);

  return base64Encode(hmac, 20); // Base64 encode the HMAC-SHA1 hash
}

String body = "{\"id\": \"" + String(apiId) + "\",\"sn\": \"" + String(inverterSerialNo) + "\"}";
String contentMD5 = calculateMD5Base64(body); // Calculate Base64 encoded MD5 of body
String date = getGMTTime();
String canonicalizedResource = "/v1/api/inverter"; // API interface path

// Construct the string to sign based on typical API signing practices
String stringToSign = "POST\n" + contentMD5 + "\napplication/json;charset=UTF-8\n" + date + "\n" + canonicalizedResource;
String signature = calculateHMAC_SHA1(stringToSign, apiSecret); // Calculate HMAC-SHA1 signature
String authorization = "API " + String(apiId) + ":" + signature;
client.sendHeader("Authorization", authorization);

Conformance: Yes

6. Body

Description: Please refer to the request parameters for each business interface for details. Example:

json

Copy code

{
  "id": "1308675217944611083",
  "sn": "120B40198150131"
}

Code:

cpp

Copy code

String body = "{\"id\": \"" + String(apiId) + "\",\"sn\": \"" + String(inverterSerialNo) + "\"}";
client.beginBody();
client.print(body);
client.endRequest();

Conformance: Yes

Standard Format for Returns

1. Content-Type

Description: Fixed value application/json;charset=UTF-8 Example:

json

Copy code

Content-Type: application/json;charset=UTF-8

Code: Response handling in the code does not explicitly check Content-Type but assumes JSON response handling. Conformance: Implicitly yes

2. Date

Description: GMT time in string format Example:

json

Copy code

Date: Fri 26 Jul 2019 06:00:46 GMT

Code: Response handling in the code does not explicitly parse the Date header. Conformance: Implicitly yes

3. Body

Description: The body content includes: {success, code, msg, data} Example:

json

Copy code

{
  "success": true,
  "code": "0",
  "msg": "success",
  "data": {}
}

Code:

cpp

Copy code

int statusCode = client.responseStatusCode();
String response = client.responseBody();
Serial.print("Status code: ");
Serial.println(statusCode);
Serial.print("Response: ");
Serial.println(response);

Conformance: Yes

Summary

The code conforms to the specified format for requests and responses as outlined in the provided document. Each required header and body content is constructed according to the standard procedures .