Контроллер стиральной машины на ардуино.

bort707

★★★★★★✩
21 Сен 2020
3,172
934
Илья, вы такой правильный, не пойму почему вы строем не ходите...ой, хотел сказать почему не хотите начать этот проект сами.
Поймите, когда вы сами реализуете безумную идею - это одно. А когда пытаетесь развести других, чтобы они все за вас сделали - совсем другое. Нахлебников никто не любит.
 

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
251
42
Лично я, в таком случае, разобрал бы стиралку полностью и провёл бы полный осмотр всех узлов и дефектовку при малейшем подозрении на выход из строя какого-то элемента, например патрубков и т. п.
ну так разберите стиралку, соберите контроллер и схему, и потом собрать не забудьте, проверьте программы для машины, и сообщите о результатах. зачем вот это всё "разобрал БЫ", "провел осмотр" и т. п. сделайте, потом расскажите

Нередко так рождаются великие идеи и изобретения, изменившие историю;)
нее, это чисто случайность, когда хотят сделать одно, а потом получается совершенно другое, и все такие: "О, да мы же это и хотели изобрести" 😁
 
  • Лойс +1
Реакции: bort707

poty

★★★★★★✩
19 Фев 2020
3,452
985
@Ilya_G, когда сам "изобретатель" себя гробит - я могу это понять, в конце-концов имя этого изобретателя, в случае успеха, будет известно всем, останется в веках (хотя ничего гарантировать нельзя). Но вот зачем других вести по опасному пути?
Что касается "разобрать полностью", "провести дефектовку"... Во-первых, современное (и я говорю про вещи конца прошлого века - начала текущего) производство довольно часто делает детали неразборными - так проще обеспечить их надёжность. Как пример - разборный бак: никто в здравом уме всё равно не будет менять на нём прокладки, потому что есть много факторов, которые приводят к тому, что действовать как изначально задумывалось это всё равно не будет: механические нагрузки приводят к деформациям, устаёт металл, особенно в зонах креплений и проч.
Во-вторых, провести материаловедческую дефектовку - не под силам и многим компаниям, я не говорю уже о стоимости этого дела.
В-третьих, осмотр, допустим, электрической изоляции - ни о чём, как и замена электропроводки для работы во влажных помещениях (отсылаю Вас к ПУЭ). Стиральная машинка - один из самых мощных потребителей с самыми сложными условиями для защиты.
Ну, и в четвёртых... Сколько же времени потребуется для того, чтобы всё это сделать? И чем заменять то, что отнесено в гараж, разобрано до основания и "ждём попадания в область видимости чего-нибудь подходящего для замены"? Не проще заняться чем-то более интересным, а время посвятить зарабатыванию этих пары тысяч разницы между купить готовую деталь для замены и переделать всю машинку?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
хотел сказать почему не хотите начать этот проект сами.
Я и не против, но! нужна помощь. По крайней мере человека, который разбирается в машинках и технологии стирки
Не проще заняться чем-то более интересным, а время посвятить зарабатыванию этих пары тысяч разницы между купить готовую деталь для замены и переделать всю машинку?
1. Электроника - моё хобби и моя же работа
2. Этот проект мне выгоден напрямую
Ну, и в четвёртых... Сколько же времени потребуется для того, чтобы всё это сделать? И чем заменять то, что отнесено в гараж, разобрано до основания и "ждём попадания в область видимости чего-нибудь подходящего для замены"?
ну так разберите стиралку, соберите контроллер и схему, и потом собрать не забудьте, проверьте программы для машины, и сообщите о результатах.
При всём желании создать что-то типа "умного таймера можно" за 1 вечер. Но есть несколько но, которые как минимум могут испортить всю малину. Где нибудь ниже я это опишу
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
Но есть несколько но, которые как минимум могут испортить всю малину. Где нибудь ниже я это опишу
1. Управление. В своём большом сообщении я описывал то, что обычной ардуинки наврядли хватит для опроса всех кнопок, энкодеров, сенсоров, вывода изображения на экран и всего остального, что взаимодействует с пользователем. Конечно если это не самая простая модель с парой кнопок и крутилкой.​
Идей тут можно предложить несколько:
  1. Выкинуть стандартную панель управления нафиг и заколхозить что-то самому. Если на 3d-принтере, то может получиться довольно неплохой результат
  2. Оставить панель управления, подключиться к ней и использовать её. Минус - не универсально и много геморроя.
  3. Оставить панель, но не подключать её, а связь держать с помощью мобильного приложения (связь через bluetooth) или web-интерфейса.
