785 lines
23 KiB
C++
785 lines
23 KiB
C++
#include <Arduino.h>
|
||
#include <Wire.h>
|
||
#include <Adafruit_GFX.h>
|
||
#include <Adafruit_SSD1306.h>
|
||
#include <Adafruit_SHT31.h>
|
||
#include <BH1750.h>
|
||
#include <SPI.h>
|
||
|
||
// -------------------- پیکربندی --------------------
|
||
#define FLASH_CS PA4
|
||
#define FLASH_MOSI PA7
|
||
#define FLASH_MISO PA6
|
||
#define FLASH_SCK PA5
|
||
#define SCREEN_WIDTH 128
|
||
#define SCREEN_HEIGHT 64
|
||
#define OLED_RESET -1
|
||
#define SOIL_PIN PA1
|
||
#define MQ7_PIN PA2
|
||
#define SDA_PIN PB9
|
||
#define SCL_PIN PB8
|
||
#define PWRKEY_PIN PB5
|
||
#define SENSOR_READ_INTERVAL 60000
|
||
|
||
// -------------------- آدرسهای حافظه --------------------
|
||
#define CONFIG_ADDRESS 0x000000
|
||
#define DATA_ADDRESS 0x010000
|
||
#define MAX_STORED_DATA 100
|
||
|
||
// -------------------- انومها --------------------
|
||
enum SIMType { SIM_UNKNOWN = 0, SIM_HAMRAHE_AVAL = 1, SIM_IRANCELL = 2, SIM_RIGHTEL = 3 };
|
||
|
||
// -------------------- ساختارها --------------------
|
||
struct DeviceConfig {
|
||
char signature[4];
|
||
char deviceId[16];
|
||
char serverPhoneNumber[16];
|
||
char serverUrl[64];
|
||
unsigned long uploadInterval;
|
||
unsigned long smsInterval;
|
||
unsigned long saveInterval;
|
||
SIMType simType;
|
||
bool smsEnabled;
|
||
bool verified;
|
||
bool valid;
|
||
};
|
||
|
||
struct SensorData {
|
||
float temperature;
|
||
float humidity;
|
||
int soilMoisture;
|
||
float coPPM;
|
||
float lightLux;
|
||
unsigned long timestamp;
|
||
uint8_t sent;
|
||
};
|
||
|
||
// -------------------- متغیرهای سراسری --------------------
|
||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||
BH1750 lightMeter;
|
||
Adafruit_SHT31 sht31;
|
||
HardwareSerial EC200U(USART3);
|
||
SPIClass flashSPI(FLASH_MOSI, FLASH_MISO, FLASH_SCK);
|
||
|
||
DeviceConfig config;
|
||
SensorData currentData;
|
||
SensorData storedData[MAX_STORED_DATA];
|
||
|
||
// متغیرهای موقت برای پردازش پیامکها
|
||
String tempPhoneNumber = "";
|
||
String tempTokenCode = "";
|
||
bool awaitingSMS2 = false;
|
||
|
||
unsigned long lastSensorRead = 0;
|
||
unsigned long lastUpload = 0;
|
||
unsigned long lastSMS = 0;
|
||
unsigned long lastSave = 0;
|
||
unsigned long lastDisplayChange = 0;
|
||
unsigned long lastReconnectAttempt = 0;
|
||
|
||
bool networkConnected = false;
|
||
int displayMode = 0;
|
||
int connectionAttempts = 0;
|
||
String lastError = "";
|
||
String currentAPN = "";
|
||
String deviceUID = "";
|
||
String lastMessage = "";
|
||
int storedDataCount = 0;
|
||
|
||
// -------------------- توابع حافظه Flash --------------------
|
||
void flashInit() {
|
||
pinMode(FLASH_CS, OUTPUT);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
flashSPI.begin();
|
||
flashSPI.setClockDivider(SPI_CLOCK_DIV4);
|
||
delay(100);
|
||
}
|
||
|
||
bool flashIsBusy() {
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x05);
|
||
uint8_t status = flashSPI.transfer(0);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
return (status & 0x01);
|
||
}
|
||
|
||
void flashWaitForReady() {
|
||
while (flashIsBusy()) delay(1);
|
||
}
|
||
|
||
void flashRead(uint32_t addr, uint8_t *data, uint32_t len) {
|
||
flashWaitForReady();
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x03);
|
||
flashSPI.transfer((addr >> 16) & 0xFF);
|
||
flashSPI.transfer((addr >> 8) & 0xFF);
|
||
flashSPI.transfer(addr & 0xFF);
|
||
for (uint32_t i = 0; i < len; i++) data[i] = flashSPI.transfer(0);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
}
|
||
|
||
void flashWrite(uint32_t addr, uint8_t *data, uint32_t len) {
|
||
uint32_t offset = 0;
|
||
while (offset < len) {
|
||
// Write Enable
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x06);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
|
||
uint32_t pageOffset = addr % 256;
|
||
uint32_t remaining = 256 - pageOffset;
|
||
uint32_t writeSize = min(remaining, len - offset);
|
||
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x02);
|
||
flashSPI.transfer((addr >> 16) & 0xFF);
|
||
flashSPI.transfer((addr >> 8) & 0xFF);
|
||
flashSPI.transfer(addr & 0xFF);
|
||
|
||
for (uint32_t i = 0; i < writeSize; i++) {
|
||
flashSPI.transfer(data[offset + i]);
|
||
}
|
||
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
flashWaitForReady();
|
||
|
||
addr += writeSize;
|
||
offset += writeSize;
|
||
}
|
||
}
|
||
|
||
void flashSectorErase(uint32_t addr) {
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x06);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
|
||
digitalWrite(FLASH_CS, LOW);
|
||
flashSPI.transfer(0x20);
|
||
flashSPI.transfer((addr >> 16) & 0xFF);
|
||
flashSPI.transfer((addr >> 8) & 0xFF);
|
||
flashSPI.transfer(addr & 0xFF);
|
||
digitalWrite(FLASH_CS, HIGH);
|
||
|
||
flashWaitForReady();
|
||
}
|
||
|
||
// -------------------- توابع مدیریت داده --------------------
|
||
void readConfig() {
|
||
uint8_t buffer[sizeof(DeviceConfig)];
|
||
flashRead(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig));
|
||
memcpy(&config, buffer, sizeof(DeviceConfig));
|
||
|
||
if (strcmp(config.signature, "CFG") != 0) {
|
||
strcpy(config.signature, "CFG");
|
||
strcpy(config.deviceId, "");
|
||
strcpy(config.serverPhoneNumber, "");
|
||
strcpy(config.serverUrl, "https://ghback.nabaksoft.ir");
|
||
config.uploadInterval = 5;
|
||
config.smsInterval = 10;
|
||
config.saveInterval = 60;
|
||
config.simType = SIM_UNKNOWN;
|
||
config.smsEnabled = false;
|
||
config.verified = false;
|
||
config.valid = false;
|
||
|
||
saveConfig();
|
||
}
|
||
}
|
||
|
||
void saveConfig() {
|
||
config.valid = true;
|
||
flashSectorErase(CONFIG_ADDRESS);
|
||
uint8_t buffer[sizeof(DeviceConfig)];
|
||
memcpy(buffer, &config, sizeof(DeviceConfig));
|
||
flashWrite(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig));
|
||
Serial1.println("✅ Config saved to Flash");
|
||
}
|
||
|
||
// -------------------- توابع کمکی --------------------
|
||
String simTypeToString(SIMType type) {
|
||
switch(type) {
|
||
case SIM_HAMRAHE_AVAL: return "HA";
|
||
case SIM_IRANCELL: return "IR";
|
||
case SIM_RIGHTEL: return "RT";
|
||
default: return "UK";
|
||
}
|
||
}
|
||
|
||
String simTypeToAPN(SIMType type) {
|
||
switch(type) {
|
||
case SIM_HAMRAHE_AVAL: return "mcinet";
|
||
case SIM_IRANCELL: return "mtnirancell";
|
||
case SIM_RIGHTEL: return "rightel";
|
||
default: return "mcinet";
|
||
}
|
||
}
|
||
|
||
String generateVerificationCode(String tokenCode) {
|
||
long token = tokenCode.toInt();
|
||
long verification = (token * 7 + 12345) % 100000;
|
||
char buffer[6];
|
||
sprintf(buffer, "%05ld", verification);
|
||
|
||
Serial1.println("=== Code Calculation ===");
|
||
Serial1.println("Token: " + tokenCode);
|
||
Serial1.println("Result: " + String(verification));
|
||
Serial1.println("Formatted: " + String(buffer));
|
||
Serial1.println("========================");
|
||
|
||
return String(buffer);
|
||
}
|
||
|
||
// -------------------- پردازش SMS --------------------
|
||
// استخراج همه اعداد از متن پیامک (برای SMS1 و SMS2)
|
||
void extractNumbersFromSMS2(String message, String numbers[], int &count) {
|
||
count = 0;
|
||
String current = "";
|
||
|
||
for (int i = 0; i < message.length(); i++) {
|
||
char c = message.charAt(i);
|
||
if (c >= '0' && c <= '9') {
|
||
current += c;
|
||
} else {
|
||
if (current.length() > 0) {
|
||
if (count < 10) {
|
||
numbers[count] = current;
|
||
count++;
|
||
}
|
||
current = "";
|
||
}
|
||
}
|
||
}
|
||
|
||
if (current.length() > 0 && count < 10) {
|
||
numbers[count] = current;
|
||
count++;
|
||
}
|
||
}
|
||
|
||
// تشخیص نوع پیامک (SMS1 یا SMS2) بر اساس تعداد اعداد
|
||
int detectSMSType(String message) {
|
||
int count = 0;
|
||
String current = "";
|
||
|
||
for (int i = 0; i < message.length(); i++) {
|
||
char c = message.charAt(i);
|
||
if (c >= '0' && c <= '9') {
|
||
current += c;
|
||
} else {
|
||
if (current.length() > 0) {
|
||
count++;
|
||
current = "";
|
||
}
|
||
}
|
||
}
|
||
if (current.length() > 0) {
|
||
count++;
|
||
}
|
||
|
||
// اگر حداقل ۵ تا عدد داشته باشیم، SMS2 فرض میکنیم
|
||
return (count >= 5) ? 2 : 1;
|
||
}
|
||
|
||
// -------------------- توابع EC200U --------------------
|
||
bool sendAT(String cmd, uint16_t wait = 2000, bool showResponse = true) {
|
||
Serial1.print(">> ");
|
||
Serial1.println(cmd);
|
||
EC200U.println(cmd);
|
||
|
||
String response = "";
|
||
unsigned long start = millis();
|
||
|
||
while (millis() - start < wait) {
|
||
while (EC200U.available()) {
|
||
char c = EC200U.read();
|
||
response += c;
|
||
if (showResponse) Serial1.write(c);
|
||
}
|
||
if (response.indexOf("OK") != -1) return true;
|
||
if (response.indexOf("ERROR") != -1) return false;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool sendSMS(String number, String message) {
|
||
if (!sendAT("AT+CMGF=1", 3000, true)) {
|
||
Serial1.println("❌ Failed to set SMS mode");
|
||
return false;
|
||
}
|
||
|
||
delay(200);
|
||
|
||
EC200U.print("AT+CMGS=\"");
|
||
EC200U.print(number);
|
||
EC200U.println("\"");
|
||
|
||
String response = "";
|
||
unsigned long start = millis();
|
||
bool gotPrompt = false;
|
||
|
||
while (millis() - start < 5000) {
|
||
while (EC200U.available()) {
|
||
char c = EC200U.read();
|
||
response += c;
|
||
Serial1.write(c);
|
||
if (response.indexOf(">") != -1) {
|
||
gotPrompt = true;
|
||
break;
|
||
}
|
||
}
|
||
if (gotPrompt) break;
|
||
}
|
||
|
||
if (!gotPrompt) {
|
||
Serial1.println("❌ No prompt received");
|
||
return false;
|
||
}
|
||
|
||
EC200U.print(message);
|
||
EC200U.write(26);
|
||
|
||
response = "";
|
||
start = millis();
|
||
bool gotResponse = false;
|
||
|
||
while (millis() - start < 15000) {
|
||
while (EC200U.available()) {
|
||
char c = EC200U.read();
|
||
response += c;
|
||
Serial1.write(c);
|
||
if (response.indexOf("+CMGS:") != -1) {
|
||
gotResponse = true;
|
||
break;
|
||
}
|
||
if (response.indexOf("ERROR") != -1) return false;
|
||
}
|
||
if (gotResponse) break;
|
||
}
|
||
|
||
if (!gotResponse) return false;
|
||
|
||
delay(1000);
|
||
sendAT("AT+CMGD=1,4", 3000, true);
|
||
return true;
|
||
}
|
||
|
||
// -------------------- پردازش پیامکها --------------------
|
||
void processSMS1(String message) {
|
||
Serial1.println("🔐 PROCESSING SMS1 (Verification)");
|
||
|
||
// فقط اعداد را بهترتیب از متن استخراج میکنیم
|
||
String numbers[10];
|
||
int count = 0;
|
||
extractNumbersFromSMS2(message, numbers, count);
|
||
|
||
String phone = "";
|
||
String token = "";
|
||
|
||
if (count >= 2) {
|
||
phone = numbers[0]; // شماره تلفن (عدد اول)
|
||
token = numbers[1]; // کد ورود (عدد دوم)
|
||
}
|
||
|
||
Serial1.println("Phone: " + phone);
|
||
Serial1.println("Token: " + token);
|
||
|
||
if (phone.length() >= 10 && token.length() == 5) {
|
||
tempPhoneNumber = phone;
|
||
tempTokenCode = token;
|
||
|
||
String verifyCode = generateVerificationCode(token);
|
||
String reply = "Code: " + verifyCode;
|
||
|
||
if (sendSMS(phone, reply)) {
|
||
Serial1.println("✅ Verification code sent");
|
||
awaitingSMS2 = true;
|
||
lastMessage = "Code Sent";
|
||
|
||
Serial1.println("⏳ Now awaiting SMS2...");
|
||
Serial1.println("tempPhone: " + tempPhoneNumber);
|
||
Serial1.println("tempToken: " + tempTokenCode);
|
||
}
|
||
}
|
||
}
|
||
|
||
void processSMS2(String message) {
|
||
Serial1.println("⚙️ PROCESSING SMS2 (Configuration)");
|
||
|
||
String numbers[10];
|
||
int count = 0;
|
||
extractNumbersFromSMS2(message, numbers, count);
|
||
|
||
Serial1.print("Numbers: ");
|
||
for (int i = 0; i < count; i++) {
|
||
Serial1.print(numbers[i] + " ");
|
||
}
|
||
Serial1.println("(Count: " + String(count) + ")");
|
||
|
||
// پیامک دوم معمولاً ۵ یا ۶ عدد دارد
|
||
// ترتیب: [0]=نابک, [1]=اینتروال, [2]=پیامک, [3]=سیستم(بیاستفاده), [4]=سیم, [5]=لغو(بیاستفاده)
|
||
if (count >= 5) {
|
||
// 1. Device ID (نابک) - عدد اول
|
||
String deviceId = numbers[0];
|
||
if (deviceId.length() > 2) {
|
||
deviceId = deviceId.substring(0, deviceId.length() - 2);
|
||
}
|
||
deviceId.toCharArray(config.deviceId, 16);
|
||
Serial1.println("✓ Device ID: " + String(config.deviceId));
|
||
|
||
// 2. Upload Interval (اینتروال) - عدد دوم
|
||
if (numbers[1].length() > 1) {
|
||
config.uploadInterval = numbers[1].substring(1).toInt();
|
||
} else {
|
||
config.uploadInterval = numbers[1].toInt();
|
||
}
|
||
Serial1.println("✓ Upload Interval: " + String(config.uploadInterval));
|
||
|
||
// 3. SMS Settings (پیامک) - عدد سوم
|
||
if (numbers[2].length() >= 2) {
|
||
config.smsEnabled = (numbers[2].charAt(0) == '1');
|
||
config.smsInterval = numbers[2].substring(1).toInt();
|
||
} else {
|
||
config.smsEnabled = false;
|
||
config.smsInterval = 0;
|
||
}
|
||
Serial1.println("✓ SMS: " + String(config.smsEnabled ? "ON" : "OFF") + ", Interval: " + String(config.smsInterval));
|
||
|
||
// 4. عدد چهارم (سیستم) - بیاستفاده است، رد میشود
|
||
Serial1.println("✓ System ID (ignored): " + numbers[3]);
|
||
|
||
// 5. SIM Type (سیم) - عدد پنجم (یا اگر ۶ تا بود، پنجم)
|
||
int simIndex = (count >= 6) ? 4 : (count - 1); // اگر ۶ تا بود index=4، وگرنه آخری
|
||
String simValue = numbers[simIndex];
|
||
|
||
// دو رقم آخر را بگیر
|
||
if (simValue.length() >= 2) {
|
||
String simCode = simValue.substring(simValue.length() - 2);
|
||
Serial1.println("✓ SIM Code: " + simCode);
|
||
|
||
if (simCode == "21") config.simType = SIM_HAMRAHE_AVAL;
|
||
else if (simCode == "22") config.simType = SIM_IRANCELL;
|
||
else if (simCode == "23") config.simType = SIM_RIGHTEL;
|
||
else if (simCode == "71") config.simType = SIM_HAMRAHE_AVAL;
|
||
else config.simType = SIM_UNKNOWN;
|
||
} else {
|
||
// اگر کمتر از ۲ رقم بود، خود عدد را چک کن
|
||
int simInt = simValue.toInt();
|
||
if (simInt == 21 || simInt == 71) config.simType = SIM_HAMRAHE_AVAL;
|
||
else if (simInt == 22) config.simType = SIM_IRANCELL;
|
||
else if (simInt == 23) config.simType = SIM_RIGHTEL;
|
||
else config.simType = SIM_UNKNOWN;
|
||
}
|
||
|
||
// 6. شماره تلفن (از SMS1 یا اگر نبود، خالی میماند)
|
||
if (tempPhoneNumber.length() > 0) {
|
||
tempPhoneNumber.toCharArray(config.serverPhoneNumber, 16);
|
||
Serial1.println("✓ Server Phone: " + tempPhoneNumber);
|
||
} else {
|
||
strcpy(config.serverPhoneNumber, "");
|
||
Serial1.println("⚠️ No phone number (SMS1 was skipped)");
|
||
}
|
||
|
||
// 7. تایید نهایی
|
||
config.verified = true;
|
||
saveConfig();
|
||
|
||
currentAPN = simTypeToAPN(config.simType);
|
||
awaitingSMS2 = false;
|
||
|
||
Serial1.println("\n✅✅✅ DEVICE FULLY CONFIGURED! ✅✅✅");
|
||
Serial1.println("Device ID: " + String(config.deviceId));
|
||
Serial1.println("Phone: " + String(config.serverPhoneNumber));
|
||
Serial1.println("Upload Interval: " + String(config.uploadInterval) + " minutes");
|
||
Serial1.println("SMS Enabled: " + String(config.smsEnabled ? "YES" : "NO"));
|
||
Serial1.println("SMS Interval: " + String(config.smsInterval) + " minutes");
|
||
Serial1.println("SIM Type: " + simTypeToString(config.simType));
|
||
Serial1.println("APN: " + currentAPN);
|
||
Serial1.println("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅\n");
|
||
|
||
lastMessage = "Configured";
|
||
|
||
// اتصال به شبکه
|
||
initEC200U();
|
||
} else {
|
||
Serial1.println("❌ Not enough numbers in SMS2 (need at least 5)");
|
||
}
|
||
}
|
||
|
||
void checkSMS() {
|
||
if (!sendAT("AT", 2000, false)) return;
|
||
|
||
sendAT("AT+CMGF=1", 2000, true);
|
||
delay(100);
|
||
|
||
EC200U.println("AT+CMGL=\"REC UNREAD\"");
|
||
|
||
String response = "";
|
||
unsigned long start = millis();
|
||
while (millis() - start < 5000) {
|
||
while (EC200U.available()) {
|
||
char c = EC200U.read();
|
||
response += c;
|
||
Serial1.write(c);
|
||
}
|
||
if (response.indexOf("OK") != -1) break;
|
||
}
|
||
|
||
if (response.indexOf("+CMGL:") != -1) {
|
||
int msgIndex = response.indexOf("+CMGL:");
|
||
int comma1 = response.indexOf(',', msgIndex);
|
||
int comma2 = response.indexOf(',', comma1 + 1);
|
||
int quote1 = response.indexOf('\"', comma2 + 1);
|
||
int quote2 = response.indexOf('\"', quote1 + 1);
|
||
|
||
if (quote1 != -1 && quote2 != -1) {
|
||
int msgStart = response.indexOf('\n', quote2) + 1;
|
||
int msgEnd = response.indexOf('\n', msgStart);
|
||
if (msgEnd == -1) msgEnd = response.length();
|
||
|
||
String message = response.substring(msgStart, msgEnd);
|
||
message.trim();
|
||
|
||
Serial1.println("\n📱 NEW SMS: " + message);
|
||
Serial1.println("Verified: " + String(config.verified));
|
||
Serial1.println("Awaiting SMS2: " + String(awaitingSMS2));
|
||
|
||
// استخراج اعداد برای دیباگ
|
||
String debugNumbers[10];
|
||
int debugCount = 0;
|
||
extractNumbersFromSMS2(message, debugNumbers, debugCount);
|
||
Serial1.print("🔢 Numbers found: ");
|
||
for (int i = 0; i < debugCount; i++) {
|
||
Serial1.print(debugNumbers[i]);
|
||
if (i < debugCount - 1) Serial1.print(", ");
|
||
}
|
||
Serial1.println(" (Total: " + String(debugCount) + ")");
|
||
|
||
int smsType = detectSMSType(message);
|
||
Serial1.println("📋 SMS Type detected: " + String(smsType));
|
||
|
||
if (config.verified) {
|
||
Serial1.println("⚠️ Already verified, ignoring");
|
||
} else if (smsType == 1 && !awaitingSMS2) {
|
||
processSMS1(message);
|
||
} else if (smsType == 2 && awaitingSMS2) {
|
||
// حالت استاندارد: پیامک اول آمده و الان پیامک دوم
|
||
processSMS2(message);
|
||
} else if (smsType == 2 && !awaitingSMS2 && !config.verified) {
|
||
// حالت مستقیم: پیامک دوم بدون پیامک اول (برای تست یا تنظیم سریع)
|
||
Serial1.println("⚡ Direct SMS2 (without SMS1) - Processing anyway");
|
||
processSMS2(message);
|
||
} else {
|
||
Serial1.println("❌ Wrong SMS type or state");
|
||
Serial1.println(" Expected: SMS type=" + String(awaitingSMS2 ? "2" : "1") + ", Got: " + String(smsType));
|
||
}
|
||
|
||
String indexStr = response.substring(msgIndex + 6, comma1);
|
||
indexStr.trim();
|
||
sendAT("AT+CMGD=" + indexStr, 2000, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
// -------------------- اتصال شبکه --------------------
|
||
void initEC200U() {
|
||
if (!config.verified) return;
|
||
|
||
Serial1.println("📡 Connecting to network...");
|
||
lastMessage = "Connecting";
|
||
|
||
digitalWrite(PWRKEY_PIN, HIGH);
|
||
delay(2000);
|
||
digitalWrite(PWRKEY_PIN, LOW);
|
||
delay(5000);
|
||
|
||
if (!sendAT("AT", 3000)) {
|
||
Serial1.println("❌ Modem error");
|
||
return;
|
||
}
|
||
|
||
currentAPN = simTypeToAPN(config.simType);
|
||
String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + currentAPN + "\"";
|
||
sendAT(apnCmd, 2000);
|
||
|
||
if (sendAT("AT+QIACT=1", 15000)) {
|
||
networkConnected = true;
|
||
lastMessage = "Connected";
|
||
Serial1.println("✅ Network connected");
|
||
} else {
|
||
networkConnected = false;
|
||
Serial1.println("❌ Network failed");
|
||
}
|
||
}
|
||
|
||
// -------------------- سنسورها --------------------
|
||
void readSensors() {
|
||
currentData.temperature = sht31.readTemperature();
|
||
currentData.humidity = sht31.readHumidity();
|
||
|
||
// خاک
|
||
int adc = analogRead(SOIL_PIN);
|
||
float soil = (4095 - adc) / (4095.0 - 1200.0) * 100.0;
|
||
if (soil > 100) soil = 100;
|
||
if (soil < 0) soil = 0;
|
||
currentData.soilMoisture = (int)soil;
|
||
|
||
// CO
|
||
long sum = 0;
|
||
for (int i = 0; i < 10; i++) {
|
||
sum += analogRead(MQ7_PIN);
|
||
delay(5);
|
||
}
|
||
float voltage = (sum / 10.0 / 4095.0) * 3.3;
|
||
float ppm;
|
||
if (voltage < 0.1) ppm = 0;
|
||
else if (voltage < 0.2) ppm = 10 * (voltage / 0.2);
|
||
else if (voltage < 0.5) ppm = 50 + (voltage - 0.2) * 300;
|
||
else if (voltage < 1.0) ppm = 140 + (voltage - 0.5) * 800;
|
||
else if (voltage < 2.0) ppm = 500 + (voltage - 1.0) * 1500;
|
||
else ppm = 2000 + (voltage - 2.0) * 2000;
|
||
if (ppm > 10000) ppm = 10000;
|
||
currentData.coPPM = ppm;
|
||
|
||
// نور
|
||
currentData.lightLux = lightMeter.readLightLevel();
|
||
if (isnan(currentData.lightLux)) currentData.lightLux = 0;
|
||
|
||
lastSensorRead = millis();
|
||
}
|
||
|
||
// -------------------- نمایشگر --------------------
|
||
void updateDisplay() {
|
||
if (millis() - lastDisplayChange > 5000) {
|
||
displayMode = (displayMode + 1) % 3;
|
||
lastDisplayChange = millis();
|
||
}
|
||
|
||
display.clearDisplay();
|
||
display.setTextSize(1);
|
||
|
||
switch(displayMode) {
|
||
case 0:
|
||
display.setCursor(0, 0);
|
||
if (config.verified) {
|
||
display.println("ID: " + String(config.deviceId));
|
||
display.println("SIM: " + simTypeToString(config.simType));
|
||
display.println("NET: " + String(networkConnected ? "OK" : "NO"));
|
||
} else if (awaitingSMS2) {
|
||
display.println("AWAITING SMS2");
|
||
display.println("Phone: " + tempPhoneNumber);
|
||
} else {
|
||
display.println("WAIT SMS1");
|
||
display.println("Send: NUM#CODE");
|
||
}
|
||
break;
|
||
|
||
case 1:
|
||
display.setCursor(0, 0);
|
||
display.print("T:");
|
||
display.print(currentData.temperature, 1);
|
||
display.print("C H:");
|
||
display.print(currentData.humidity, 1);
|
||
display.println("%");
|
||
display.print("S:");
|
||
display.print(currentData.soilMoisture);
|
||
display.print("% C:");
|
||
display.print(currentData.coPPM, 0);
|
||
display.println("PPM");
|
||
display.print("L:");
|
||
display.print(currentData.lightLux, 0);
|
||
display.println("Lx");
|
||
break;
|
||
|
||
case 2:
|
||
display.setTextSize(2);
|
||
display.setCursor(0, 20);
|
||
display.print(currentData.temperature, 1);
|
||
display.println("C");
|
||
break;
|
||
}
|
||
|
||
display.display();
|
||
}
|
||
|
||
// -------------------- Setup & Loop --------------------
|
||
void setup() {
|
||
Serial1.begin(115200);
|
||
delay(1000);
|
||
|
||
Serial1.println("\n\n🚀 IoT Device Starting...");
|
||
|
||
pinMode(PWRKEY_PIN, OUTPUT);
|
||
pinMode(SOIL_PIN, INPUT);
|
||
pinMode(MQ7_PIN, INPUT);
|
||
digitalWrite(PWRKEY_PIN, LOW);
|
||
|
||
Wire.setSDA(SDA_PIN);
|
||
Wire.setSCL(SCL_PIN);
|
||
Wire.begin();
|
||
|
||
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
|
||
Serial1.println("❌ OLED failed!");
|
||
}
|
||
|
||
sht31.begin(0x44);
|
||
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
||
|
||
flashInit();
|
||
readConfig();
|
||
|
||
// همیشه در شروع awaitingSMS2 = false
|
||
awaitingSMS2 = false;
|
||
tempPhoneNumber = "";
|
||
tempTokenCode = "";
|
||
|
||
EC200U.begin(115200);
|
||
|
||
Serial1.println("\n=== INITIAL STATUS ===");
|
||
Serial1.println("Verified: " + String(config.verified));
|
||
Serial1.println("Awaiting SMS2: " + String(awaitingSMS2));
|
||
Serial1.println("=====================");
|
||
|
||
if (config.verified) {
|
||
currentAPN = simTypeToAPN(config.simType);
|
||
initEC200U();
|
||
}
|
||
|
||
Serial1.println("✅ Ready");
|
||
}
|
||
|
||
void loop() {
|
||
checkSMS();
|
||
|
||
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
|
||
readSensors();
|
||
|
||
if (config.verified && networkConnected) {
|
||
if (millis() - lastUpload > config.uploadInterval * 60000) {
|
||
// ارسال داده به API
|
||
String data = "deviceId=" + String(config.deviceId) +
|
||
"&temp=" + String(currentData.temperature, 1) +
|
||
"&hum=" + String(currentData.humidity, 1) +
|
||
"&soil=" + String(currentData.soilMoisture) +
|
||
"&co=" + String(currentData.coPPM, 0) +
|
||
"&lux=" + String(currentData.lightLux, 1);
|
||
|
||
String url = String(config.serverUrl) + "/api/Telemetry/AddData?" + data;
|
||
|
||
String cmd = "AT+QHTTPURL=" + String(url.length()) + ",80";
|
||
if (sendAT(cmd, 5000)) {
|
||
delay(100);
|
||
EC200U.print(url);
|
||
if (sendAT("", 5000)) {
|
||
if (sendAT("AT+QHTTPGET=60", 10000)) {
|
||
lastUpload = millis();
|
||
lastMessage = "Data Sent";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
updateDisplay();
|
||
delay(1000);
|
||
} |