diff --git a/Autoclave Scale 60mm.html b/Autoclave Scale 60mm.html new file mode 100644 index 0000000..4e70b1c --- /dev/null +++ b/Autoclave Scale 60mm.html @@ -0,0 +1,146 @@ + + + + + Autoclave Scale 60mm + + + + +
+

Horizontal Scale for 60mm Fader (1:1)

+

Instructions: Press Ctrl + P to print. In the print dialog, set Scale to 100% (or "Actual Size") and disable margins/headers. Cut along the outer solid box if needed.

+
+ +
+ + + + + + + + + + + + + + + + + + + VEG / MUSH + + + + FISH + + + + POULTRY + + + + MEAT + + + + + 5m + + + + 15m + + + + 30m + + + + 45m + + + + 1h + + + + 1:30 + + + + 2h + + + + 2:30 + + + + 3h + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/README.md b/README.md index abbf8de..2067e30 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ -# autoclav +# Таймер автоклава для работы с терморегулятором -Автоматика таймера для автоклава \ No newline at end of file +Легковесное решение для автоматизации процессов стерилизации в автоклаве на базе микроконтроллера Wemos D1 Mini (ESP8266) под управлением прошивки NodeMCU Lua (версия integer-only, без плавающей точки). + +Система управляет циклом нагрева и выдержки времени по сигналу от внешнего термостата. Настройка времени работы осуществляется локально с помощью ползункового фейдера, а удаленный мониторинг доступен через автономный веб-интерфейс по WiFi. + +## Особенности системы +- **Защита при старте:** Встроенная программная пауза 5 секунд при включении устройства в розетку для предотвращения ложных срабатываний до стабилизации питания. +- **Аппаратная фиксация таймера:** Уставка времени жестко блокируется в памяти в момент первого достижения рабочей температуры. Случайное смещение ползунка во время стерилизации не повлияет на обратный отсчет. +- **Фильтрация шумов АЦП:** Сглаживание показаний потенциометра методом скользящего среднего по 10 замерам для устранения дребезга значений. +- **Нелинейная шкала времени:** Дискретность шага составляет 5 минут в диапазоне до 1 часа и 10 минут в диапазоне от 1 до 3 часов для удобной физической разметки панели. +- **Автономный Веб-интерфейс:** Точка доступа WiFi (`192.168.4.1`) работает без пароля и выводит адаптивную страницу со статусом системы и поминутным округлением оставшегося времени (с автообновлением каждые 5 секунд). +- **Ориентированность на отказоустойчивость:** Код разработан с учетом совместной работы с независимой механической защитой по питанию. + +## Коммутация и распиновка (Pinout) + +| Компонент | Пин Wemos D1 | ID пина в NodeMCU | Логический уровень / Питание | Описание | +| :--- | :--- | :--- | :--- | :--- | +| **Реле ТЭНа** | `D1` (GPIO5) | `1` | Выход 3.3V | Управляет силовым реле или контактором ТЭНов. | +| **Сигнал термостата** | `D2` (GPIO4) | `2` | Вход 3.3V (Pull-up) | Подключается к сухим контактам внешнего термостата. Пока идет нагрев, контакты замкнуты на GND. При достижении уставки размыкаются (переход в HIGH). | +| **Фейдер времени** | `A0` (ADC0) | `0` | Аналоговый 0V - 3.3V | Ползунковый потенциометр 10 кОм. Подключается строго к линиям `3V3` и `GND`. | +| **Аварийный термостат** | *Внешний* | *Отсутствует* | Силовая линия ~220В | Биметаллический выключатель KSD301 (130°C/140°C NC с кнопкой ручного сброса) на корпусе. Включается последовательно в разрыв питания ТЭНа. | + +### ⚠️ Предупреждение по питанию потенциометра +Несмотря на то, что периферийные датчики могут питаться от 5V, ползунковый резистор времени **ОБЯЗАТЕЛЬНО** должен быть запитан от пина **`3V3`** платы Wemos. Это гарантирует, что аналоговый сигнал не превысит допустимый предел встроенного делителя напряжения ESP8266 (3.2V). diff --git a/flash.sh b/flash.sh new file mode 100644 index 0000000..48a105c --- /dev/null +++ b/flash.sh @@ -0,0 +1,2 @@ +python3 -m esptool --port /dev/ttyUSB0 erase_flash +python3 -m esptool --port /dev/ttyUSB0 write_flash -fm dio 0x00000 nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..ca3675d --- /dev/null +++ b/init.lua @@ -0,0 +1,151 @@ +main_timer = tmr.create() +cycle_timer = tmr.create() +adc_timer = tmr.create() +srv = net.createServer(net.TCP) +ap_config = {} + +local pin_relay = 1 +local pin_thermostat = 2 +local START_DELAY_MS = 5000 + +local time_table = {} +local table_size = 0 + +local function init_time_table() + local index = 0 + for m = 5, 60, 5 do + time_table[index] = m * 60 + index = index + 1 + end + for m = 70, 180, 10 do + time_table[index] = m * 60 + index = index + 1 + end + table_size = index - 1 +end +init_time_table() + +local adc_history = {} +local adc_index = 1 +local max_samples = 10 +local adc_average = 0 +local set_time_sec = 300 +local remaining_time_sec = 300 +local process_started = false +local process_finished = false + +gpio.mode(pin_relay, gpio.OUTPUT) +gpio.write(pin_relay, gpio.LOW) +gpio.mode(pin_thermostat, gpio.INPUT, gpio.PULLUP) + +print("System init delay...") + +local function read_averaged_adc() + local raw = adc.read(0) + adc_history[adc_index] = raw + adc_index = adc_index + 1 + if adc_index > max_samples then adc_index = 1 end + + local sum = 0 + local count = 0 + for i = 1, max_samples do + if adc_history[i] then + sum = sum + adc_history[i] + count = count + 1 + end + end + adc_average = sum / count + + local tbl_index = (adc_average * table_size) / 1023 + if tbl_index < 0 then tbl_index = 0 end + if tbl_index > table_size then tbl_index = table_size end + + set_time_sec = time_table[tbl_index] + + if not process_started then + remaining_time_sec = set_time_sec + end +end + +adc_timer:register(200, tmr.ALARM_AUTO, read_averaged_adc) + +main_timer:register(START_DELAY_MS, tmr.ALARM_SINGLE, function() + print("Heater ON. Waiting thermostat...") + gpio.write(pin_relay, gpio.HIGH) + adc_timer:start() + + cycle_timer:register(1000, tmr.ALARM_AUTO, function() + local thermo_state = gpio.read(pin_thermostat) + + local left_min = (remaining_time_sec + 59) / 60 + local set_min = set_time_sec / 60 + print("TMR: Left=" .. left_min .. "m | Set=" .. set_min .. "m | Input D2=" .. thermo_state) + + if not process_started and not process_finished then + if thermo_state == gpio.HIGH then + process_started = true + adc_timer:stop() + print("Target reached! Timer active: " .. set_min .. "m") + end + + elseif process_started and not process_finished then + if remaining_time_sec > 0 then + remaining_time_sec = remaining_time_sec - 1 + else + process_started = false + process_finished = true + gpio.write(pin_relay, gpio.LOW) + print("Process finished. Heater OFF.") + cycle_timer:stop() + end + end + end) + cycle_timer:start() +end) +main_timer:start() + +wifi.setmode(wifi.SOFTAP) +ap_config.ssid = "Autoclave_Control" +ap_config.auth = wifi.OPEN +wifi.ap.config(ap_config) +print("AP Ready. IP: " .. wifi.ap.getip()) + +srv:listen(80, function(conn) + conn:on("receive", function(client, request) + + local function format_time_min(sec, is_set) + local total_min = 0 + if is_set then + total_min = sec / 60 + else + total_min = (sec + 59) / 60 + end + local h = total_min / 60 + local m = total_min % 60 + return string.format("%02d:%02d", h, m) + end + + local status_text = "Heating" + if process_started then status_text = "Sterilization" end + if process_finished then status_text = "Done" end + + local html = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + html = html .. "" + html = html .. "" + html = html .. "" + html = html .. "Autoclave" + html = html .. "" + html = html .. "
" + html = html .. "

Autoclave Control

" + html = html .. "

Status: " .. status_text .. "

" + html = html .. "

Set Time: " .. format_time_min(set_time_sec, true) .. "

" + html = html .. "

Remaining: " .. format_time_min(remaining_time_sec, false) .. "

" + html = html .. "
" + + client:send(html) + client:close() + collectgarbage() + end) +end) diff --git a/nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin b/nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin new file mode 100644 index 0000000..83d6118 Binary files /dev/null and b/nodemcu-release-10-modules-2026-08-18-07-13-33-integer.bin differ