2. Платформа для разработки.​
  • По идее, достаточно обычной Arduino Nano/микроконтроллер ATMega328 + у неё достаточно выводов для всего
  • Если помощнее, то можно взять STM32F401, F411, F103. Стоят как Arduino Mega, но мощнее на порядок. Но это уже без меня, т. к. я и с обычной ардуинкой не справляюсь + нет навыка программирования на C++
  • Если с Wi-Fi, то ESP8266. Минус - мало выводов. Плюс - дешёвая и довольно мощная
  • Модульный проект, состоящий из нескольких отдельных модулей, например ESP8266 как мозг и связь и Arduino Nano/микроконтроллер ATMega328 как расширитель портов и продвинутый ПИД-регулятор. Дополнительные модули типа контроллера мотора (а почему бы и нет, хотя можно и без него), модулей обработки сигнала с датчиков (тахометр и прессостат как минимум) не учитываются
3. Особенности датчиков и конструкции​
Как минимум в СМА используются "особенные" датчики, такие как индукционный тахометр и прессостат. Стоит ли их менять на более простые и распространённые (оптический тахометр и резистивный прессостат).
Если да - упрощается разработка. Если нет - придётся заморочиться, но изменений в конструкции минимум.
Да и термистор может быть "не стандартным", а его коэффициент мне не известен.
4. Особенности работы​
По большей части это касается уровня воды в баке на каждом из режимов, режима работы двигателя и других тонкостей, которые не очевидны, но могут быть ощутимыми в дальнейшем
Вроде бы всё описал:unsure:
 
Изменено:

Arhat109

★★★★✩✩✩
9 Июн 2019
472
202
@bort707, 20-30 тысяч может кому-то и не дорого, а кто-то столько в месяц получает, а ещё и кушать хочет .. всё относительно.
 

Divin

★★★✩✩✩✩
30 Янв 2021
450
197
@Arhat109, Ну это точно, кому война, а кому и спецоперация, но с доходом в 30к электроника как хобби это подрыв бюджета.
 

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
251
42
@Ilya_G, понятно. в электронике зничит шарите, а в остальном не очень? занимаетесь ремонтом бытовухи?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
251
42
Довольно неплохой заработок можно иметь и работа не пыльная
и вы хотите ставить свое изобретение в стиральные машины? есть уверенность в том, что это прокатит? лично у меня нет, поскольку производители не очень любят юзать универсальные комплектующие и сопутствующее в своих продуктах. а если универсальность не подходит, тогда пилить свой контроллер под каждого производителя?
Смотря что именно вы имели ввиду
я имел ввиду прграммирование МК

может тогда проще будет собрать свою стиралку из бочки?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
я имел ввиду прграммирование МК
Ну, этим я тоже занимаюсь, только в меньшей степени. Увы или к счастью (каждый сам выбирает), без программирования МК сейчас электронщику уже не обойтись. Конечно, я не Nich1con который библиотеки пишет, но немного в этом разбираюсь.
и вы хотите ставить свое изобретение в стиральные машины?
Если не сделаю это я - сделает дядя Вася из другого города. К тому же, если этот проект наберёт популярность, то он уже без моего прямого участия обрастёт новыми функциями и улучшится его совместимость
поскольку производители не очень любят юзать универсальные комплектующие и сопутствующее в своих продуктах.
Никто не мешает мне их поставить. А коллекторный мотор или клапан останется им же хоть в Африке, хоть на Марсе.
 

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
2
Я тут ещё на просторах паутины отыскал годный проект по этой теме, там у автора и холодильника проект есть.

 
Изменено:

ukrainian

✩✩✩✩✩✩✩
17 Авг 2022
0
1
Нашел "секретную" программу-алгоритм от какой-то стиральной машинкипрограммы работы машинки.png
 
  • Лойс +1
Реакции: Arhat109

Proton

✩✩✩✩✩✩✩
5 Окт 2021
0
0
Приветствую форумчане. Тема изготовления программного устройства к стиральной машине на платформе Arduino интересна. В наличии имеется промышленная ст. м. КП-122-4. Командоаппарат работает с перфокартой. Подумываю как заменить только функцию перфокарты - микроконтроллером.
Пока что только идея.
 

Ярослав93

✩✩✩✩✩✩✩
24 Апр 2025
0
0
Все ж элементарно😂😂😂!
Берем esp32, ее и к сети подключить можно, и пишем примерно такой код:

C++:
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>

// Настройки WiFi
const char* ssid = "мой_сеть_название";
const char* password = "мой_пароль_ниркому_не_расскажу_такой_я_жадина";

// Пины управления
const int waterValvePin = 2;
const int drainValvePin = 4;
const int motorPin = 5;
const int heaterPin = 18;
const int waterLevelSensorPin = 34;

// Состояния стиральной машины
enum WashState {
  IDLE,
  FILLING_WATER,
  HEATING,
  WASHING,
  DRAINING,
  RINSING,
  SPINNING,
  COMPLETED,
  ABORTED
};

// Режимы стирки
enum WashMode {
  MANUAL,
  QUICK,
  NORMAL
};

struct MachineState {
  WashState state;
  WashMode mode;
  bool waterValve;
  bool drainValve;
  bool motor;
  bool heater;
  int waterLevel;
  unsigned long stateStartTime;
  unsigned long stateDuration;
};

MachineState machine = {
  .state = IDLE,
  .mode = MANUAL,
  .waterValve = false,
  .drainValve = false,
  .motor = false,
  .heater = false,
  .waterLevel = 0,
  .stateStartTime = 0,
  .stateDuration = 0
};

WebServer server(80);

void setup() {
  Serial.begin(115200);
 
  // Настройка пинов
  pinMode(waterValvePin, OUTPUT);
  pinMode(drainValvePin, OUTPUT);
  pinMode(motorPin, OUTPUT);
  pinMode(heaterPin, OUTPUT);
  pinMode(waterLevelSensorPin, INPUT);
 
  digitalWrite(waterValvePin, LOW);
  digitalWrite(drainValvePin, LOW);
  digitalWrite(motorPin, LOW);
  digitalWrite(heaterPin, LOW);
 
  // Подключение к WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
 
  // Маршруты веб-сервера
  server.on("/", handleRoot);
  server.on("/control", handleControl);
  server.on("/status", handleStatus);
  server.on("/start_wash", handleStartWash);
  server.on("/abort_wash", handleAbortWash);
 
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();
  updateSensors();
  updateStateMachine();
  delay(100);
}

void updateSensors() {
  machine.waterLevel = analogRead(waterLevelSensorPin);
}

void updateStateMachine() {
  unsigned long currentTime = millis();
 
  if (machine.state == IDLE || machine.state == COMPLETED || machine.state == ABORTED) {
    return;
  }
 
  if (currentTime - machine.stateStartTime >= machine.stateDuration) {
    switch (machine.state) {
      case FILLING_WATER:
        if (machine.mode == QUICK) transitionState(WASHING);
        else if (machine.mode == NORMAL) transitionState(HEATING);
        break;
      case HEATING:
        transitionState(WASHING);
        break;
      case WASHING:
        transitionState(DRAINING);
        break;
      case DRAINING:
        if (machine.mode == QUICK) transitionState(RINSING);
        else transitionState(COMPLETED);
        break;
      case RINSING:
        transitionState(SPINNING);
        break;
      case SPINNING:
        transitionState(COMPLETED);
        break;
      default:
        break;
    }
  }
}

