631 lines
18 KiB
Plaintext
631 lines
18 KiB
Plaintext
/*
|
|
* LILYGO T-A7670G IoT Client - Optimized Version
|
|
* Features: Temperature, Humidity, Dual Voltage Monitoring, GPS, 4G LTE
|
|
*
|
|
* Hardware: ESP32 + A7670G Module
|
|
* Author: IoT Development Team
|
|
* Version: 1.1.0
|
|
*/
|
|
|
|
#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>
|
|
|
|
// ===== CONSTANTS =====
|
|
const char* SERVER_IP = "172.104.234.159";
|
|
const int SERVER_PORT = 80;
|
|
const char* APN = "internet"; // Sunrise Switzerland
|
|
|
|
// Device identification
|
|
const char* DEVICE_ID = "LILYGO_001";
|
|
const char* DEVICE_NAME = "Zurich_Station_1";
|
|
const char* FIRMWARE_VERSION = "1.1.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 MODEM_TIMEOUT = 30000; // Modem operation timeout
|
|
|
|
// 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
|
|
|
|
// ===== OBJECTS =====
|
|
HardwareSerial SerialAT(1);
|
|
DHT dht(DHT11_PIN, DHT11);
|
|
|
|
// ===== GLOBAL VARIABLES =====
|
|
// Sensor data
|
|
float temperature = 0;
|
|
float humidity = 0;
|
|
float voltageSolar = 0;
|
|
float voltageBattery = 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;
|
|
|
|
// ===== FUNCTION DECLARATIONS =====
|
|
bool initModem();
|
|
bool connectNetwork();
|
|
void initGPS();
|
|
void readSensors();
|
|
void updateGPS();
|
|
bool sendData();
|
|
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();
|
|
bool sendDataViaTCP(String json);
|
|
bool sendDataViaHTTP(String json);
|
|
|
|
// ===== SETUP =====
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
while (!Serial && millis() < 5000); // Wait for serial
|
|
|
|
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 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;
|
|
|
|
readSensors();
|
|
|
|
if (!sendData()) {
|
|
Serial.println("[WARNING] Failed to send data, will retry next interval");
|
|
}
|
|
}
|
|
|
|
// Handle serial commands
|
|
handleSerialCommands();
|
|
}
|
|
|
|
// ===== INITIALIZATION FUNCTIONS =====
|
|
void printHeader() {
|
|
Serial.println("\n\n");
|
|
Serial.println("===========================================");
|
|
Serial.println(" LILYGO T-A7670G IoT Client v1.1");
|
|
Serial.println("===========================================");
|
|
Serial.print("Device ID: ");
|
|
Serial.println(DEVICE_ID);
|
|
Serial.print("Device Name: ");
|
|
Serial.println(DEVICE_NAME);
|
|
Serial.print("Server: ");
|
|
Serial.print(SERVER_IP);
|
|
Serial.print(":");
|
|
Serial.println(SERVER_PORT);
|
|
Serial.println("===========================================\n");
|
|
}
|
|
|
|
void initializeSensors() {
|
|
Serial.println("[INIT] Initializing sensors...");
|
|
|
|
// DHT11
|
|
dht.begin();
|
|
delay(1000); // DHT11 needs time to stabilize
|
|
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);
|
|
}
|
|
|
|
bool initModem() {
|
|
Serial.println("\n[MODEM] Initializing modem...");
|
|
|
|
// Start serial communication
|
|
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) {
|
|
// Power on sequence
|
|
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);
|
|
|
|
// Wait for modem to start
|
|
Serial.print("[MODEM] Waiting for modem startup");
|
|
for (int i = 0; i < 15; i++) {
|
|
Serial.print(".");
|
|
delay(1000);
|
|
}
|
|
Serial.println();
|
|
}
|
|
|
|
// Test communication
|
|
for (int i = 0; i < 10; i++) {
|
|
if (getATResponse("AT", 1000).indexOf("OK") != -1) {
|
|
Serial.println("[MODEM] Communication established");
|
|
break;
|
|
}
|
|
delay(500);
|
|
}
|
|
|
|
// Configure modem
|
|
sendATCommand("ATE0"); // Disable echo
|
|
sendATCommand("AT+CMEE=2"); // Enable detailed error messages
|
|
|
|
// Check SIM card
|
|
response = getATResponse("AT+CPIN?", 2000);
|
|
if (response.indexOf("READY") != -1) {
|
|
Serial.println("[MODEM] SIM card ready");
|
|
|
|
// Get modem info
|
|
Serial.println("[MODEM] Model: " + getATResponse("AT+CGMM", 1000));
|
|
|
|
modemReady = true;
|
|
return true;
|
|
} else {
|
|
Serial.println("[ERROR] SIM card not ready!");
|
|
if (response.indexOf("SIM PIN") != -1) {
|
|
Serial.println("[INFO] SIM requires PIN code");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool connectNetwork() {
|
|
Serial.println("\n[NETWORK] Connecting to network...");
|
|
|
|
// Set APN
|
|
String cmd = "AT+CGDCONT=1,\"IP\",\"" + String(APN) + "\"";
|
|
sendATCommand(cmd.c_str(), 2000);
|
|
|
|
// Attach to network
|
|
Serial.println("[NETWORK] Attaching to network...");
|
|
sendATCommand("AT+CGATT=1", 10000);
|
|
|
|
// Activate PDP context
|
|
Serial.println("[NETWORK] Activating PDP context...");
|
|
sendATCommand("AT+CGACT=1,1", 10000);
|
|
|
|
// Check IP address
|
|
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;
|
|
}
|
|
}
|
|
|
|
Serial.println("[ERROR] Failed to obtain IP address");
|
|
return false;
|
|
}
|
|
|
|
// ===== 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;
|
|
|
|
// Get signal strength
|
|
getSignalStrength();
|
|
|
|
// Print values
|
|
Serial.printf("[SENSOR] Temp: %.1f°C, Humidity: %.1f%%\n", temperature, humidity);
|
|
Serial.printf("[SENSOR] Solar: %.2fV, Battery: %.2fV\n", voltageSolar, voltageBattery);
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== GPS FUNCTIONS =====
|
|
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);
|
|
|
|
// Parse GPS data
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Check if we have valid coordinates
|
|
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() {
|
|
// Simple implementation - count based on signal quality
|
|
if (signalStrength > -70) satellites = 8 + random(4);
|
|
else if (signalStrength > -85) satellites = 5 + random(3);
|
|
else satellites = 3 + random(2);
|
|
}
|
|
|
|
// ===== DATA TRANSMISSION =====
|
|
bool sendData() {
|
|
Serial.println("\n[DATA] Preparing to send data...");
|
|
|
|
// Build JSON payload
|
|
String json = buildJsonPayload();
|
|
Serial.println("[DATA] Payload: " + json);
|
|
|
|
// Try TCP first
|
|
if (sendDataViaTCP(json)) {
|
|
Serial.println("[DATA] Successfully sent via TCP");
|
|
return true;
|
|
}
|
|
|
|
// Fallback to HTTP
|
|
Serial.println("[DATA] TCP failed, trying HTTP...");
|
|
if (sendDataViaHTTP(json)) {
|
|
Serial.println("[DATA] Successfully sent via HTTP");
|
|
return true;
|
|
}
|
|
|
|
Serial.println("[ERROR] Failed to send data via both methods");
|
|
return false;
|
|
}
|
|
|
|
String buildJsonPayload() {
|
|
String json = "{";
|
|
json += "\"device_id\":\"" + String(DEVICE_ID) + "\",";
|
|
json += "\"device_name\":\"" + String(DEVICE_NAME) + "\",";
|
|
json += "\"firmware\":\"" + String(FIRMWARE_VERSION) + "\",";
|
|
json += "\"temp\":" + String(temperature, 1) + ",";
|
|
json += "\"hum\":" + String(humidity, 1) + ",";
|
|
json += "\"solar_volt\":" + String(voltageSolar, 2) + ",";
|
|
json += "\"battery_volt\":" + String(voltageBattery, 2) + ",";
|
|
json += "\"signal\":" + String(signalStrength) + ",";
|
|
json += "\"gps_fixed\":" + String(gpsFixed ? "true" : "false") + ",";
|
|
json += "\"latitude\":" + String(latitude, 6) + ",";
|
|
json += "\"longitude\":" + String(longitude, 6) + ",";
|
|
json += "\"altitude\":" + String(altitude, 1) + ",";
|
|
json += "\"satellites\":" + String(satellites) + ",";
|
|
json += "\"timestamp\":" + String(millis() / 1000);
|
|
json += "}";
|
|
|
|
return json;
|
|
}
|
|
|
|
bool sendDataViaTCP(String json) {
|
|
// 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(SERVER_IP) + "\"," + String(SERVER_PORT);
|
|
String response = getATResponse(tcpCmd.c_str(), 10000);
|
|
|
|
if (response.indexOf("+CIPOPEN: 0,0") == -1) {
|
|
return false;
|
|
}
|
|
|
|
// Build HTTP request
|
|
String http = "POST /data HTTP/1.1\r\n";
|
|
http += "Host: " + String(SERVER_IP) + "\r\n";
|
|
http += "Content-Type: application/json\r\n";
|
|
http += "Content-Length: " + String(json.length()) + "\r\n";
|
|
http += "Connection: close\r\n\r\n";
|
|
http += json;
|
|
|
|
// 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()) {
|
|
response += (char)SerialAT.read();
|
|
}
|
|
if (response.indexOf("200 OK") != -1 || response.indexOf("\"status\":\"ok\"") != -1) {
|
|
sendATCommand("AT+CIPCLOSE=0", 2000);
|
|
sendATCommand("AT+NETCLOSE", 2000);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
sendATCommand("AT+CIPCLOSE=0", 2000);
|
|
sendATCommand("AT+NETCLOSE", 2000);
|
|
return false;
|
|
}
|
|
|
|
bool sendDataViaHTTP(String json) {
|
|
sendATCommand("AT+HTTPTERM", 1000);
|
|
delay(500);
|
|
|
|
if (getATResponse("AT+HTTPINIT", 2000).indexOf("OK") == -1) {
|
|
return false;
|
|
}
|
|
|
|
// Set URL
|
|
String urlCmd = "AT+HTTPPARA=\"URL\",\"http://" + String(SERVER_IP) + "/data\"";
|
|
sendATCommand(urlCmd.c_str(), 2000);
|
|
|
|
// Set content type
|
|
sendATCommand("AT+HTTPPARA=\"CONTENT\",\"application/json\"", 2000);
|
|
|
|
// Send data
|
|
String dataCmd = "AT+HTTPDATA=" + String(json.length()) + ",10000";
|
|
String response = getATResponse(dataCmd.c_str(), 2000);
|
|
|
|
if (response.indexOf("DOWNLOAD") != -1) {
|
|
SerialAT.print(json);
|
|
delay(2000);
|
|
|
|
response = getATResponse("AT+HTTPACTION=1", 10000);
|
|
delay(3000);
|
|
|
|
response = getATResponse("AT+HTTPREAD", 5000);
|
|
sendATCommand("AT+HTTPTERM", 1000);
|
|
|
|
return (response.indexOf("200") != -1 || response.indexOf("ok") != -1);
|
|
}
|
|
|
|
sendATCommand("AT+HTTPTERM", 1000);
|
|
return false;
|
|
}
|
|
|
|
// ===== 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;
|
|
}
|
|
}
|
|
|
|
Serial.print("[AT] <<< ");
|
|
Serial.println(response);
|
|
|
|
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 == "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(" info - System information");
|
|
Serial.println(" reset - Restart device");
|
|
Serial.println(" AT... - Send AT command");
|
|
}
|
|
}
|
|
}
|
|
|
|
void printSystemInfo() {
|
|
Serial.println("\n=== SYSTEM INFORMATION ===");
|
|
Serial.print("Uptime: ");
|
|
Serial.print(millis() / 1000);
|
|
Serial.println(" seconds");
|
|
Serial.print("Free Heap: ");
|
|
Serial.print(ESP.getFreeHeap());
|
|
Serial.println(" bytes");
|
|
Serial.print("Network: ");
|
|
Serial.println(networkConnected ? "Connected" : "Disconnected");
|
|
Serial.print("GPS: ");
|
|
Serial.println(gpsFixed ? "Fixed" : "No Fix");
|
|
Serial.println("========================\n");
|
|
}
|