API_INTEGRATION.md - SolarLog API Documentation
Integration guide for SolarLog API (https://solarlog-api.karma.organic)
π API Overview
Base URL: https://solarlog-api.karma.organic
Documentation: https://solarlog-api.karma.organic/docs
Protocol: HTTPS (TLS 1.2+)
Format: JSON (application/json)
Authentication: TBD (check API docs for Bearer token / API key)
π Authentication
β οΈ TODO: Research Required
Check the API documentation to determine: 1. Authentication Method: Bearer token, API key, OAuth2? 2. Token Acquisition: How to obtain API credentials? 3. Token Lifespan: Does token expire? Refresh mechanism? 4. Rate Limiting: Requests per minute/hour limit?
Placeholder Implementation
// firmware/src/network/solarlog_api.h
#define SOLARLOG_API_KEY "your-api-key-here" // Store in NVS
#define SOLARLOG_API_URL "https://solarlog-api.karma.organic/api/v1"
// Example: Bearer token authentication (TBD)
void api_set_auth_header(HTTPClient &http) {
String auth = "Bearer " + String(SOLARLOG_API_KEY);
http.addHeader("Authorization", auth);
http.addHeader("Content-Type", "application/json");
}
π‘ API Endpoints
β οΈ Endpoints are placeholder - verify with actual API docs!
1. POST /api/v1/inverters/{inverter_id}/data
Purpose: Submit solar production data from inverter
Method: POST
URL: https://solarlog-api.karma.organic/api/v1/inverters/{inverter_id}/data
Headers:
Request Body:
{
"timestamp": "2025-10-23T09:30:00Z",
"power_w": 3500,
"energy_kwh": 12.5,
"voltage_v": 230,
"current_a": 15.2,
"status": "online",
"temperature_c": 45.3
}
Response (200 OK):
Response (400 Bad Request):
Response (401 Unauthorized):
2. GET /api/v1/inverters/{inverter_id}/status
Purpose: Check inverter status and last data point
Method: GET
URL: https://solarlog-api.karma.organic/api/v1/inverters/{inverter_id}/status
Response (200 OK):
{
"inverter_id": "inv-001",
"status": "online",
"last_update": "2025-10-23T09:25:00Z",
"power_w": 3500,
"energy_today_kwh": 12.5
}
3. GET /api/v1/inverters/{inverter_id}/history
Purpose: Retrieve historical data (last 7/30 days)
Method: GET
URL: https://solarlog-api.karma.organic/api/v1/inverters/{inverter_id}/history?days=7
Query Parameters:
- days: Number of days (1-365)
- resolution: hourly | daily (optional)
Response (200 OK):
{
"inverter_id": "inv-001",
"days": 7,
"data": [
{"date": "2025-10-17", "energy_kwh": 18.3},
{"date": "2025-10-18", "energy_kwh": 22.1},
{"date": "2025-10-19", "energy_kwh": 19.8},
{"date": "2025-10-20", "energy_kwh": 21.5},
{"date": "2025-10-21", "energy_kwh": 20.2},
{"date": "2025-10-22", "energy_kwh": 23.7},
{"date": "2025-10-23", "energy_kwh": 12.5}
]
}
π» ESP32 Implementation
HTTP Client (HTTPS)
// firmware/src/network/solarlog_api.cpp
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "solarlog_api.h"
WiFiClientSecure secureClient;
HTTPClient http;
bool api_init() {
// Set root CA certificate (for HTTPS validation)
// TODO: Add karma.organic root CA cert
// secureClient.setCACert(root_ca_cert);
// OR: Skip certificate validation (NOT recommended for production)
secureClient.setInsecure();
return true;
}
bool api_post_data(float power_w, float energy_kwh, const char* status) {
String url = String(SOLARLOG_API_URL) + "/inverters/inv-001/data";
http.begin(secureClient, url);
http.addHeader("Authorization", "Bearer " + String(SOLARLOG_API_KEY));
http.addHeader("Content-Type", "application/json");
// Create JSON payload
StaticJsonDocument<256> doc;
doc["timestamp"] = get_iso8601_timestamp(); // Helper function
doc["power_w"] = power_w;
doc["energy_kwh"] = energy_kwh;
doc["status"] = status;
String payload;
serializeJson(doc, payload);
// Send POST request
int httpCode = http.POST(payload);
if (httpCode == 200) {
String response = http.getString();
Serial.println("β
API POST success: " + response);
http.end();
return true;
} else {
Serial.printf("β API POST failed: %d - %s\n", httpCode, http.errorToString(httpCode).c_str());
http.end();
return false;
}
}
String get_iso8601_timestamp() {
// Get current time from NTP or RTC
time_t now;
time(&now);
char buffer[25];
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
return String(buffer);
}
π‘οΈ Error Handling
Retry Logic
bool api_post_with_retry(float power, float energy, int max_retries = 3) {
for (int attempt = 1; attempt <= max_retries; attempt++) {
Serial.printf("π€ API POST attempt %d/%d\n", attempt, max_retries);
if (api_post_data(power, energy, "online")) {
return true; // Success
}
// Exponential backoff: 2s, 4s, 8s
int delay_ms = (1 << attempt) * 1000;
Serial.printf("β³ Retry in %d ms...\n", delay_ms);
delay(delay_ms);
}
Serial.println("β API POST failed after all retries");
return false;
}
Network Error Handling
typedef enum {
API_SUCCESS,
API_ERR_NETWORK, // WiFi not connected
API_ERR_TIMEOUT, // HTTP timeout
API_ERR_AUTH, // 401 Unauthorized
API_ERR_BAD_REQUEST, // 400 Bad Request
API_ERR_SERVER, // 500 Server Error
API_ERR_UNKNOWN
} api_error_t;
api_error_t api_post_data_ex(float power, float energy) {
// Check WiFi first
if (WiFi.status() != WL_CONNECTED) {
return API_ERR_NETWORK;
}
// ... HTTP request ...
int httpCode = http.POST(payload);
if (httpCode == 200) return API_SUCCESS;
else if (httpCode == 401) return API_ERR_AUTH;
else if (httpCode == 400) return API_ERR_BAD_REQUEST;
else if (httpCode >= 500) return API_ERR_SERVER;
else if (httpCode < 0) return API_ERR_TIMEOUT;
else return API_ERR_UNKNOWN;
}
π Data Caching (Offline Mode)
Store failed uploads in NVS
// Cache data locally if API upload fails
void cache_data_point(float power, float energy) {
Preferences prefs;
prefs.begin("cache", false);
// Store up to 100 data points (circular buffer)
int count = prefs.getInt("count", 0);
String key = "dp_" + String(count % 100);
StaticJsonDocument<128> doc;
doc["t"] = millis();
doc["p"] = power;
doc["e"] = energy;
String data;
serializeJson(doc, data);
prefs.putString(key.c_str(), data);
prefs.putInt("count", count + 1);
prefs.end();
Serial.printf("πΎ Cached data point #%d\n", count);
}
// Retry uploading cached data when online
void flush_cached_data() {
Preferences prefs;
prefs.begin("cache", false);
int count = prefs.getInt("count", 0);
if (count == 0) {
prefs.end();
return; // Nothing to flush
}
Serial.printf("π€ Flushing %d cached data points...\n", count);
for (int i = 0; i < min(count, 100); i++) {
String key = "dp_" + String(i);
String data = prefs.getString(key.c_str(), "");
if (data.length() > 0) {
// Parse and upload
StaticJsonDocument<128> doc;
deserializeJson(doc, data);
if (api_post_data(doc["p"], doc["e"], "cached")) {
prefs.remove(key.c_str()); // Delete on success
}
}
}
prefs.putInt("count", 0); // Reset counter
prefs.end();
}
π Update Intervals
Recommended Schedule
| Interval | Use Case | Battery Life Impact |
|---|---|---|
| 5 min | Real-time monitoring | ~30 days |
| 15 min | Normal operation | ~90 days |
| 30 min | Battery saving mode | ~180 days |
| 60 min | Minimal updates | ~360 days |
Implementation
#define UPDATE_INTERVAL_MS (5 * 60 * 1000) // 5 minutes
void loop() {
static unsigned long last_update = 0;
if (millis() - last_update > UPDATE_INTERVAL_MS) {
// 1. Read inverter data
float power = inverter_get_power();
float energy = inverter_get_energy();
// 2. POST to API
if (api_post_with_retry(power, energy)) {
Serial.println("β
Data uploaded");
} else {
Serial.println("β Upload failed, caching...");
cache_data_point(power, energy);
}
// 3. Update display
screen_dashboard_update(power, energy);
last_update = millis();
}
lv_timer_handler();
delay(10);
}
π§ͺ Testing
1. Mock API Server (Development)
Use Postman or httpbin for testing:
# Test POST with httpbin.org
curl -X POST https://httpbin.org/post \
-H "Authorization: Bearer test-key-123" \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2025-10-23T10:00:00Z",
"power_w": 3500,
"energy_kwh": 12.5
}'
2. ESP32 Serial Monitor
void test_api() {
Serial.println("π§ͺ Testing API connection...");
if (api_post_data(3500, 12.5, "online")) {
Serial.println("β
API test PASSED");
} else {
Serial.println("β API test FAILED");
}
}
π Troubleshooting
Issue: 401 Unauthorized
- β Verify API key is correct
- β Check if key is expired
- β
Ensure
Authorizationheader format is correct
Issue: SSL/TLS Handshake Failed
- β
Check if
secureClient.setInsecure()is enabled (for testing) - β Add root CA certificate for karma.organic domain
- β Verify ESP32 has correct date/time (NTP sync required)
Issue: Timeout
- β Check WiFi signal strength (RSSI)
- β
Increase HTTP timeout:
http.setTimeout(10000)(10s) - β Verify API endpoint URL is correct
π Resources
- API Documentation: https://solarlog-api.karma.organic/docs
- ESP32 HTTPClient: https://github.com/espressif/arduino-esp32/tree/master/libraries/HTTPClient
- ArduinoJson: https://arduinojson.org/v6/doc/
β οΈ CRITICAL TODO:
1. Visit https://solarlog-api.karma.organic/docs
2. Obtain API credentials (API key or OAuth token)
3. Update authentication code in solarlog_api.cpp
4. Test with real API endpoint
5. Document actual request/response format
Last Updated: 2025-10-23
API Version: TBD
Status: π§ Placeholder - Requires API Documentation Review