2023-03-20 11:13:36 +00:00
|
|
|
|
|
|
|
// inspred by https://github.com/MoritzLerch/tesla-pv-display
|
|
|
|
#ifndef Powerwall_h
|
|
|
|
#define Powerwall_h
|
|
|
|
|
|
|
|
// include libraries
|
|
|
|
#include "WiFiClientSecureLightBearSSL.h"
|
|
|
|
|
|
|
|
class Powerwall {
|
|
|
|
private:
|
|
|
|
const char* powerwall_ip;
|
|
|
|
String tesla_email;
|
|
|
|
String tesla_password;
|
|
|
|
String authCookie;
|
|
|
|
|
|
|
|
public:
|
|
|
|
Powerwall();
|
|
|
|
String getAuthCookie();
|
|
|
|
String GetRequest(String url, String authCookie);
|
|
|
|
String GetRequest(String url);
|
|
|
|
String AuthCookie();
|
2023-05-05 08:17:17 +01:00
|
|
|
void resetAuthCookie();
|
2023-03-20 11:13:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Powerwall::Powerwall() {
|
|
|
|
powerwall_ip = POWERWALL_IP_CONFIG;
|
|
|
|
tesla_email = TESLA_EMAIL;
|
|
|
|
tesla_password = TESLA_PASSWORD;
|
|
|
|
authCookie = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
String Powerwall::AuthCookie() {
|
|
|
|
return authCookie;
|
|
|
|
}
|
2023-05-05 08:17:17 +01:00
|
|
|
void Powerwall::resetAuthCookie() {
|
|
|
|
authCookie = "";
|
|
|
|
}
|
2023-03-20 11:13:36 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This function returns a string with the authToken based on the basic login endpoint of
|
|
|
|
* the powerwall in combination with the credentials from the secrets.h
|
|
|
|
* @returns authToken to be used in an authCookie
|
|
|
|
*/
|
|
|
|
String Powerwall::getAuthCookie() {
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("PWL: requesting new auth Cookie from %s"), powerwall_ip);
|
|
|
|
String apiLoginURL = "/api/login/Basic";
|
|
|
|
|
|
|
|
#ifdef ESP32
|
|
|
|
WiFiClientSecure *httpsClient = new WiFiClientSecure;
|
|
|
|
#else
|
|
|
|
// BearSSL::WiFiClientSecure_light *httpsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
|
|
|
|
WiFiClientSecure *httpsClient = new WiFiClientSecure;
|
|
|
|
#endif
|
|
|
|
httpsClient->setInsecure();
|
|
|
|
httpsClient->setTimeout(10000);
|
|
|
|
|
|
|
|
int retry = 0;
|
|
|
|
|
|
|
|
#define PW_RETRIES 5
|
|
|
|
while ((!httpsClient->connect(powerwall_ip, 443)) && (retry < PW_RETRIES)) {
|
|
|
|
delay(100);
|
|
|
|
Serial.print(".");
|
|
|
|
retry++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (retry >= PW_RETRIES) {
|
|
|
|
delete httpsClient;
|
|
|
|
return ("CONN-FAIL");
|
|
|
|
}
|
|
|
|
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("PWL: connected"));
|
|
|
|
|
|
|
|
String dataString = "{\"username\":\"customer\",\"email\":\"" + tesla_email + "\",\"password\":\"" + tesla_password + "\",\"force_sm_off\":false}";
|
|
|
|
|
|
|
|
String payload = String("POST ") + apiLoginURL + " HTTP/1.1\r\n" +
|
|
|
|
"Host: " + powerwall_ip + "\r\n" +
|
|
|
|
"Connection: close" + "\r\n" +
|
|
|
|
"Content-Type: application/json" + "\r\n" +
|
|
|
|
"Content-Length: " + dataString.length() + "\r\n" +
|
|
|
|
"\r\n" + dataString + "\r\n\r\n";
|
|
|
|
|
|
|
|
httpsClient->println(payload);
|
|
|
|
|
|
|
|
while (httpsClient->connected()) {
|
|
|
|
String response = httpsClient->readStringUntil('\n');
|
|
|
|
if (response == "\r") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String jsonInput = httpsClient->readStringUntil('\n');
|
|
|
|
|
|
|
|
char str_value[128];
|
|
|
|
str_value[0] = 0;
|
|
|
|
float fv;
|
|
|
|
JsonParser parser((char*)jsonInput.c_str());
|
|
|
|
JsonParserObject obj = parser.getRootObject();
|
|
|
|
uint32_t res = JsonParsePath(&obj, "token", '#', &fv, str_value, sizeof(str_value));
|
|
|
|
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("PWL: token: %s"), str_value);
|
|
|
|
|
|
|
|
authCookie = str_value;
|
|
|
|
|
|
|
|
delete httpsClient;
|
|
|
|
|
|
|
|
return authCookie;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function does a GET-request on the local powerwall web server.
|
|
|
|
* This is mainly used here to do API requests.
|
|
|
|
* HTTP/1.0 is used because some responses are so big that this would encounter
|
|
|
|
* chunked transfer encoding in HTTP/1.1 (https://en.wikipedia.org/wiki/Chunked_transfer_encoding)
|
|
|
|
*
|
|
|
|
* @param url relative URL on the Powerwall
|
|
|
|
* @param authCookie optional, but recommended
|
|
|
|
* @returns content of request
|
|
|
|
*/
|
|
|
|
String Powerwall::GetRequest(String url, String authCookie) {
|
|
|
|
#ifdef ESP32
|
|
|
|
WiFiClientSecure *httpsClient = new WiFiClientSecure;
|
|
|
|
#else
|
|
|
|
//BearSSL::WiFiClientSecure_light *httpsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
|
|
|
|
WiFiClientSecure *httpsClient = new WiFiClientSecure;
|
|
|
|
#endif
|
|
|
|
httpsClient->setInsecure();
|
|
|
|
httpsClient->setTimeout(10000);
|
|
|
|
|
|
|
|
if (authCookie == "") {
|
|
|
|
getAuthCookie();
|
|
|
|
}
|
|
|
|
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("PWL: doing GET-request to %s%s"), powerwall_ip, url.c_str());
|
|
|
|
|
|
|
|
int retry = 0;
|
|
|
|
|
|
|
|
while ((!httpsClient->connect(powerwall_ip, 443)) && (retry < 15)) {
|
|
|
|
delay(100);
|
|
|
|
Serial.print(".");
|
|
|
|
retry++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (retry >= 15) {
|
|
|
|
delete httpsClient;
|
|
|
|
return ("CONN-FAIL");
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTP/1.0 is used because of Chunked transfer encoding
|
|
|
|
httpsClient->print(String("GET ") + url + " HTTP/1.0" + "\r\n" +
|
|
|
|
"Host: " + powerwall_ip + "\r\n" +
|
|
|
|
"Cookie: " + "AuthCookie" + "=" + authCookie + "\r\n" +
|
|
|
|
"Connection: close\r\n\r\n");
|
|
|
|
|
|
|
|
while (httpsClient->connected()) {
|
|
|
|
String response = httpsClient->readStringUntil('\n');
|
|
|
|
char *cp = (char*)response.c_str();
|
|
|
|
if (!strncmp_P(cp, PSTR("HTTP"), 4)) {
|
|
|
|
char *sp = strchr(cp, ' ');
|
|
|
|
if (sp) {
|
|
|
|
sp++;
|
|
|
|
uint16_t result = strtol(sp, 0, 10);
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("PWL: result %d"), result);
|
|
|
|
// in case of error 401, get new cookie
|
|
|
|
if (result == 401) {
|
|
|
|
authCookie = "";
|
2023-05-05 08:17:17 +01:00
|
|
|
resetAuthCookie();
|
2023-03-20 11:13:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (response == "\r") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String result = httpsClient->readStringUntil('\n');
|
|
|
|
delete httpsClient;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* this is getting called if there was no provided authCookie in powerwallGetRequest(String url, String authCookie)
|
|
|
|
*/
|
|
|
|
String Powerwall::GetRequest(String url) {
|
|
|
|
return (GetRequest(url, getAuthCookie()));
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|