No description
  • C++ 55.8%
  • Shell 44.2%
Find a file
2026-07-13 16:03:10 +03:00
psu-fan-controller.conf upload project 2026-07-13 16:03:10 +03:00
psu-fan-controller.ino upload project 2026-07-13 16:03:10 +03:00
psu-fan-ctl.service upload project 2026-07-13 16:03:10 +03:00
psu-fan-ctl.sh upload project 2026-07-13 16:03:10 +03:00
README.md upload project 2026-07-13 16:03:10 +03:00

PSU Fan Controller

Контроллер вентилятора блока питания на базе Arduino Nano с хост-сервером для Debian/Linux.

Проект состоит из двух компонентов:

  • Arduino Nano (.ino) — прошивка: чтение NTC-термисторов, управление ШИМ вентилятора, аварийная защита, звуковые оповещения
  • psu-fan-ctl (.sh) — демон и CLI для Linux: чтение данных Arduino по USB-serial, sysfs-интерфейс, CSV-логирование, алерты, MQTT

Содержание


Архитектура

┌─────────────────────┐      USB Serial       ┌───────────────────┐
│    Arduino Nano     │◄────── 115200 ────────►│   psu-fan-ctl     │
│                     │     JSON (2 сек)       │   (bash daemon)   │
│  A0 ─ 10k NTC       │                        │                   │
│  A1 ─ 100k NTC      │                        │  /tmp/psu-fan/    │  ◄── sysfs
│  D3 ─ Fan PWM 25kHz │                        │  sensors.csv      │  ◄── лог
│  D6 ─ Piezo speaker │                        │  alerts.log       │  ◄── алерты
└─────────────────────┘                        │  MQTT (опц.)      │
                                                └───────────────────┘

Принцип работы:

  1. Arduino читает два NTC-термистора, вычисляет температуру.
  2. Управление вентилятором ведётся по max(temp1, temp2) — максимальной температуре среди исправных датчиков.
  3. Arduino отправляет JSON-статус каждые 2 секунды по serial.
  4. Демон psu-fan-ctl парсит JSON, обновляет sysfs, логирует в CSV, проверяет пороги алертов.
  5. CLI-команды передаются от клиента к демону через файловый релей, от демоны — к Arduino по serial.

Схема подключения

Arduino Nano
┌──────────────────┐
│                  │
│  A0 ──── 10k ──── 5V       (channel 1: 10k NTC)
│  A0 ──── NTC ──── GND      5V ──[10k]──┬── A0 ──[NTC]── GND
│                  │                        │
│  A1 ──── 100k ─── 5V       (channel 2: 100k NTC)
│  A1 ──── NTC ──── GND      5V ──[100k]──┬── A1 ──[NTC]── GND
│                  │                        │
│  D3 ──── Fan gate (OC2B, PWM 25 kHz)    │
│  D6 ──── Piezo speaker                   │
│                  │
│  TX/RX ── USB Serial (115200 baud)       │
└──────────────────┘

Датчики:

  • Канал 1 (A0): NTC 10kOhm, B=3950, подтянутый резистор 10kOhm
  • Канал 2 (A1): NTC 100kOhm, B=3950, подтянутый резистор 100kOhm

Вентилятор:

  • Один вентилятор (M3016D или аналог), управление через D3 (Timer2, OC2B, 25 kHz)
  • Оба канала имеют приоритет: применяется max(duty_ch1, duty_ch2)

Прошивка Arduino

Требования

  • Arduino IDE 1.8+ или arduino-cli
  • Библиотеки: только стандартные AVR (<avr/wdt.h>, <avr/eeprom.h>, <math.h>, <string.h>, <stdlib.h>)

Таймеры

Таймер Режим Частота Назначение
Timer1 CTC, prescaler=64 10 kHz Генерация тонов + системный тик (1 мс)
Timer2 Fast PWM, TOP=79, prescaler=8 25 kHz ШИМ вентилятора (OC2B → D3)

Загрузка прошивки

arduino-cli compile --fqbn arduino:avr:nano psu-fan-controller.ino
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:nano psu-fan-controller.ino

Первичный запуск

При первом запуске (или после повреждения EEPROM) автоматически выполняется factory reset с дефолтными параметрами:

  • min_temp=50.0°C, max_temp=75.0°C
  • min_duty=25%, max_duty=100%
  • hysteresis=2.0°C
  • Режим: throttle
  • PID: Kp=5.0, Ki=0.1, Kd=2.0

