#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", "Стирка прервана");
}