Files
Arduino/stm32f103.ino
2025-12-23 17:19:03 +03:30

785 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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);
}