void transitionState(WashState newState) {
  // Выход из предыдущего состояния
  switch (machine.state) {
    case FILLING_WATER:
      digitalWrite(waterValvePin, LOW);
      machine.waterValve = false;
      break;
    case HEATING:
      digitalWrite(heaterPin, LOW);
      machine.heater = false;
      break;
    case WASHING:
      digitalWrite(motorPin, LOW);
      machine.motor = false;
      break;
    case DRAINING:
      digitalWrite(drainValvePin, LOW);
      machine.drainValve = false;
      break;
    case RINSING:
      digitalWrite(waterValvePin, LOW);
      machine.waterValve = false;
      break;
    case SPINNING:
      digitalWrite(motorPin, LOW);
      machine.motor = false;
      break;
    case ABORTED:
      digitalWrite(waterValvePin, LOW);
      digitalWrite(drainValvePin, LOW);
      digitalWrite(motorPin, LOW);
      digitalWrite(heaterPin, LOW);
      machine.waterValve = false;
      machine.drainValve = false;
      machine.motor = false;
      machine.heater = false;
      break;
    default:
      break;
  }
 
  // Вход в новое состояние
  machine.state = newState;
  machine.stateStartTime = millis();
 
  switch (newState) {
    case IDLE:
      machine.mode = MANUAL;
      machine.stateDuration = 0;
      break;
      
    case FILLING_WATER:
      digitalWrite(waterValvePin, HIGH);
      machine.waterValve = true;
      machine.stateDuration = (machine.mode == QUICK) ? 5000 : 10000;
      break;
      
    case HEATING:
      digitalWrite(heaterPin, HIGH);
      machine.heater = true;
      machine.stateDuration = 15000;
      break;
      
    case WASHING:
      digitalWrite(motorPin, HIGH);
      machine.motor = true;
      machine.stateDuration = (machine.mode == QUICK) ? 10000 : 20000;
      break;
      
    case DRAINING:
      digitalWrite(drainValvePin, HIGH);
      machine.drainValve = true;
      machine.stateDuration = (machine.mode == QUICK) ? 3000 : 5000;
      break;
      
    case RINSING:
      digitalWrite(waterValvePin, HIGH);
      machine.waterValve = true;
      machine.stateDuration = 3000;
      break;
      
    case SPINNING:
      digitalWrite(motorPin, HIGH);
      machine.motor = true;
      machine.stateDuration = 5000;
      break;
      
    case COMPLETED:
      machine.stateDuration = 0;
      machine.mode = MANUAL;
      break;
      
    case ABORTED:
      machine.stateDuration = 0;
      break;
  }
}

