Перейти к содержимому

Open

Фотография
* * * * * 1 Голосов

DIY pH-метр лабораторного уровня


  • Авторизуйтесь для ответа в теме
Сообщений в теме: 227

#201 Карен

Карен

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 4 040
  • Откуда:Камчатка

Отправлено 12 Июль 2025 - 12:33

Карен, а если произойдет обрыв одного из проводов датчика, он будет тупо лить по минуте, каждые 10 минут? То есть выходит, что в течение суток он будет лить, в общем, примерно 2.5 часа.

Не имеет ли смысл сохранять в eprom-е среднее время автодолива, скажем, за предыдущие 5 дней, и при двукратном превышении среднего уровня давать сигнал тревоги?

Может и имеет, но я не озабачивался. Сейчас уже нет интереса, а раньше, когда энтузиазм был, не пришло такое в голову. 

За все это время у меня несколько раз замыкало провода, например шмотком засохшей нитчатки - автодолив отключался и узнавал я об этом по журчанию воды в шахте. 

А вот чтобы вставал на постоянку, в смысле как ты написал, обрыв провода - не было ни разу. 


Самодельные тесты: NO3, NO2, PO4, Si, KH, Ca, Mg.
от крипта...

#202 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 00:32

/ Параметры оборудования
#define FAN_PIN D0
#define WATER_SENSOR_PIN D7
#define TEMP_INNER_PIN D5
#define TEMP_EXTERNAL_PIN D6

// Настройки температуры
const float FAN_ON_TEMP = 26.0;
const float FAN_OFF_TEMP = 25.0;
const float HIGH_TEMP_ALARM = 26.5;

// Интервалы времени (мс)
const unsigned long BOT_INTERVAL = 1000;
const unsigned long TEMP_GRAPH_INTERVAL = 3600000;
const unsigned long ALARM_COOLDOWN = 900000;
const unsigned long WATER_ALARM_COOLDOWN = 1800000;

// Объекты компонентов
OneWire innerWire(TEMP_INNER_PIN);
OneWire externalWire(TEMP_EXTERNAL_PIN);
DallasTemperature innerSensor(&innerWire);
DallasTemperature externalSensor(&externalWire);
RTC_DS3231 rtc;
Adafruit_SSD1306 display(128, 64, &Wire);
WiFiClientSecure client;
UniversalTelegramBot bot(BOT_TOKEN, client);

// Глобальные переменные
float innerTemp = 0;
float externalTemp = 0;
bool fanState = false;
bool waterAlarm = false;
bool tempAlarm = false;
bool innerSensorActive = true;
bool externalSensorActive = true;
bool manualFanControl = false; // Флаг ручного управления вентилятором
unsigned long lastBotUpdate = 0;
unsigned long lastTempSend = 0;
unsigned long lastTempAlarm = 0;
unsigned long lastWaterAlarm = 0;

// История температур
const int HISTORY_SIZE = 24;
float tempHistory[HISTORY_SIZE] = {0};
int historyIndex = 0;

// Клавиатура Telegram
const String keyboardJson = R"([
["Вкл вентилятор", "Выкл вентилятор"],
["Статус", "График"],
["Датчик1 Вкл/Выкл", "Датчик2 Вкл/Выкл"]
])";