После загрузки прошивки Arduino выполняет самотест вентилятора (включение на 100% на 2 секунды) и воспроизводит мелодию boot.


Установка на Debian

Быстрая установка

chmod +x psu-fan-ctl.sh
sudo ./psu-fan-ctl.sh install

Это:

  1. Скопирует скрипт в /usr/local/bin/psu-fan-ctl
  2. Установит systemd-сервис
  3. Установит конфиг в /etc/psu-fan-controller.conf (если отсутствует)
  4. Создаст udev-правило для стабильного имени serial-порта (/dev/psu-fan-arduino)

Запуск

sudo systemctl start psu-fan-ctl
sudo systemctl enable psu-fan-ctl    # автозапуск при загрузке

Зависимости

  • jq — парсинг JSON (обязательно)
  • python3 — очистка DTR при реконнекте (опционально)
  • mosquitto_pub — MQTT-публикация (опционально)
sudo apt install jq

Удаление

sudo psu-fan-ctl uninstall

Конфигурация

Файл: /etc/psu-fan-controller.conf

# Последовательный порт (пусто = автодетект /dev/ttyACM* или /dev/ttyUSB*)
SERIAL_PORT=""

# Скорость порта
BAUD=115200

# Интервал чтения (секунды)
INTERVAL=2

# Пороги алертов (°C) — на стороне хоста
ALERT_TEMP_WARN=80
ALERT_TEMP_CRIT=85
ALERT_TEMP_EMERG=90

# Методы оповещений
ALERT_WALL=1        # wall-сообщение при CRITICAL+
ALERT_SYSLOG=1      # запись в syslog

# Ретенция CSV-логов (дни, 0 = навсегда)
LOG_RETENTION_DAYS=30

# Необязательно: MQTT
# MQTT_HOST="localhost"
# MQTT_PORT=1883
# MQTT_USER=""
# MQTT_PASS=""
# MQTT_PREFIX="psu_fan"

Перезагрузка конфига

sudo kill -HUP $(cat /var/run/psu-fan-ctl.pid)

Автодетект порта

Если SERIAL_PORT пуст, демон ищет первый доступный порт:

  1. /dev/ttyACM* (Arduino Nano)
  2. /dev/ttyUSB* (USB-serial адаптеры)

udev-правило создаёт стабильный симлинк /dev/psu-fan-arduino.


CLI-команды

Получение данных

Команда Описание
psu-fan-ctl status Текущие показания (таблица)
psu-fan-ctl watch [N] Мониторинг в реальном времени (обновление каждые N сек, по умолчанию 2)
psu-fan-ctl info Информация о железе и конфиге Arduino
psu-fan-ctl log Журнал событий Arduino (последние 32)
psu-fan-ctl history [N] Последние N записей CSV (по умолчанию 20)

Управление

Команда Описание
psu-fan-ctl set <1|2> <0-100> Ручная установка duty вентилятора
psu-fan-ctl auto <1|2> Автоматический режим
psu-fan-ctl config <1|2> min_temp,max_temp,min_d,max_d,hyst Параметры управления
psu-fan-ctl pid <1|2> Kp,Ki,Kd PID-коэффициенты
psu-fan-ctl mode <1|2> <0|1> Режим: 0=throttle, 1=PID
psu-fan-ctl test Самотест вентилятора (100% на 2 сек)
psu-fan-ctl reset Factory reset EEPROM

Системные

Команда Описание
psu-fan-ctl daemon Запуск демона (в фоне)
psu-fan-ctl rotate Ротация CSV-логов (при >1MB)
psu-fan-ctl install Установка systemd-сервиса
psu-fan-ctl uninstall Удаление systemd-сервиса

Примеры

# Посмотреть текущий статус
psu-fan-ctl status

# Живой мониторинг каждую секунду
psu-fan-ctl watch 1

# Ручная установка вентилятора на 60%
psu-fan-ctl set 1 60

# Переключить в PID-режим
psu-fan-ctl mode 1 1

# Настроить пороги: min=50°C, max=80°C, duty 30-100%, гистерезис 20
psu-fan-ctl config 1 500,800,30,100,20