void handleRoot() {
  String html = R"=====(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Управление стиральной машинкой</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
    .container { max-width: 800px; margin: 0 auto; }
    .panel { background: #f5f5f5; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
    h1 { color: #333; text-align: center; }
    .btn {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 10px 15px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      cursor: pointer;
      border-radius: 5px;
    }
    .btn-off { background-color: #f44336; }
    .btn-wash { background-color: #2196F3; }
    .btn-abort { background-color: #ff9800; }
    .status { font-weight: bold; }
    .sensor-value { font-size: 18px; color: #2196F3; }
    .wash-status { font-size: 18px; color: #4CAF50; margin-top: 10px; }
    .state-display { font-size: 20px; font-weight: bold; margin: 10px 0; }
    select { padding: 8px; border-radius: 4px; border: 1px solid #ddd; }
    .control-group { margin-bottom: 15px; }
  </style>
  <script>
    let currentMode = 'QUICK';
    
    function updateStatus() {
      fetch('/status')
        .then(response => response.json())
        .then(data => {
          // Обновление кнопок ручного управления
          document.getElementById('waterValveBtn').textContent = 'Клапан воды: ' + (data.waterValve ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('waterValveBtn').className = data.waterValve ? 'btn' : 'btn btn-off';
          document.getElementById('waterValveBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('drainValveBtn').textContent = 'Клапан слива: ' + (data.drainValve ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('drainValveBtn').className = data.drainValve ? 'btn' : 'btn btn-off';
          document.getElementById('drainValveBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('motorBtn').textContent = 'Мотор: ' + (data.motor ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('motorBtn').className = data.motor ? 'btn' : 'btn btn-off';
          document.getElementById('motorBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('heaterBtn').textContent = 'Нагреватель: ' + (data.heater ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('heaterBtn').className = data.heater ? 'btn' : 'btn btn-off';
          document.getElementById('heaterBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          // Обновление показаний датчиков
          document.getElementById('waterLevelValue').textContent = data.waterLevel;
          
          // Обновление статуса
          const stateNames = {
            'IDLE': 'Готов к работе',
            'FILLING_WATER': 'Наполнение водой',
            'HEATING': 'Нагрев воды',
            'WASHING': 'Стирка',
            'DRAINING': 'Слив воды',
            'RINSING': 'Полоскание',
            'SPINNING': 'Отжим',
            'COMPLETED': 'Цикл завершен',
            'ABORTED': 'Цикл прерван'
          };
          
          document.getElementById('currentState').textContent = stateNames[data.state] || data.state;
          
          // Обновление кнопки старта/остановки
          const startBtn = document.getElementById('startAbortBtn');
          if (data.state === 'IDLE' || data.state === 'COMPLETED' || data.state === 'ABORTED') {
            startBtn.textContent = 'Запустить (' + document.getElementById('washMode').value + ')';
            startBtn.className = 'btn btn-wash';
            document.getElementById('washMode').disabled = false;
          } else {
            startBtn.textContent = 'Прервать (' + data.mode + ')';
            startBtn.className = 'btn btn-abort';
            document.getElementById('washMode').disabled = true;
          }
          
          // Прогресс выполнения
          if (data.stateDuration > 0) {
            const elapsed = Date.now() - data.stateStartTime;
            const progress = Math.min(100, (elapsed / data.stateDuration) * 100);
            document.getElementById('progressBar').style.width = progress + '%';
            document.getElementById('progressContainer').style.display = 'block';
          } else {
            document.getElementById('progressContainer').style.display = 'none';
          }
        });
    }
    
    function toggleControl(element) {
      const control = element.id.replace('Btn', '');
      fetch('/control?cmd=' + control)
        .then(() => updateStatus());
    }
    
    function handleStartAbort() {
      const startBtn = document.getElementById('startAbortBtn');
      
      if (startBtn.className.includes('btn-wash')) {
        // Запуск стирки
        const mode = document.getElementById('washMode').value;
        fetch('/start_wash?mode=' + mode)
          .then(() => updateStatus());
      } else {
        // Прерывание стирки
        fetch('/abort_wash')
          .then(() => updateStatus());
      }
    }
    
    function updateButtonText() {
      const startBtn = document.getElementById('startAbortBtn');
      if (startBtn.className.includes('btn-wash')) {
        startBtn.textContent = 'Запустить (' + document.getElementById('washMode').value + ')';
      }
    }
    
    // Обновляем статус каждые 2 секунды
    setInterval(updateStatus, 2000);
    // Инициализация при загрузке
    window.onload = updateStatus;
  </script>
</head>
<body>
  <div class="container">
    <h1>Управление стиральной машинкой</h1>
    
    <div class="panel">
      <h2>Текущее состояние</h2>
      <p>Состояние: <span id="currentState" class="state-display">Готов к работе</span></p>
      <div id="progressContainer" style="display:none; width:100%; background-color:#ddd; border-radius:5px;">
        <div id="progressBar" style="height:20px; width:0%; background-color:#4CAF50; border-radius:5px;"></div>
      </div>
    </div>
    
    <div class="panel">
      <h2>Ручное управление</h2>
      <button id="waterValveBtn" class="btn" onclick="toggleControl(this)">Клапан воды: ВЫКЛ</button>
      <button id="drainValveBtn" class="btn" onclick="toggleControl(this)">Клапан слива: ВЫКЛ</button>
      <button id="motorBtn" class="btn" onclick="toggleControl(this)">Мотор: ВЫКЛ</button>
      <button id="heaterBtn" class="btn" onclick="toggleControl(this)">Нагреватель: ВЫКЛ</button>
    </div>
    
    <div class="panel">
      <h2>Датчики</h2>
      <p>Уровень воды: <span id="waterLevelValue" class="sensor-value">0</span></p>
    </div>
    
    <div class="panel">
      <h2>Управление стиркой</h2>
      <div class="control-group">
        <select id="washMode" onchange="updateButtonText()">
          <option value="QUICK">Быстрая стирка</option>
          <option value="NORMAL">Обычная стирка</option>
        </select>
      </div>
      <button id="startAbortBtn" class="btn btn-wash" onclick="handleStartAbort()">Запустить (Быстрая стирка)</button>
    </div>
  </div>
</body>
</html>
)=====";
 
  server.send(200, "text/html", html);
}

void handleControl() {
  if (machine.state != IDLE && machine.state != COMPLETED && machine.state != ABORTED) {
    server.send(403, "text/plain", "Ручное управление недоступно во время стирки");
    return;
  }
 
  String cmd = server.arg("cmd");
 
  if (cmd == "waterValve") {
    machine.waterValve = !machine.waterValve;
    digitalWrite(waterValvePin, machine.waterValve ? HIGH : LOW);
  } else if (cmd == "drainValve") {
    machine.drainValve = !machine.drainValve;
    digitalWrite(drainValvePin, machine.drainValve ? HIGH : LOW);
  } else if (cmd == "motor") {
    machine.motor = !machine.motor;
    digitalWrite(motorPin, machine.motor ? HIGH : LOW);
  } else if (cmd == "heater") {
    machine.heater = !machine.heater;
    digitalWrite(heaterPin, machine.heater ? HIGH : LOW);
  }
 
  server.send(200, "text/plain", "OK");
}

void handleStatus() {
  StaticJsonDocument<512> doc;
 
  doc["state"] = String(getStateName(machine.state));
  doc["mode"] = String(getModeName(machine.mode));
  doc["waterValve"] = machine.waterValve;
  doc["drainValve"] = machine.drainValve;
  doc["motor"] = machine.motor;
  doc["heater"] = machine.heater;
  doc["waterLevel"] = machine.waterLevel;
  doc["stateStartTime"] = machine.stateStartTime;
  doc["stateDuration"] = machine.stateDuration;
 
  String json;
  serializeJson(doc, json);
  server.send(200, "application/json", json);
}

const char* getStateName(WashState state) {
  switch(state) {
    case IDLE: return "IDLE";
    case FILLING_WATER: return "FILLING_WATER";
    case HEATING: return "HEATING";
    case WASHING: return "WASHING";
    case DRAINING: return "DRAINING";
    case RINSING: return "RINSING";
    case SPINNING: return "SPINNING";
    case COMPLETED: return "COMPLETED";
    case ABORTED: return "ABORTED";
    default: return "UNKNOWN";
  }
}

const char* getModeName(WashMode mode) {
  switch(mode) {
    case MANUAL: return "MANUAL";
    case QUICK: return "QUICK";
    case NORMAL: return "NORMAL";
    default: return "UNKNOWN";
  }
}

void handleStartWash() {
  if (machine.state != IDLE && machine.state != COMPLETED && machine.state != ABORTED) {
    server.send(403, "text/plain", "Стирка уже выполняется");
    return;
  }
 
  String mode = server.arg("mode");
 
  if (mode == "QUICK") {
    machine.mode = QUICK;
    transitionState(FILLING_WATER);
    server.send(200, "text/plain", "Started quick wash");
  } else if (mode == "NORMAL") {
    machine.mode = NORMAL;
    transitionState(FILLING_WATER);
    server.send(200, "text/plain", "Started normal wash");
  } else {
    server.send(400, "text/plain", "Invalid mode");
  }
}

void handleAbortWash() {
  if (machine.state == IDLE || machine.state == COMPLETED || machine.state == ABORTED) {
    server.send(400, "text/plain", "Нет активного процесса стирки");
    return;
  }
 
  transitionState(ABORTED);
  server.send(200, "text/plain", "Стирка прервана");
}