void setup() {
Serial.begin(115200);
Serial.println("\nСистема аквариумного терморегулятора");
Serial.println("Доступные команды:");
Serial.println("SETTIME ГГГГ-ММ-ДД ЧЧ:ММ:СС - Установка времени");
Serial.println("GETTIME - Получить текущее время");
Serial.println("STATUS - Получить статус системы");
Serial.println("FAN ON/OFF - Ручное управление вентилятором");
Serial.println("SENSORS - Статус датчиков");

pinMode(FAN_PIN, OUTPUT);
pinMode(WATER_SENSOR_PIN, INPUT);
digitalWrite(FAN_PIN, LOW);

// Инициализация OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("OLED ошибка!"));
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Загрузка...");
display.display();

// Подключение Wi-Fi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
display.print("Подключение WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
display.print(".");
display.display();
Serial.print(".");
}
display.println("\nПодключено!");
display.display();
Serial.println("\nWiFi подключен");

// Инициализация датчиков
innerSensor.begin();
externalSensor.begin();
Wire.begin();

if (!rtc.begin()) {
Serial.println("RTC не найден!");
display.println("RTC ошибка!");
display.display();
}

// Проверка датчиков температуры
if (!checkSensor(innerSensor)) {
Serial.println("Внутр. датчик не найден!");
innerSensorActive = false;
}
if (!checkSensor(externalSensor)) {
Serial.println("Внеш. датчик не найден!");
externalSensorActive = false;
}

// Настройка времени если RTC не инициализирован
if (rtc.lostPower()) {
Serial.println("Настройте время RTC!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

// Настройка TLS для Telegram
client.setInsecure();

// Отправка приветственного сообщения
bot.sendMessage(CHAT_ID, "Аквариумный терморегулятор запущен!", "");
bot.sendMessageWithReplyKeyboard(CHAT_ID, "Выберите действие:", "", keyboardJson, true);
}

bool checkSensor(DallasTemperature &sensor) {
DeviceAddress addr;
return sensor.getDeviceCount() > 0 && sensor.getAddress(addr, 0);
}

void loop() {
// Обработка Telegram
if (millis() - lastBotUpdate > BOT_INTERVAL) {
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
handleTelegram(numNewMessages);
lastBotUpdate = millis();
}

// Чтение температуры
readTemperatures();

// Управление вентилятором
controlFan();

// Проверка тревог
checkAlarms();

// Сохранение температуры в историю
if (millis() - lastTempSend > TEMP_GRAPH_INTERVAL) {
saveTemperature();
sendTemperatureData();
lastTempSend = millis();
}

// Обновление дисплея
updateDisplay();

// Обработка команд Serial
handleSerialCommands();
}

void readTemperatures() {
if (innerSensorActive) {
innerSensor.requestTemperatures();
innerTemp = innerSensor.getTempCByIndex(0);
}

if (externalSensorActive) {
externalSensor.requestTemperatures();
externalTemp = externalSensor.getTempCByIndex(0);
}
delay(100);
}

void controlFan() {
if (manualFanControl) {
// В режиме ручного управления автоматика не работает
return;
}

if (!fanState && innerTemp >= FAN_ON_TEMP) {
fanState = true;
digitalWrite(FAN_PIN, HIGH);
bot.sendMessage(CHAT_ID, "Вентилятор ВКЛ: " + String(innerTemp, 1) + "°C", "");
Serial.println("Вентилятор автоматически включен");
}
else if (fanState && innerTemp <= FAN_OFF_TEMP) {
fanState = false;
digitalWrite(FAN_PIN, LOW);
bot.sendMessage(CHAT_ID, "Вентилятор ВЫКЛ: " + String(innerTemp, 1) + "°C", "");
Serial.println("Вентилятор автоматически выключен");
}
}

void checkAlarms() {
// Проверка высокой температуры
if (innerTemp >= HIGH_TEMP_ALARM && millis() - lastTempAlarm > ALARM_COOLDOWN) {
String msg = "?? ВНИМАНИЕ! Высокая температура: " + String(innerTemp,1) + "°C";
bot.sendMessage(CHAT_ID, msg, "");
Serial.println(msg);
lastTempAlarm = millis();
tempAlarm = true;
}
else {
tempAlarm = false;
}

// Проверка уровня воды
if (digitalRead(WATER_SENSOR_PIN) == HIGH) {
if (millis() - lastWaterAlarm > WATER_ALARM_COOLDOWN) {
String msg = "?? КРИТИЧЕСКО! Отсутствует вода!";
bot.sendMessage(CHAT_ID, msg, "");
Serial.println(msg);
lastWaterAlarm = millis();
waterAlarm = true;
}
}
else {
waterAlarm = false;
}
}

void saveTemperature() {
tempHistory[historyIndex] = innerTemp;
historyIndex = (historyIndex + 1) % HISTORY_SIZE;
}

void sendTemperatureData() {
String data = "?? Температура за последние " + String(HISTORY_SIZE) + " ч:\n";

DateTime now = rtc.now();
int currentHour = now.hour();

for (int i = 0; i < HISTORY_SIZE; i++) {
int idx = (historyIndex + i) % HISTORY_SIZE;
int hour = (currentHour - i + 24) % 24;

if (tempHistory[idx] != 0) {
data += String(hour) + ":00 - " + String(tempHistory[idx], 1) + "°C\n";
}
}

bot.sendMessage(CHAT_ID, data, "");
}

void updateDisplay() {
display.clearDisplay();
display.setCursor(0,0);

// Температуры
display.print("Внутр: ");
if (innerSensorActive) display.print(innerTemp,1);
else display.print("--");
display.println("C");

display.print("Внеш: ");
if (externalSensorActive) display.print(externalTemp,1);
else display.print("--");
display.println("C");

// Дата и время
DateTime now = rtc.now();
display.printf("%02d.%02d.%04d\n", now.day(), now.month(), now.year());
display.printf("%02d:%02d:%02d\n", now.hour(), now.minute(), now.second());

// Статус вентилятора
display.print(fanState ? "ВЕНТ ВКЛ" : "ВЕНТ ВЫКЛ");
if (manualFanControl) display.print(" (РУЧН)");
display.println();

// Тревоги
if (waterAlarm) display.println("! НЕТ ВОДЫ !");
if (tempAlarm) display.println("! ПЕРЕГРЕВ !");

display.display();
}

void handleTelegram(int numNewMessages) {
for (int i = 0; i < numNewMessages; i++) {
String chat_id = bot.messages[i].chat_id;
String text = bot.messages[i].text;

if (text == "/start" || text == "Меню") {
bot.sendMessageWithReplyKeyboard(chat_id, "Выберите действие:", "", keyboardJson, true);
}
else if (text == "Вкл вентилятор") {
digitalWrite(FAN_PIN, HIGH);
fanState = true;
manualFanControl = true;
bot.sendMessage(chat_id, "Вентилятор принудительно ВКЛ (автоматика отключена)", "");
Serial.println("Вентилятор включен через Telegram (ручное управление)");
}
else if (text == "Выкл вентилятор") {
digitalWrite(FAN_PIN, LOW);
fanState = false;
manualFanControl = false;
bot.sendMessage(chat_id, "Вентилятор принудительно ВЫКЛ (автоматика включена)", "");
Serial.println("Вентилятор выключен через Telegram (включена автоматика)");
}
else if (text == "Статус") {
String status = "?? Внутр. темп: " + String(innerTemp,1) + "°C\n";
status += "?? Внеш. темп: " + String(externalTemp,1) + "°C\n";
status += "?? Вентилятор: " + String(fanState ? "ВКЛ" : "ВЫКЛ");
status += manualFanControl ? " (РУЧНОЕ УПР)\n" : " (АВТОМАТИКА)\n";
status += "?? Вода: " + String(waterAlarm ? "ОТСУТСТВУЕТ! ??" : "норма ?");
bot.sendMessage(chat_id, status, "");
}
else if (text == "График") {
sendTemperatureData();
}
else if (text == "Датчик1 Вкл/Выкл") {
innerSensorActive = !innerSensorActive;
String msg = innerSensorActive ? "? Внутр. датчик ВКЛ" : "? Внутр. датчик ВЫКЛ";
bot.sendMessage(chat_id, msg, "");
Serial.println(msg);
}
else if (text == "Датчик2 Вкл/Выкл") {
externalSensorActive = !externalSensorActive;
String msg = externalSensorActive ? "? Внеш. датчик ВКЛ" : "? Внеш. датчик ВЫКЛ";
bot.sendMessage(chat_id, msg, "");
Serial.println(msg);
}
}
}

void handleSerialCommands() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();

if (input.startsWith("SETTIME")) {
// Формат: SETTIME ГГГГ-ММ-ДД ЧЧ:ММ:СС
if (input.length() >= 19) {
int y = input.substring(8,12).toInt();
int m = input.substring(13,15).toInt();
int d = input.substring(16,18).toInt();
int hh = input.substring(19,21).toInt();
int mm = input.substring(22,24).toInt();
int ss = input.substring(25,27).toInt();

rtc.adjust(DateTime(y, m, d, hh, mm, ss));
Serial.println("Время установлено!");
} else {
Serial.println("Ошибка формата! Используйте: SETTIME ГГГГ-ММ-ДД ЧЧ:ММ:СС");
}
}
else if (input == "GETTIME") {
DateTime now = rtc.now();
Serial.printf("Текущее время: %02d.%02d.%04d %02d:%02d:%02d\n",
now.day(), now.month(), now.year(),
now.hour(), now.minute(), now.second());
}
else if (input == "STATUS") {
Serial.println("\n=== СТАТУС СИСТЕМЫ ===");
Serial.printf("Внутр. температура: %.1f°C\n", innerTemp);
Serial.printf("Внеш. температура: %.1f°C\n", externalTemp);
Serial.printf("Вентилятор: %s (%s)\n",
fanState ? "ВКЛ" : "ВЫКЛ",
manualFanControl ? "РУЧНОЕ УПР" : "АВТОМАТИКА");
Serial.printf("Датчик воды: %s\n", digitalRead(WATER_SENSOR_PIN) ? "НЕТ ВОДЫ!" : "норма");
Serial.printf("Внутр. датчик: %s\n", innerSensorActive ? "работает" : "отключен");
Serial.printf("Внеш. датчик: %s\n", externalSensorActive ? "работает" : "отключен");
}
else if (input == "FAN ON") {
digitalWrite(FAN_PIN, HIGH);
fanState = true;
manualFanControl = true;
Serial.println("Вентилятор принудительно включен (автоматика отключена)");
}
else if (input == "FAN OFF") {
digitalWrite(FAN_PIN, LOW);
fanState = false;
manualFanControl = false;
Serial.println("Вентилятор принудительно выключен (автоматика включена)");
}
else if (input == "SENSORS") {
Serial.println("\n=== СОСТОЯНИЕ ДАТЧИКОВ ===");
Serial.printf("Внутр. датчик: %s (%.1f°C)\n",
innerSensorActive ? "активен" : "отключен", innerTemp);
Serial.printf("Внеш. датчик: %s (%.1f°C)\n",
externalSensorActive ? "активен" : "отключен", externalTemp);
Serial.printf("Датчик воды: %s\n",
digitalRead(WATER_SENSOR_PIN) ? "НЕТ ВОДЫ!" : "норма");
}
else if (input == "HELP") {
Serial.println("\nДоступные команды:");
Serial.println("SETTIME ГГГГ-ММ-ДД ЧЧ:ММ:СС - Установка времени");
Serial.println("GETTIME - Текущее время");
Serial.println("STATUS - Статус системы");
Serial.println("FAN ON/OFF - Ручное управление вентилятором");
Serial.println("SENSORS - Состояние датчиков");
Serial.println("HELP - Справка по командам");
}
else {
Serial.println("Неизвестная команда. Введите HELP для списка команд");
}
}
}

