Files
Arduino/old/STM32-03/STM32-03.ino
2025-12-19 11:50:13 +03:30

519 lines
14 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>
// -------------------- پیکربندی حافظه SPI Flash --------------------
#define FLASH_CS PA4
#define FLASH_MOSI PA7
#define FLASH_MISO PA6
#define FLASH_SCK PA5
// کتابخانه جایگزین برای حافظه SPI
#include <SerialFlash.h>
// -------------------- پیکربندی OLED --------------------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// -------------------- پیکربندی سنسورها --------------------
#define SOIL_PIN PA1
#define MQ7_PIN PA2
#define SDA_PIN PB9
#define SCL_PIN PB8
BH1750 lightMeter;
Adafruit_SHT31 sht31 = Adafruit_SHT31();
// -------------------- پیکربندی EC200U --------------------
#define PWRKEY_PIN PB5
HardwareSerial EC200U(USART3);
// -------------------- پارامترهای قابل تنظیم --------------------
#define SENSOR_READ_INTERVAL 60000 // خواندن سنسورها هر 1 دقیقه
// -------------------- ساختار تنظیمات --------------------
struct DeviceConfig {
char signature[4];
char apn[32];
char serverUrl[64];
char deviceName[16];
char smsNumber[16];
unsigned long webInterval; // دقیقه
unsigned long smsInterval; // دقیقه
unsigned long saveInterval; // ثانیه - فاصله ذخیره در قطعی
bool smsEnabled;
bool valid;
};
// -------------------- ساختار داده سنسورها --------------------
struct SensorData {
float temperature;
float humidity;
int soilMoisture;
float coPPM;
float lightLux;
unsigned long timestamp;
};
// -------------------- متغیرهای سراسری --------------------
DeviceConfig config;
SensorData currentData;
SensorData storedData;
unsigned long lastSensorRead = 0;
unsigned long lastUpload = 0;
unsigned long lastSMS = 0;
unsigned long lastSave = 0;
bool networkConnected = false;
bool dataStored = false;
int displayMode = 0;
unsigned long lastDisplayChange = 0;
// -------------------- توابع حافظه SPI Flash --------------------
void initFlash() {
SPI.setMOSI(FLASH_MOSI);
SPI.setMISO(FLASH_MISO);
SPI.setSCLK(FLASH_SCK);
SPI.begin();
pinMode(FLASH_CS, OUTPUT);
digitalWrite(FLASH_CS, HIGH);
delay(100);
// مقداردهی اولیه SerialFlash
if (!SerialFlash.begin(FLASH_CS)) {
Serial1.println("❌ Flash init failed!");
return;
}
Serial1.println("✅ Flash OK");
// نمایش اطلاعات حافظه
SerialFlash::ID id;
SerialFlash.readID(&id);
Serial1.print("Flash ID: ");
Serial1.print(id.id, HEX);
Serial1.print(" Size: ");
Serial1.print(SerialFlash.capacity(id) / 1024);
Serial1.println("KB");
}
void readConfig() {
// ابتدا از EEPROM داخلی بخوان
EEPROM.get(0, config);
// اگر تنظیمات معتبر نیست، از حافظه SPI بخوان
if (strcmp(config.signature, "CFG") != 0) {
if (SerialFlash.exists("config.bin")) {
SerialFlashFile file = SerialFlash.open("config.bin");
if (file) {
file.read(&config, sizeof(DeviceConfig));
file.close();
Serial1.println("✅ Config read from SPI Flash");
}
}
}
// اگر هنوز تنظیمات معتبر نیست، مقادیر پیش‌فرض
if (strcmp(config.signature, "CFG") != 0) {
strcpy(config.signature, "CFG");
strcpy(config.apn, "mcinet");
strcpy(config.serverUrl, "https://ghback.nabaksoft.ir/api/Telemetry/AddData");
strcpy(config.smsNumber, "+989120000000");
strcpy(config.deviceName, "ST-");
config.webInterval = 1;
config.smsInterval = 5;
config.saveInterval = 60; // پیش‌فرض 60 ثانیه
config.smsEnabled = false;
config.valid = false;
}
}
void saveConfig() {
config.valid = true;
// در EEPROM داخلی ذخیره کن
EEPROM.put(0, config);
EEPROM.commit();
// در حافظه SPI هم ذخیره کن
if (SerialFlash.exists("config.bin")) {
SerialFlash.remove("config.bin");
}
SerialFlashFile file = SerialFlash.create("config.bin", sizeof(DeviceConfig));
if (file) {
file.write((byte*)&config, sizeof(DeviceConfig));
file.close();
Serial1.println("✅ Config saved to SPI Flash");
}
}
void saveDataToFlash() {
if (!dataStored) {
if (SerialFlash.exists("data.bin")) {
SerialFlash.remove("data.bin");
}
dataStored = true;
}
currentData.timestamp = millis();
SerialFlashFile file = SerialFlash.open("data.bin");
if (!file) {
file = SerialFlash.create("data.bin", sizeof(SensorData));
}
if (file) {
file.seek(0);
file.write((byte*)&currentData, sizeof(SensorData));
file.close();
Serial1.println("💾 Data saved to flash");
}
}
void readDataFromFlash() {
if (SerialFlash.exists("data.bin")) {
SerialFlashFile file = SerialFlash.open("data.bin");
if (file) {
file.read(&storedData, sizeof(SensorData));
file.close();
// بررسی اعتبار داده
if (storedData.timestamp > 0) {
Serial1.println("📖 Stored data found");
}
}
}
}
void clearStoredData() {
if (SerialFlash.exists("data.bin")) {
SerialFlash.remove("data.bin");
storedData.timestamp = 0;
dataStored = false;
}
}
// -------------------- توابع EC200U --------------------
bool sendAT(String cmd, uint16_t wait = 2000) {
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;
Serial1.write(c);
}
if (response.indexOf("OK") != -1) return true;
if (response.indexOf("ERROR") != -1) return false;
}
return false;
}
bool waitForResponse(String expected, unsigned long timeout = 10000) {
unsigned long startTime = millis();
String response = "";
while (millis() - startTime < timeout) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
Serial1.write(c);
if (response.indexOf(expected) != -1) return true;
if (response.indexOf("ERROR") != -1) return false;
}
}
return false;
}
bool sendSMS(String number, String message) {
if (!sendAT("AT+CMGF=1", 2000)) return false;
String cmd = "AT+CMGS=\"" + number + "\"";
if (!sendAT(cmd, 2000)) return false;
delay(100);
EC200U.print(message);
EC200U.write(26);
return waitForResponse("+CMGS:", 10000);
}
void initEC200U() {
Serial1.println("🚀 Starting EC200U...");
digitalWrite(PWRKEY_PIN, HIGH);
delay(2000);
digitalWrite(PWRKEY_PIN, LOW);
delay(5000);
if (!sendAT("AT", 3000)) {
Serial1.println("❌ EC200U not responding!");
return;
}
String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + String(config.apn) + "\"";
sendAT(apnCmd, 2000);
if (sendAT("AT+QIACT=1", 15000)) {
networkConnected = true;
Serial1.println("✅ Network connected");
// اگر داده ذخیره شده وجود دارد، ارسال کن
if (storedData.timestamp > 0) {
Serial1.println("📤 Sending stored data...");
sendStoredData();
}
} else {
networkConnected = false;
Serial1.println("❌ Network failed");
}
}
// -------------------- توابع سنسورها --------------------
void readSensors() {
currentData.temperature = sht31.readTemperature();
currentData.humidity = sht31.readHumidity();
currentData.soilMoisture = readSoil();
currentData.coPPM = MQ7_ReadPPM();
currentData.lightLux = lightMeter.readLightLevel();
if (isnan(currentData.temperature)) currentData.temperature = -999;
if (isnan(currentData.humidity)) currentData.humidity = -999;
if (isnan(currentData.lightLux)) currentData.lightLux = 0;
lastSensorRead = millis();
}
int readSoil() {
int adcValue = analogRead(SOIL_PIN);
const int adcDry = 4095;
const int adcWet = 1200;
float soilPercent = (float)(adcDry - adcValue) / (adcDry - adcWet) * 100.0;
if (soilPercent > 100.0) soilPercent = 100.0;
if (soilPercent < 0.0) soilPercent = 0.0;
return (int)soilPercent;
}
float MQ7_ReadPPM() {
long sum = 0;
for (int i = 0; i < 10; i++) {
sum += analogRead(MQ7_PIN);
delay(5);
}
float adcValue = sum / 10.0;
float voltage = (adcValue / 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 < 0) ppm = 0;
if (ppm > 10000) ppm = 10000;
return ppm;
}
// -------------------- توابع ارسال داده --------------------
bool sendData(SensorData data) {
if (data.temperature == -999 || data.humidity == -999) return false;
String dataStr = "temperatureC=" + String(data.temperature, 1) +
"&humidityPercent=" + String(data.humidity, 1) +
"&soilPercent=" + String(data.soilMoisture) +
"&gasPPM=" + String(data.coPPM, 0) +
"&lux=" + String(data.lightLux, 1);
String url = String(config.serverUrl) + "?deviceName=" + String(config.deviceName) + "&" + dataStr;
String httpCmd = "AT+QHTTPURL=" + String(url.length()) + ",80";
if (!sendAT(httpCmd, 5000)) return false;
delay(100);
EC200U.print(url);
if (!waitForResponse("OK", 5000)) return false;
return sendAT("AT+QHTTPGET=60", 15000);
}
void sendStoredData() {
if (sendData(storedData)) {
Serial1.println("✅ Stored data sent successfully");
clearStoredData();
}
}
// -------------------- توابع نمایش OLED --------------------
void updateDisplay() {
if (millis() - lastDisplayChange > 5000) {
displayMode = (displayMode + 1) % 3;
lastDisplayChange = millis();
}
display.clearDisplay();
display.setTextSize(1);
switch(displayMode) {
case 0:
display.setTextSize(2);
display.setCursor(20, 10);
display.println("DEVICE");
display.setCursor(30, 35);
display.println(config.deviceName);
break;
case 1:
display.setCursor(0, 0);
display.print("T:");
display.print(currentData.temperature, 1);
display.print("C ");
display.print("H:");
display.print(currentData.humidity, 1);
display.println("%");
display.setCursor(0, 16);
display.print("S:");
display.print(currentData.soilMoisture);
display.print("% ");
display.print("C:");
display.print(currentData.coPPM, 0);
display.println("P");
display.setCursor(0, 32);
display.print("L:");
display.print(currentData.lightLux, 0);
display.println("Lx");
display.setCursor(0, 48);
display.print(networkConnected ? "ONLINE" : "OFFLINE");
if (dataStored) display.print(" *");
break;
case 2:
display.setTextSize(3);
display.setCursor(10, 25);
display.print(currentData.temperature, 1);
display.setTextSize(2);
display.setCursor(100, 30);
display.print("C");
break;
}
display.display();
}
// -------------------- setup --------------------
void setup() {
Serial1.begin(115200);
delay(1000);
Serial1.println("\n\n🚀 System 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);
// مقداردهی حافظه
initFlash();
readConfig();
readDataFromFlash();
// اگر نام دستگاه تنظیم نشده، از UID استفاده کن
if (strlen(config.deviceName) < 3) {
uint32_t uid0 = *(uint32_t*)0x1FFFF7E8;
uint32_t uid1 = *(uint32_t*)0x1FFFF7EC;
uint32_t uid2 = *(uint32_t*)0x1FFFF7F0;
sprintf(config.deviceName, "ST-%08X", (unsigned int)(uid0 ^ uid1 ^ uid2));
saveConfig();
}
EC200U.begin(115200);
if (config.valid) {
initEC200U();
}
Serial1.println("✅ System Ready");
}
// -------------------- loop --------------------
void loop() {
// خواندن سنسورها در بازه زمانی مشخص
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
readSensors();
Serial1.println("📊 Sensors read");
// اگر شبکه وصل است، داده را ارسال کن
if (networkConnected) {
if (millis() - lastUpload > config.webInterval * 60000) {
if (sendData(currentData)) {
Serial1.println("✅ Web data sent");
lastUpload = millis();
}
}
}
// اگر شبکه قطع است، داده را ذخیره کن
else {
if (config.saveInterval > 0) {
if (millis() - lastSave > config.saveInterval * 1000) {
saveDataToFlash();
lastSave = millis();
}
}
// ارسال SMS در صورت فعال بودن
if (config.smsEnabled && millis() - lastSMS > config.smsInterval * 60000) {
String smsMsg = String(config.deviceName) +
" T:" + String(currentData.temperature, 1) +
" H:" + String(currentData.humidity, 1) +
" S:" + String(currentData.soilMoisture) + "%";
if (sendSMS(config.smsNumber, smsMsg)) {
Serial1.println("✅ SMS sent");
lastSMS = millis();
}
}
}
}
updateDisplay();
// تلاش برای اتصال مجدد شبکه
static unsigned long lastReconnect = 0;
if (!networkConnected && millis() - lastReconnect > 60000) {
initEC200U();
lastReconnect = millis();
}
delay(1000);
}