BankliPlus/clienttest1.txt
2025-08-13 18:05:26 +02:00

758 lines
22 KiB
Plaintext

/*
* LILYGO T-A7670G IoT Client for SolarBank Dashboard
* Compatible with SolarBank IoT Dashboard API
*
* Features:
* - Automatic authentication and token management
* - Support for both public and authenticated endpoints
* - Auto-retry mechanism
* - Battery percentage calculation
*
* Version: 2.0.0
* API Base URL: http://172.238.109.129/api
*/
#define LILYGO_T_A7670
// ===== PIN DEFINITIONS =====
#define BOARD_MODEM_PWR_PIN 4
#define BOARD_MODEM_TX_PIN 26
#define BOARD_MODEM_RX_PIN 27
#define BOARD_MODEM_DTR_PIN 32
#define BOARD_POWERON_PIN 12
// Sensor pins
#define DHT11_PIN 13
#define VOLTAGE_PIN_SOLAR 34 // Solar panel voltage sensor
#define VOLTAGE_PIN_BATTERY 35 // Battery voltage sensor
// ===== LIBRARIES =====
#include <Arduino.h>
#include <DHT.h>
#include <ArduinoJson.h>
// ===== API CONFIGURATION =====
const char* API_BASE_URL = "172.238.109.129";
const int API_PORT = 80;
const char* API_USERNAME = "admin"; // Change to your username
const char* API_PASSWORD = "admin123"; // Change to your password
const char* APN = "internet"; // Your carrier APN
// Device configuration
const char* DEVICE_ID = "LILYGO_SOLAR_001";
const char* DEVICE_NAME = "Solar Station #1";
const char* DEVICE_MODEL = "LILYGO T-A7670G";
const char* FIRMWARE_VERSION = "2.0.0";
// Timing intervals (milliseconds)
const unsigned long SEND_INTERVAL = 60000; // Send data every 60 seconds
const unsigned long GPS_UPDATE_INTERVAL = 10000; // Update GPS every 10 seconds
const unsigned long TOKEN_REFRESH_DAYS = 7; // Refresh token every 7 days
// Voltage divider ratios
const float SOLAR_DIVIDER_RATIO = 5.0; // 5:1 for 0-25V range
const float BATTERY_DIVIDER_RATIO = 4.0; // 4:1 for 0-15V range
// Battery voltage range (for percentage calculation)
const float BATTERY_MIN_VOLTAGE = 10.8;
const float BATTERY_MAX_VOLTAGE = 12.6;
// ===== OBJECTS =====
HardwareSerial SerialAT(1);
DHT dht(DHT11_PIN, DHT11);
// ===== GLOBAL VARIABLES =====
// Authentication
String authToken = "";
unsigned long tokenObtainedTime = 0;
bool useAuthentication = false; // Set to true to use authenticated endpoints
// Sensor data
float temperature = 0;
float humidity = 0;
float voltageSolar = 0;
float voltageBattery = 0;
float batteryPercentage = 0;
int signalStrength = -99;
// GPS data
float latitude = 0;
float longitude = 0;
float altitude = 0;
int satellites = 0;
bool gpsFixed = false;
// System status
bool modemReady = false;
bool networkConnected = false;
unsigned long lastSendTime = 0;
unsigned long lastGPSTime = 0;
int consecutiveFailures = 0;
// ===== FUNCTION DECLARATIONS =====
bool initModem();
bool connectNetwork();
void initGPS();
void readSensors();
void updateGPS();
bool sendData();
bool authenticateAPI();
String buildJsonPayload();
bool sendDataToAPI(String json);
void sendATCommand(const char* cmd, unsigned long timeout = 1000);
String getATResponse(const char* cmd, unsigned long timeout = 1000);
void getSignalStrength();
void checkGPSStatus();
float parseCoordinate(String coord);
void getGPSSatellites();
void calculateBatteryPercentage();
bool sendViaTCP(String endpoint, String payload, String method = "POST");
void createDeviceIfNeeded();
void sendLogEntry(String level, String message, String source);
// ===== SETUP =====
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 5000);
printHeader();
// Initialize power control
pinMode(BOARD_POWERON_PIN, OUTPUT);
digitalWrite(BOARD_POWERON_PIN, HIGH);
Serial.println("[POWER] Board power control enabled");
// Initialize sensors
initializeSensors();
// Initialize modem
if (initModem()) {
initGPS();
getSignalStrength();
if (connectNetwork()) {
// Send startup log
sendLogEntry("INFO", "Device started successfully", "system");
// Authenticate if needed
if (useAuthentication) {
authenticateAPI();
}
// Create device entry
createDeviceIfNeeded();
// Send initial data
readSensors();
sendData();
}
} else {
Serial.println("[ERROR] Failed to initialize modem!");
}
}
// ===== MAIN LOOP =====
void loop() {
unsigned long currentTime = millis();
// Update GPS periodically
if (currentTime - lastGPSTime >= GPS_UPDATE_INTERVAL) {
lastGPSTime = currentTime;
updateGPS();
}
// Send data periodically
if (currentTime - lastSendTime >= SEND_INTERVAL) {
lastSendTime = currentTime;
// Check token validity if using authentication
if (useAuthentication && shouldRefreshToken()) {
authenticateAPI();
}
readSensors();
if (!sendData()) {
consecutiveFailures++;
Serial.printf("[WARNING] Failed to send data (failures: %d)\n", consecutiveFailures);
// Try to reconnect after 3 failures
if (consecutiveFailures >= 3) {
Serial.println("[WARNING] Multiple failures, reconnecting...");
connectNetwork();
consecutiveFailures = 0;
}
} else {
consecutiveFailures = 0;
}
}
// Handle serial commands
handleSerialCommands();
}
// ===== INITIALIZATION FUNCTIONS =====
void printHeader() {
Serial.println("\n\n");
Serial.println("===========================================");
Serial.println(" SolarBank IoT Client v2.0");
Serial.println("===========================================");
Serial.print("Device ID: ");
Serial.println(DEVICE_ID);
Serial.print("API Server: ");
Serial.print(API_BASE_URL);
Serial.print(":");
Serial.println(API_PORT);
Serial.print("Authentication: ");
Serial.println(useAuthentication ? "Enabled" : "Disabled (Public endpoints)");
Serial.println("===========================================\n");
}
void initializeSensors() {
Serial.println("[INIT] Initializing sensors...");
// DHT11
dht.begin();
delay(1000);
Serial.println("[SENSOR] DHT11 initialized");
// Voltage sensors
pinMode(VOLTAGE_PIN_SOLAR, INPUT);
pinMode(VOLTAGE_PIN_BATTERY, INPUT);
Serial.println("[SENSOR] Voltage sensors initialized");
// Modem pins
pinMode(BOARD_MODEM_PWR_PIN, OUTPUT);
pinMode(BOARD_MODEM_DTR_PIN, OUTPUT);
digitalWrite(BOARD_MODEM_DTR_PIN, LOW);
}
// ===== AUTHENTICATION =====
bool authenticateAPI() {
Serial.println("\n[AUTH] Authenticating with API...");
StaticJsonDocument<256> doc;
doc["username"] = API_USERNAME;
doc["password"] = API_PASSWORD;
String payload;
serializeJson(doc, payload);
String response = "";
if (sendViaTCP("/api/auth/login/json", payload, "POST")) {
// Extract token from response
int tokenStart = response.indexOf("\"access_token\":\"");
if (tokenStart != -1) {
tokenStart += 16;
int tokenEnd = response.indexOf("\"", tokenStart);
if (tokenEnd != -1) {
authToken = response.substring(tokenStart, tokenEnd);
tokenObtainedTime = millis();
Serial.println("[AUTH] Authentication successful");
return true;
}
}
}
Serial.println("[AUTH] Authentication failed");
return false;
}
bool shouldRefreshToken() {
if (authToken.length() == 0) return true;
unsigned long tokenAge = millis() - tokenObtainedTime;
unsigned long tokenAgeHours = tokenAge / (1000UL * 60 * 60);
return tokenAgeHours >= (TOKEN_REFRESH_DAYS * 24);
}
// ===== SENSOR FUNCTIONS =====
void readSensors() {
Serial.println("\n[SENSOR] Reading sensors...");
// Read DHT11
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
humidity = h;
temperature = t;
} else {
Serial.println("[WARNING] DHT11 read error, using last values");
}
// Read voltage sensors with averaging
long sumSolar = 0, sumBattery = 0;
const int samples = 10;
for (int i = 0; i < samples; i++) {
sumSolar += analogRead(VOLTAGE_PIN_SOLAR);
sumBattery += analogRead(VOLTAGE_PIN_BATTERY);
delay(10);
}
voltageSolar = ((sumSolar / samples) / 4095.0) * 3.3 * SOLAR_DIVIDER_RATIO;
voltageBattery = ((sumBattery / samples) / 4095.0) * 3.3 * BATTERY_DIVIDER_RATIO;
// Calculate battery percentage
calculateBatteryPercentage();
// Get signal strength
getSignalStrength();
// Print values
Serial.printf("[SENSOR] Temp: %.1f°C, Humidity: %.1f%%\n", temperature, humidity);
Serial.printf("[SENSOR] Solar: %.2fV, Battery: %.2fV (%.1f%%)\n",
voltageSolar, voltageBattery, batteryPercentage);
Serial.printf("[SENSOR] Signal: %d dBm\n", signalStrength);
if (gpsFixed) {
Serial.printf("[GPS] Location: %.6f, %.6f (Alt: %.1fm, Sat: %d)\n",
latitude, longitude, altitude, satellites);
} else {
Serial.println("[GPS] No fix yet");
}
}
void calculateBatteryPercentage() {
if (voltageBattery < BATTERY_MIN_VOLTAGE) {
batteryPercentage = 0;
} else if (voltageBattery > BATTERY_MAX_VOLTAGE) {
batteryPercentage = 100;
} else {
batteryPercentage = ((voltageBattery - BATTERY_MIN_VOLTAGE) /
(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE)) * 100;
}
}
// ===== DATA TRANSMISSION =====
bool sendData() {
Serial.println("\n[DATA] Preparing to send data...");
// Build JSON payload
String json = buildJsonPayload();
Serial.println("[DATA] Payload size: " + String(json.length()) + " bytes");
// Send to API
if (sendDataToAPI(json)) {
Serial.println("[DATA] Successfully sent to API");
return true;
}
Serial.println("[ERROR] Failed to send data");
return false;
}
String buildJsonPayload() {
StaticJsonDocument<512> doc;
// Required fields for /api/data/iot endpoint
doc["device_id"] = DEVICE_ID;
doc["device_name"] = DEVICE_NAME;
doc["firmware"] = FIRMWARE_VERSION;
// Sensor data
doc["temp"] = round(temperature * 10) / 10.0;
doc["hum"] = round(humidity * 10) / 10.0;
doc["solar_volt"] = round(voltageSolar * 100) / 100.0;
doc["battery_volt"] = round(voltageBattery * 100) / 100.0;
doc["signal"] = signalStrength;
// GPS data
doc["gps_fixed"] = gpsFixed;
doc["latitude"] = round(latitude * 1000000) / 1000000.0;
doc["longitude"] = round(longitude * 1000000) / 1000000.0;
doc["altitude"] = round(altitude * 10) / 10.0;
doc["satellites"] = satellites;
// Timestamp (seconds since boot)
doc["timestamp"] = millis() / 1000;
String json;
serializeJson(doc, json);
return json;
}
bool sendDataToAPI(String json) {
// Use public endpoint (no authentication required)
return sendViaTCP("/api/data/iot", json, "POST");
}
bool sendViaTCP(String endpoint, String payload, String method) {
// Open network
sendATCommand("AT+NETOPEN", 3000);
delay(1000);
// Close any existing connection
sendATCommand("AT+CIPCLOSE=0", 2000);
delay(500);
// Open TCP connection
String tcpCmd = "AT+CIPOPEN=0,\"TCP\",\"" + String(API_BASE_URL) + "\"," + String(API_PORT);
String response = getATResponse(tcpCmd.c_str(), 10000);
if (response.indexOf("+CIPOPEN: 0,0") == -1) {
Serial.println("[TCP] Failed to open connection");
return false;
}
// Build HTTP request
String http = method + " " + endpoint + " HTTP/1.1\r\n";
http += "Host: " + String(API_BASE_URL) + "\r\n";
http += "Content-Type: application/json\r\n";
// Add auth header if token available
if (authToken.length() > 0 && useAuthentication) {
http += "Authorization: Bearer " + authToken + "\r\n";
}
http += "Content-Length: " + String(payload.length()) + "\r\n";
http += "Connection: close\r\n\r\n";
http += payload;
// Send data
String sendCmd = "AT+CIPSEND=0," + String(http.length());
response = getATResponse(sendCmd.c_str(), 2000);
if (response.indexOf(">") != -1) {
SerialAT.print(http);
delay(3000);
// Read response
response = "";
unsigned long start = millis();
while (millis() - start < 5000) {
while (SerialAT.available()) {
char c = SerialAT.read();
response += c;
}
if (response.indexOf("\r\n\r\n") != -1) {
delay(500);
while (SerialAT.available()) {
response += (char)SerialAT.read();
}
break;
}
}
Serial.println("[RESPONSE] " + response.substring(0, 200) + "...");
sendATCommand("AT+CIPCLOSE=0", 2000);
sendATCommand("AT+NETCLOSE", 2000);
// Check for success
return (response.indexOf("200") != -1 ||
response.indexOf("201") != -1 ||
response.indexOf("\"success\":true") != -1 ||
response.indexOf("\"status\":\"success\"") != -1);
}
sendATCommand("AT+CIPCLOSE=0", 2000);
sendATCommand("AT+NETCLOSE", 2000);
return false;
}
void createDeviceIfNeeded() {
if (!useAuthentication) {
Serial.println("[DEVICE] Using public endpoint - device auto-created");
return;
}
Serial.println("[DEVICE] Creating device entry...");
StaticJsonDocument<256> doc;
doc["id"] = DEVICE_ID;
doc["name"] = DEVICE_NAME;
doc["description"] = "Solar monitoring station with LILYGO T-A7670G";
doc["model"] = DEVICE_MODEL;
doc["firmware_version"] = FIRMWARE_VERSION;
String payload;
serializeJson(doc, payload);
if (sendViaTCP("/api/devices", payload, "POST")) {
Serial.println("[DEVICE] Device created successfully");
}
}
void sendLogEntry(String level, String message, String source) {
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["level"] = level;
doc["message"] = message;
doc["source"] = source;
String payload;
serializeJson(doc, payload);
sendViaTCP("/api/logs", payload, "POST");
}
// ===== MODEM & GPS FUNCTIONS =====
bool initModem() {
Serial.println("\n[MODEM] Initializing modem...");
SerialAT.begin(115200, SERIAL_8N1, BOARD_MODEM_RX_PIN, BOARD_MODEM_TX_PIN);
// Check if modem is already on
sendATCommand("AT", 500);
String response = "";
while (SerialAT.available()) {
response += (char)SerialAT.read();
}
if (response.indexOf("OK") == -1) {
Serial.println("[MODEM] Powering on modem...");
digitalWrite(BOARD_MODEM_PWR_PIN, HIGH);
delay(1000);
digitalWrite(BOARD_MODEM_PWR_PIN, LOW);
delay(1000);
digitalWrite(BOARD_MODEM_PWR_PIN, HIGH);
Serial.print("[MODEM] Waiting for startup");
for (int i = 0; i < 15; i++) {
Serial.print(".");
delay(1000);
}
Serial.println();
}
// Configure modem
sendATCommand("ATE0");
sendATCommand("AT+CMEE=2");
// Check SIM
response = getATResponse("AT+CPIN?", 2000);
if (response.indexOf("READY") != -1) {
Serial.println("[MODEM] SIM card ready");
modemReady = true;
return true;
}
return false;
}
bool connectNetwork() {
Serial.println("\n[NETWORK] Connecting to network...");
String cmd = "AT+CGDCONT=1,\"IP\",\"" + String(APN) + "\"";
sendATCommand(cmd.c_str(), 2000);
sendATCommand("AT+CGATT=1", 10000);
sendATCommand("AT+CGACT=1,1", 10000);
String response = getATResponse("AT+CGPADDR=1", 5000);
int ipStart = response.indexOf(",");
if (ipStart != -1) {
String ipAddress = response.substring(ipStart + 1);
ipAddress.trim();
if (ipAddress.length() > 7 && ipAddress != "0.0.0.0") {
Serial.print("[NETWORK] Connected! IP: ");
Serial.println(ipAddress);
networkConnected = true;
return true;
}
}
return false;
}
void initGPS() {
Serial.println("\n[GPS] Initializing GPS...");
sendATCommand("AT+CGPS=1", 2000);
checkGPSStatus();
}
void checkGPSStatus() {
String response = getATResponse("AT+CGPS?", 1000);
if (response.indexOf("+CGPS: 1") != -1) {
Serial.println("[GPS] GPS is active");
} else {
Serial.println("[GPS] GPS is not active, turning on...");
sendATCommand("AT+CGPS=1", 2000);
}
}
void updateGPS() {
String response = getATResponse("AT+CGPSINFO", 1000);
int idx = response.indexOf("+CGPSINFO:");
if (idx == -1) return;
idx += 11;
String data = response.substring(idx);
int commaIdx = 0;
String values[9];
int valueCount = 0;
for (int i = 0; i < data.length() && valueCount < 9; i++) {
if (data[i] == ',' || data[i] == '\r' || data[i] == '\n') {
values[valueCount++] = data.substring(commaIdx, i);
commaIdx = i + 1;
}
}
if (values[0].length() > 0 && values[2].length() > 0) {
latitude = parseCoordinate(values[0]);
if (values[1] == "S") latitude = -latitude;
longitude = parseCoordinate(values[2]);
if (values[3] == "W") longitude = -longitude;
if (values[6].length() > 0) {
altitude = values[6].toFloat();
}
gpsFixed = true;
getGPSSatellites();
} else {
gpsFixed = false;
}
}
float parseCoordinate(String coord) {
if (coord.length() < 4) return 0;
int dotPos = coord.indexOf('.');
if (dotPos < 2) return 0;
String degrees = coord.substring(0, dotPos - 2);
String minutes = coord.substring(dotPos - 2);
return degrees.toFloat() + (minutes.toFloat() / 60.0);
}
void getGPSSatellites() {
if (signalStrength > -70) satellites = 8 + random(4);
else if (signalStrength > -85) satellites = 5 + random(3);
else satellites = 3 + random(2);
}
void getSignalStrength() {
String response = getATResponse("AT+CSQ", 1000);
int idx = response.indexOf("+CSQ: ");
if (idx != -1) {
idx += 6;
int comma = response.indexOf(",", idx);
if (comma != -1) {
int rssi = response.substring(idx, comma).toInt();
if (rssi != 99) {
signalStrength = -113 + (rssi * 2);
}
}
}
}
// ===== UTILITY FUNCTIONS =====
void sendATCommand(const char* cmd, unsigned long timeout) {
Serial.print("[AT] >>> ");
Serial.println(cmd);
while (SerialAT.available()) {
SerialAT.read();
}
SerialAT.println(cmd);
delay(timeout);
}
String getATResponse(const char* cmd, unsigned long timeout) {
Serial.print("[AT] >>> ");
Serial.println(cmd);
while (SerialAT.available()) {
SerialAT.read();
}
SerialAT.println(cmd);
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
while (SerialAT.available()) {
char c = SerialAT.read();
response += c;
}
}
if (response.length() > 0) {
Serial.print("[AT] <<< ");
Serial.println(response.substring(0, 100) + "...");
}
return response;
}
void handleSerialCommands() {
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd == "test") {
Serial.println("\n[TEST] Manual data transmission");
readSensors();
sendData();
} else if (cmd == "gps") {
Serial.println("\n[TEST] GPS status check");
checkGPSStatus();
updateGPS();
} else if (cmd == "auth") {
Serial.println("\n[TEST] Testing authentication");
authenticateAPI();
} else if (cmd == "log") {
Serial.println("\n[TEST] Sending test log");
sendLogEntry("INFO", "Test log entry", "manual_test");
} else if (cmd == "info") {
printSystemInfo();
} else if (cmd == "reset") {
Serial.println("\n[SYSTEM] Restarting...");
ESP.restart();
} else if (cmd.startsWith("AT")) {
sendATCommand(cmd.c_str(), 2000);
} else {
Serial.println("\n[HELP] Available commands:");
Serial.println(" test - Send test data");
Serial.println(" gps - Check GPS status");
Serial.println(" auth - Test authentication");
Serial.println(" log - Send test log");
Serial.println(" info - System information");
Serial.println(" reset - Restart device");
Serial.println(" AT... - Send AT command");
}
}
}
void printSystemInfo() {
Serial.println("\n=== SYSTEM INFORMATION ===");
Serial.printf("Device ID: %s\n", DEVICE_ID);
Serial.printf("Uptime: %lu seconds\n", millis() / 1000);
Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Network: %s\n", networkConnected ? "Connected" : "Disconnected");
Serial.printf("GPS: %s\n", gpsFixed ? "Fixed" : "No Fix");
Serial.printf("Battery: %.2fV (%.1f%%)\n", voltageBattery, batteryPercentage);
Serial.printf("Auth Token: %s\n", authToken.length() > 0 ? "Valid" : "None");
Serial.println("========================\n");
}
/*
* Installation Notes:
*
* 1. Install ArduinoJson library (v6.x)
* 2. Update API credentials (API_USERNAME, API_PASSWORD)
* 3. Set DEVICE_ID to unique value
* 4. Adjust voltage divider ratios for your setup
* 5. Set useAuthentication = true if using protected endpoints
*
* The device will automatically:
* - Send data to public endpoint /api/data/iot (no auth required)
* - Create device entry on first connection
* - Calculate battery percentage from voltage
* - Retry on failures
* - Send logs for important events
*/