Это для этого. Работает ведь

Прикрепленные изображения

  • IMG-20250712-WA0002.jpeg


#203 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 00:33

А для этого раздельный код, пока не понимаю но сделаем

Прикрепленные изображения

  • IMG_20250713_001426.jpg


#204 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 00:42

Ну если успею до отпуска

#205 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 01:55

Получилось говорить ему по русски. Но криво

Прикрепленные изображения

  • IMG-20250713-WA0000.jpg


#206 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 01:55

Постараюсь поправить

#207 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 02:02

Работает через телеграм

Прикрепленные изображения

  • Screenshot_20250713_020135.jpg


#208 Карен

Карен

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 4 040
  • Откуда:Камчатка

Отправлено 13 Июль 2025 - 03:07

Работает через телеграм

Давайте все, что касается ESP32 в другую тему (без меня). Мы и так уже отошли слишком далеко от изначального назначения темы.


Самодельные тесты: NO3, NO2, PO4, Si, KH, Ca, Mg.
от крипта...

#209 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 13 Июль 2025 - 09:11

Это к примеру. Хочу на вашей плате сделать. Там проводов лишних получится всего 4 но думаю окуратно. И разьемчики есть для термометр и вентилятора

#210 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 20 Июль 2025 - 19:01

Вот два результата с резистор R2 5.0 0.1% и R2 5.1 0.1%.

