Основные изменения:
Добавлено хранилище для имени файла:
cpp
RTC_DATA_ATTR char fileName[32] = "data"; // Имя файла по умолчанию
Изменена работа с файлами:
Все файловые операции теперь используют шаблон: /[fileName].csv
Автоматическое создание файла при первом запуске
Поддержка уникальных имен для разных устройств
Добавлен веб-интерфейс для управления именем:
Новая форма в веб-интерфейсе:
html
Обработчик /setname для сохранения нового имени
Отображение текущего имени в веб-интерфейсе
Скачивание данных:
Файл скачивается с текущим именем устройства
Автоматическое имя файла при скачивании: [fileName].csv
Особенности реализации:
Имя сохраняется в энергонезависимой памяти (RTC_DATA_ATTR)
Ограничение длины имени: 31 символ
Автоматическое создание нового файла при изменении имени
Обратная совместимость (по умолчанию используется "data.csv")
При скачивании файл сохраняется с текущим именем устройства
Работа системы полностью сохраняет предыдущую функциональность, добавляя гибкую настройку имени устройства/файла через веб-интерфейс.
#include
#include
#include
#include
#include
#include
#include
// Конфигурация пинов
#define ONE_WIRE_BUS 5 // Пин для датчиков температуры
#define BUTTON_PIN 32 // Пин кнопки для пробуждения
#define POWER_CTRL_PIN 33 // Пин управления питанием датчиков (опционально)
// Настройки Wi-Fi
const char* AP_SSID = "TempLogger_1";
const char* AP_PASSWORD = "securepass123";
// Интервалы сна (в микросекундах)
const uint64_t SLEEP_INTERVALS[] = {
2 * 60 * 1000000ULL, // 2 минуты
15 * 60 * 1000000ULL, // 15 минут
60 * 60 * 1000000ULL, // 1 час
24 * 60 * 60 * 1000000ULL // 1 день
};
RTC_DATA_ATTR uint8_t currentIntervalIdx = 1; // По умолчанию 15 минут
// Глобальные объекты
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
WebServer server(80);
// Переменные, сохраняемые между циклами сна
RTC_DATA_ATTR time_t baseEpoch = 0; // Базовое время (Unix epoch)
RTC_DATA_ATTR uint64_t totalSleepMicros = 0; // Общее время в сне
RTC_DATA_ATTR uint32_t bootCount = 0; // Счетчик запусков
RTC_DATA_ATTR bool firstBoot = true; // Флаг первого запуска
RTC_DATA_ATTR char fileName[32] = "data"; // Имя файла для данных
// Структура для хранения данных датчика
struct SensorData {
DeviceAddress address;
float temperature;
bool valid = false;
};
void setup() {
Serial.begin(115200);
delay(1000); // Даем время для инициализации Serial
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(POWER_CTRL_PIN, OUTPUT);
digitalWrite(POWER_CTRL_PIN, HIGH); // Включаем питание датчиков
// Инициализация SPIFFS с проверкой
if (!initFileSystem()) {
Serial.println("Критическая ошибка: Не удалось инициализировать файловую систему!");
goToDeepSleep(SLEEP_INTERVALS[currentIntervalIdx]);
return;
}
// Определяем причину пробуждения
esp_sleep_wakeup_cause_t wakeCause = esp_sleep_get_wakeup_cause();
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("Пробуждение по таймеру");
totalSleepMicros += SLEEP_INTERVALS[currentIntervalIdx];
if (!collectAndSaveData()) {
Serial.println("Ошибка сбора данных!");
}
goToDeepSleep(SLEEP_INTERVALS[currentIntervalIdx]);
}
else if (wakeCause == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("Пробуждение по кнопке");
startWebInterface();
}
else {
Serial.println("Первичный запуск системы");
bootCount++;
firstBoot = true;
startWebInterface();
}
}
void loop() {
// Обработка веб-интерфейса, если активен
if (WiFi.softAPgetStationNum() > 0) {
server.handleClient();
// Если нет подключенных клиентов более 5 минут - спать
static unsigned long lastClientTime = millis();
if (millis() - lastClientTime > 5 * 60 * 1000) {
goToDeepSleep(SLEEP_INTERVALS[currentIntervalIdx]);
}
}
}
// ================== ФУНКЦИИ ДЛЯ РАБОТЫ С ФАЙЛАМИ ==================
bool initFileSystem() {
Serial.println("Инициализация SPIFFS...");
if (!SPIFFS.begin(true)) {
Serial.println("Ошибка монтирования SPIFFS, пробуем форматировать...");
if (!SPIFFS.format()) {
Serial.println("Ошибка форматирования SPIFFS!");
return false;
}
if (!SPIFFS.begin(true)) {
Serial.println("Повторная ошибка монтирования SPIFFS!");
return false;
}
}
// Создаем файл данных с заголовком, если не существует
String fullFileName = String("/") + fileName + ".csv";
if (!SPIFFS.exists(fullFileName)) {
Serial.println("Создание нового файла данных...");
File file = SPIFFS.open(fullFileName, FILE_WRITE);
if (!file) {
Serial.println("Ошибка создания файла данных!");
return false;
}
if (!file.println("timestamp,free_ram,sensor_count,sensor_address,temperature")) {
Serial.println("Ошибка записи заголовка!");
file.close();
return false;
}
file.close();
}
return true;
}
bool saveDataToFile(const String& data) {
String fullFileName = String("/") + fileName + ".csv";
File file = SPIFFS.open(fullFileName, FILE_APPEND);
if (!file) {
Serial.println("Ошибка открытия файла для записи!");
return false;
}
if (!file.println(data)) {
Serial.println("Ошибка записи данных!");
file.close();
return false;
}
file.close();
return true;
}
// ================== ФУНКЦИИ ДЛЯ РАБОТЫ С ДАТЧИКАМИ ==================
bool initSensors() {
Serial.println("Инициализация датчиков температуры...");
sensors.begin();
int deviceCount = sensors.getDeviceCount();
if (deviceCount == 0) {
Serial.println("Датчики не обнаружены!");
return false;
}
Serial.print("Найдено датчиков: ");
Serial.println(deviceCount);
return true;
}
bool readSensors(SensorData* data, int maxSensors) {
if (!initSensors()) return false;
// Запрашиваем температуру со всех датчиков
sensors.requestTemperatures();
DeviceAddress addr;
int validCount = 0;
for (int i = 0; i < maxSensors && i < sensors.getDeviceCount(); i++) {
if (sensors.getAddress(addr, i)) {
float tempC = sensors.getTempC(addr);
if (tempC != DEVICE_DISCONNECTED_C) {
memcpy(data[validCount].address, addr, 8);
data[validCount].temperature = tempC;
data[validCount].valid = true;
validCount++;
}
}
}
return validCount > 0;
}
// ================== ОСНОВНАЯ ЛОГИКА ==================
bool collectAndSaveData() {
Serial.println("=== Начало сбора данных ===");
// Максимальное количество датчиков
const int MAX_SENSORS = 20;
SensorData sensorData[MAX_SENSORS];
// Читаем данные с датчиков
if (!readSensors(sensorData, MAX_SENSORS)) {
Serial.println("Нет данных для сохранения!");
return false;
}
// Формируем строку данных
String dataString = getCurrentDateTime() + ",";
dataString += String(esp_get_free_heap_size()) + ",";
// Считаем валидные датчики
int validCount = 0;
for (int i = 0; i < MAX_SENSORS; i++) {
if (sensorData[i].valid) validCount++;
}
dataString += String(validCount);
// Добавляем данные каждого датчика
for (int i = 0; i < MAX_SENSORS; i++) {
if (sensorData[i].valid) {
dataString += "," + addressToString(sensorData[i].address);
dataString += "," + String(sensorData[i].temperature, 2);
}
}
Serial.println("Данные для сохранения:");
Serial.println(dataString);
// Сохраняем в файл
if (!saveDataToFile(dataString)) {
return false;
}
Serial.println("Данные успешно сохранены!");
return true;
}
// ================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==================
String addressToString(const DeviceAddress addr) {
String str;
for (uint8_t i = 0; i < 8; i++) {
if (addr[i] < 16) str += "0";
str += String(addr[i], HEX);
}
return str;
}
String getCurrentDateTime() {
time_t current = baseEpoch + (totalSleepMicros / 1000000);
struct tm *timeinfo = gmtime(¤t);
char buffer[30];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);
return String(buffer);
}
time_t parseDateTime(String datetime) {
if (datetime.length() < 16) return 0;
int year = datetime.substring(0,4).toInt();
int month = datetime.substring(5,7).toInt();
int day = datetime.substring(8,10).toInt();
int hour = datetime.substring(11,13).toInt();
int minute = datetime.substring(14,16).toInt();
tmElements_t tm;
tm.Year = year - 1970;
tm.Month = month;
tm.Day = day;
tm.Hour = hour;
tm.Minute = minute;
tm.Second = 0;
return makeTime(tm);
}
// ================== ВЕБ-ИНТЕРФЕЙС ==================
void startWebInterface() {
Serial.println("Запуск веб-интерфейса...");
WiFi.softAP(AP_SSID, AP_PASSWORD);
IPAddress ip = WiFi.softAPIP();
Serial.print("AP запущен. IP: ");
Serial.println(ip);
// Настройка обработчиков веб-сервера
server.on("/", HTTP_GET, handleRoot);
server.on("/settime", HTTP_POST, handleSetTime);
server.on("/setinterval", HTTP_POST, handleSetInterval);
server.on("/setname", HTTP_POST, handleSetName); // Новый обработчик
server.on("/download", HTTP_GET, handleDownload);
server.on("/sleep", HTTP_GET, handleSleep);
server.begin();
Serial.println("HTTP сервер запущен");
}
void handleRoot() {
String html = "";
html += "";
html += "";
html += "Температурный даталоггер
";
html += "Текущее время: " + getCurrentDateTime() + "
";
html += "Свободная память: " + String(esp_get_free_heap_size()) + " байт
";
html += "Имя файла данных: " + String(fileName) + ".csv
"; // Отображение текущего имени
html += "Скачать данные
";
// Форма для установки времени
html += "";
// Форма для установки интервала
html += "";
// Новая форма для изменения имени файла
html += "";
html += "Перейти в спящий режим
";
html += "";
server.send(200, "text/html", html);
}
void handleSetTime() {
if (server.hasArg("time")) {
time_t newTime = parseDateTime(server.arg("time"));
if (newTime > 0) {
baseEpoch = newTime;
totalSleepMicros = 0;
server.send(200, "text/plain", "Время успешно установлено!");
} else {
server.send(400, "text/plain", "Неверный формат времени!");
}
} else {
server.send(400, "text/plain", "Время не указано!");
}
}
void handleSetInterval() {
if (server.hasArg("interval")) {
uint8_t newInterval = server.arg("interval").toInt();
if (newInterval >= 0 && newInterval <= 3) {
currentIntervalIdx = newInterval;
server.send(200, "text/plain", "Интервал изменен!");
} else {
server.send(400, "text/plain", "Неверный интервал!");
}
} else {
server.send(400, "text/plain", "Интервал не указан!");
}
}
// Новый обработчик для изменения имени
void handleSetName() {
if (server.hasArg("name")) {
String newName = server.arg("name");
if (newName.length() > 0 && newName.length() < 32) {
newName.toCharArray(fileName, 32);
server.send(200, "text/plain", "Имя устройства обновлено!");
} else {
server.send(400, "text/plain", "Имя должно содержать от 1 до 31 символа!");
}
} else {
server.send(400, "text/plain", "Имя не указано!");
}
}
void handleDownload() {
String fullFileName = String("/") + fileName + ".csv";
if (!SPIFFS.exists(fullFileName)) {
server.send(404, "text/plain", "Файл данных не найден!");
return;
}
File file = SPIFFS.open(fullFileName, "r");
if (!file) {
server.send(500, "text/plain", "Ошибка открытия файла!");
return;
}
// Используем имя файла для скачивания
String downloadName = fileName + ".csv";
server.sendHeader("Content-Type", "text/csv");
server.sendHeader("Content-Disposition", "attachment; filename=" + downloadName);
server.streamFile(file, "text/csv");
file.close();
}
void handleSleep() {
server.send(200, "text/plain", "Переход в спящий режим...");
delay(1000);
goToDeepSleep(SLEEP_INTERVALS[currentIntervalIdx]);
}
// ================== УПРАВЛЕНИЕ ПИТАНИЕМ ==================
void goToDeepSleep(uint64_t interval) {
Serial.println("Подготовка к глубокому сну...");
// Отключаем все перед сном
server.stop();
WiFi.softAPdisconnect(true);
digitalWrite(POWER_CTRL_PIN, LOW); // Отключаем питание датчиков
// Настраиваем пробуждение
esp_sleep_enable_ext0_wakeup(GPIO_NUM_32, LOW);
esp_sleep_enable_timer_wakeup(interval);
Serial.print("Переход в сон на ");
Serial.print(interval / 1000000);
Serial.println(" секунд");
delay(100);
esp_deep_sleep_start();
}