# Настроить PID
psu-fan-ctl pid 1 5.0,0.1,2.0

# Запустить демон
sudo psu-fan-ctl daemon

Sysfs-интерфейс

Демон экспортирует данные в виртуальную файловую систему /tmp/psu-fan/:

Файл Содержимое Формат
temp1_input Температура канала 1 millidegrees (×1000)
temp2_input Температура канала 2 millidegrees (×1000)
control_temp_input Управляющая температура (max) millidegrees (×1000)
fan1_input ШИМ duty вентилятора % (0-100)
fan1_mode Режим управления auto / manual
fan1_speed_rpm Скорость вентилятора (оценка) RPM (duty × 30)
emergency_level Уровень аварии 0-5
status Текстовый статус normal/watch/warning/critical/emergency/fatal
uptime Время работы Arduino секунды
connected Состояние serial-соединения 0/1
timestamp Unix timestamp последнего обновления секунды

Пример чтения

# Температура канала 1 в °C
awk "BEGIN { printf \"%.1f\", $(cat /tmp/psu-fan/temp1_input) / 1000 }"

# Статус аварии
cat /tmp/psu-fan/status

Протокол serial

Все данные передаются в формате JSON, 115200 baud.

Arduino → Хост (каждые 2 сек)

{"t1":72.3,"t2":68.5,"tc":72.3,"f1":45,"f2":0,"m1":1,"m2":1,"e":0,"s1":1,"s2":1,"u":3600,"d":0,"b":5}
Поле Описание
t1 Температура канала 1 (°C)
t2 Температура канала 2 (°C)
tc Управляющая температура (max t1, t2)
f1, f2 Duty вентилятора каналов 1 и 2 (%)
m1, m2 Режим каналов (0=manual, 1=auto)
e Глобальный аварийный уровень (0-5)
s1, s2 Состояние датчиков (0=ошибка, 1=ОК)
u Время работы (секунды)
d Режим управления (0=throttle, 1=PID)
b Счётчик загрузок

Хост → Arduino (команды)

Команда отправляется одной строкой с \n.

Команда Формат Описание
F F<ch>:<duty> Ручной duty (ch=1|2, duty=0-100)
M M<ch>:<0|1> Режим: 0=manual, 1=auto
C C<ch>:mn,mx,mi,ma,hy Конфигурация: min_temp, max_temp, min_duty, max_duty, hyst
P P<ch>:kp,ki,kd PID-коэффициенты
X X<ch>:<0|1> Режим регулирования: 0=throttle, 1=PID
T T Самотест вентилятора
I I Информация о hardware
G G Принудительная выдача статуса
E E Выгрузка журнала событий
Z Z Factory reset EEPROM

Журнал событий (E)

{"events":[{"t":120,"e":0,"c":255,"v":5},{"t":125,"e":12,"c":0,"v":723}]}
Поле e (EventType) Значение
0 EVT_BOOT
1 EVT_CONFIG_CHANGE
2 EVT_EEPROM_LOAD
3 EVT_EEPROM_RESET
4 EVT_SENSOR_FAIL
5 EVT_SENSOR_RECOVER
6 EVT_FAN_FAIL
7 EVT_FAN_RECOVER
8 EVT_TEMP_WATCH
9 EVT_TEMP_WARN
10 EVT_TEMP_CRIT
11 EVT_TEMP_EMERG
12 EVT_THERMAL_RUNAWAY
13 EVT_EMERG_OVERRIDE
14 EVT_FAN_TEST
15 EVT_FAN_TEST_DONE
16 EVT_WDT_RESET

Система аварийных уровней

Уровень Значение Порог (от max_temp) При 75°C max Действие
NORMAL 0 < WATCH < 75°C Обычное управление
WATCH 1 +0 75°C Короткий писк
WARNING 2 +50 (5.0°C) 80°C Двойной писк, повтор каждые 10 сек
CRITICAL 3 +100 (10.0°C) 85°C Тройной восходящий писк, принудительный max duty, auto-override
EMERGENCY 4 +150 (15.0°C) 90°C Срочный писк, повтор каждые 3 сек, принудительно 100% duty
FATAL 5 +200 (20.0°C) 95°C Долгий сигнальный писк, повтор каждые 2 сек, принудительно 100% duty