Прикрепленные изображения

  • IMG_20250720_152347.jpg
  • IMG_20250720_152320.jpg


#211 Карен

Карен

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 4 040
  • Откуда:Камчатка

Отправлено 21 Июль 2025 - 02:52

То есть один резистор говорит что при pH 6.86 электрод дает 19.05 милливольт, другой тот же потенциал замеряет как 18.40 mv. Разница в измерении потенциала с электрода 3.4%. 

Еще раз: понятно что калибровкой вы все это дело нивелируете в любом случае. Но параметры электрода (наклон кривой и смещение изопотенциальной точки от нуля при pH 7) нет.


Самодельные тесты: NO3, NO2, PO4, Si, KH, Ca, Mg.
от крипта...

#212 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 21 Июль 2025 - 05:29

Работает прекрасно в обоих случаях. Но 5К я ждал больше месяца, а 5.1К везде есть

#213 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 21 Июль 2025 - 05:30

То есть один резистор говорит что при pH 6.86 электрод дает 19.05 милливольт, другой тот же потенциал замеряет как 18.40 mv. Разница в измерении потенциала с электрода 3.4%.
Еще раз: понятно что калибровкой вы все это дело нивелируете в любом случае. Но параметры электрода (наклон кривой и смещение изопотенциальной точки от нуля при pH 7) нет.



Мало что понял)))

#214 Карен

Карен

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 4 040
  • Откуда:Камчатка

Отправлено 21 Июль 2025 - 06:41

Мало что понял)))

Ничем не могу помочь :) 


Самодельные тесты: NO3, NO2, PO4, Si, KH, Ca, Mg.
от крипта...

#215 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:36

20 июля было откалибровано все. Щас в растворе 6.86 показывает 5.31. Такое возможно?

#216 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:36

Темп раствора 24.8

#217 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:48

Устаканилось. 5.06

#218 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:48

Как так?

#219 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:57

До синхронизации так должно быть?

Прикрепленные изображения

  • IMG_20250725_195607.jpg


#220 White80

White80

    Продвинутый пользователь

  • Пользователи
  • PipPipPip
  • Cообщений: 1 234

Отправлено 25 Июль 2025 - 19:57

Рано обрадовался аказывается(




Количество пользователей, читающих эту тему: 0

0 пользователей, 0 гостей, 0 анонимных