Дополнительные условия:

  • Датчик неисправен → CRITICAL (вентилятор на максимум)
  • Thermal runaway (temp растёт >5.0°C/мин при duty ≥90%) → EMERGENCY
  • Fan failure (duty ≥80% >30 сек, но температура растёт >1.0°C/мин) → WARNING
  • Emergency override: при CRITICAL+ принудительно включается auto-режим

Звуки

Уровень Мелодия
Boot C5→E5→G5→C6 (восходящий арпеджио)
WATCH Одиночный мягкий писк
WARNING Двойной писк
CRITICAL Тройной восходящий (C5→E5→G5)
EMERGENCY Быстрый тревожный (C6→G5→C6→G5)
FATAL Долгий чередующийся (C6, пауза, C6, пауза, G5, пауза, G5)

PID-регулирование

При mode=1 (PID) управляющий сигнал вычисляется классическим PID-регулятором:

error = temperature - setpoint
setpoint = (min_temp + max_temp) / 2
output = Kp × error + Ki × ∫error + Kd × d(error)/dt

Дефолтные коэффициенты: Kp=5.0, Ki=0.1, Kd=2.0

Ограничения:

  • Anti-windup: интегральная составляющая ограничена ±1000
  • Выход ограничен 0-100%
  • При temperature < min_temp: duty ≥ min_duty (если duty > 0)

Throttle (по умолчанию)

При mode=0 (throttle) duty вычисляется линейной интерполяцией:

duty = min_duty + (temp - min_temp) × (max_duty - min_duty) / (max_temp - min_temp)

С гистерезисом для предотвращения дребезга:

  • Включение: при достижении min_temp + hysteresis
  • Выключение: при падении ниже min_temp - hysteresis/2

Сохранение конфигурации (EEPROM)

Настройки хранятся в EEPROM Arduino с защитой CRC16- checksum.

Поле Размер Описание
magic uint16 0xA55A (валидность данных)
version uint8 3 (версия формата)
boot_count uint16 Счётчик загрузок
ch1/ch2_* разные Параметры каналов (min_temp, max_temp, duty, hyst, PID)
crc uint16 CRC16 по всем полям

Валидация при загрузке: magic, version, CRC, sanity-check (max_temp > min_temp, max_duty > 0).

При любой ошибке EEPROM выполняется factory reset. Factory reset также доступен через команду Z.


MQTT

Опциональная интеграция с MQTT (требует mosquitto-clients).

Публикация происходит каждые 60 секунд в топик {MQTT_PREFIX}/status:

{"t1":72.3,"t2":68.5,"tc":72.3,"f1":45,"e":0,"u":3600,"mode":"auto"}

Настройка в конфиге:

MQTT_HOST="localhost"
MQTT_PORT=1883
MQTT_USER=""
MQTT_PASS=""
MQTT_PREFIX="psu_fan"

Структура файлов

psu-fan-controller/
├── psu-fan-controller.ino     # Прошивка Arduino Nano
├── psu-fan-ctl.sh             # Демон и CLI для Linux
├── psu-fan-controller.conf    # Конфигурация (устанавливается в /etc/)
└── psu-fan-ctl.service        # Systemd unit-файл

Файловая система при установке

/etc/psu-fan-controller.conf          # Конфигурация
/etc/systemd/system/psu-fan-ctl.service  # Systemd сервис
/etc/udev/rules.d/99-psu-fan.rules    # Правила udev
/usr/local/bin/psu-fan-ctl             # Скрипт

/var/lib/psu-fan-controller/
├── sensors.csv                # CSV-лог температур
├── alerts.log                 # Лог алертов
└── stats                      # Min/max статистика

/tmp/psu-fan/
├── temp1_input                # Температура канала 1
├── temp2_input                # Температура канала 2
├── control_temp_input         # Управляющая температура
├── fan1_input                 # Duty вентилятора
├── fan1_mode                  # Режим
├── fan1_speed_rpm             # Скорость (оценка)
├── emergency_level            # Уровень аварии
├── status                     # Текстовый статус
├── connected                  # Serial-соединение
├── uptime                     # Время работы
├── timestamp                  # Unix timestamp
├── command                    # Релей команд (CLI → daemon)
└── response                   # Релей ответов (daemon → CLI)

/var/run/
├── psu-fan-ctl.pid            # PID демона
└── psu-fan-ctl.lock           # Lock-файл (singleton)