Умный ИБП для умного дома
Живу в новостройке и пока тут идут ремонты, изредка, но бывает, что отключается электричество. Чтобы умный дом продолжал работать (а вместе с ним и домашний интернет), купил себе мобильный UPS на AliExpress и немного его доработал.
У безымянного устройства (у меня 18 ваттная младшая модель) следующие характеристики:
Емкость: 10400 мАч
Входное напряжение: AC85–265В 50/60 Гц
Выход: USB 5В 2A
Выход: 5В 1A, 9В 1A, 12В 1A
Выход POE: 15В 0.6А, 24В 0.6А
Обзоры на это устройство можно найти в сети.
Итак, дано:
Роутер Archer AX53 (питание 12В)
Абонентский терминал SNR ONU-EPON-1G-mini (питание 12В)
Raspberry Pi4 (питание USB)
Именно эти устройства я запланировал подключить через этот ИБП.
Вся слаботочка у меня находится на антресольной полке и чтобы, например, перезагрузить RPi4 или роутер приходится ставить стремянку, неудобно.
Решено было доработать устройство: первое чтобы UPS сообщал о своем уровне заряда и статус подключения к сети 220В, второе это возможность перезагружать по внешнему сигналу подключенное к USB выходу устройство, в моем случае — Raspberry Pi4 с установленным на нем умным домом, если вдруг он зависнет.
В заначке у меня нашлось: wemos d1 mini — будущие мозги, Pololu LV — микро цифровая кнопка, hw-384 — dc dc преобразователь, DHT11 — датчик температуры и влажности.
Использовал комплектующие, которые уже были у меня в наличии, поэтому некоторые подключения могут быть не совсем верными (без подтягивающих резисторов и фильтрующих конденсаторов). Возможно можно было использовать другие датчики и реле для отключения USB, вместо DCDC преобразователя использовать простой USB разъем. Но имеем, что имеем.
Спасибо нашим китайским друзьям, которые оставили нераспаенный разъем со всеми нужными сигналами от UPS (снизу вверх): Земля, питание 3.3 В, D25 — сигнал со светодиода 25%; W50, C75, 100 — сигнал со светодиодов 50%, 75%, 100% соответственно. R — красный светодиод, сигнал об отсутствии сети 220В, G — зеленый светодиод, сигнал о наличии 220В.
Первым делом выкидываем плату PoE-инжектора (она мне все равно не нужна, нет таких устройств), на его место отлично встает wemos d1:
Пропилил отверстие для будущего нового управляемого USB разъема:
потом залил все это термоклеем
Припаял сигнальные провода к основной плате:
подальше от высоковольтной части
И все вместе в сборке:
Принципиальная схема проапгреженного устройства:
Схема очень простая: Wemos питается от главной платы напряжением 3.3В, инвертированные сигналы 25%-100% и Red передаются на вход микрокомпьютера и считываются с определённой периодичностью. Питанием 8В (раньше туда подключался инжектор PoE) поступает на вход цифрового микровыключателя Pololu LV и через управляющий сигнал Cntrl включает или выключает свои мосфеты, соответственно подавая 8В на вход DC-DC преобразователя hw-384 (на выходе USB получаем 5В). Ну и еще подключен цифровой датчик температуры и влажности DHT11 (на всякий пожарный), у которого, для уменьшения толщины, я снял корпус и приклеил поверх аккумуляторов.
При обращении по IP адресу устройства можно посмотреть его состояние и вручную «перезагрузить» подключенное к новому USB разъему устройство. Так же эти данные рассылаются по протоколу MQTT в умный дом.
Также устройством можно управлять через телеграм бот.
Полный код для управления UPS
#include
#include
#include
#include
#include
#include
const char* ssid = "ВАШАСЕТЬ"; // Имя вашей Wi-Fi сети
const char* password = "ПАРОЛЬWIFI"; // Пароль для вашей Wi-Fi сети
const char* mqttServer = "IP"; // IP-адрес MQTT-сервера
const char* mqttUsername = "mqtt"; // Логин MQTT-сервера
const char* mqttPassword = "mqtt"; // Пароль MQTT-сервера
const char* mqttClientID = "ups"; // Идентификатор клиента MQTT
const char* mqttTopic = "battery/charge"; // Топик MQTT для публикации данных о батарее
const char* mqttTopicH = "battery/health"; // Топик MQTT для публикации данных о температуре
const int mqttPort = 1883; // Порт MQTT-сервера
const char* botToken = "TOKEN"; // Токен для телеграм бота
const char* chatId = "CHATID"; // чат бота
const unsigned long BOT_MTBS = 1000; // интервал обработки сообщений от бота
unsigned long botLasttime;
#define DHTPIN D4 // Пин, к которому подключен датчик DHT11
#define DHTTYPE DHT11 // Тип датчика DHT
const int batteryPin1 = D1; // Пин входа для первого инвертированного сигнала 25%
const int batteryPin2 = D2; // Пин входа для второго инвертированного сигнала 50%
const int batteryPin3 = D5; // Пин входа для третьего инвертированного сигнала 70%
const int batteryPin4 = D6; // Пин входа для четвертого инвертированного сигнала 100%
const int noPowerPin = D7; // Пин входа для сигнала об отсутствии напряжения 220В
const int outputPin = D8; // Пин вывода для отправки сигнала отключения линии USB
int battery=0;
bool noPower=false;
unsigned int unsuccessfullAttempts = 0;
float humidity;
float temperature;
const int maxAttempt = 25;
const unsigned long MQTT_SEND_INTERVAL = 8000;
unsigned long previousMillis = 0;
X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure secureClient;
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);
ESP8266WebServer server(80);
UniversalTelegramBot bot(botToken, secureClient);
// перезагрузка устройства (прерывание питания USB) через Pololu Switch LV
void restartUSBdevice() {
String html = "";
html += "Идет перезагрузка USB устройства...
";
bot.sendMessage(chatId, "Идет перезагрузка устройства подключенного к USB", "");
digitalWrite(outputPin, LOW);
delay(2000);
digitalWrite(outputPin, HIGH);
server.sendHeader("Location", String("/"), true);
server.send( 302, "text/html", html);
}
// обработка сообщений от Телеграм бота
void handleNewMessages(int numNewMessages)
{
for (int i = 0; i < numNewMessages; i++){
String chat_id = bot.messages[i].chat_id;
String text = bot.messages[i].text;
if (bot.messages[i].type == "callback_query"){
text = bot.messages[i].text;
}
if (text == "/health") {
String text = "Состояние UPS:\n";
if (noPower) text +="Сеть 220 отсутствует\n";
else text +="Сеть 220 подключена\n";
text +="Уровень заряда - "+String(battery)+"%\n";
text +="Температура - "+String(temperature)+"\n";
text +="Влажность - "+String(humidity)+"%";
bot.sendMessage(chat_id, text, "Markdown");
}
if (text == "/rebootUSB"){
restartUSBdevice();
}
}
}
// Функция обработчика HTTP запроса
void handleRoot() {
String html = "Умный UPS ";
html += "Уровень заряда:";
html += "
" + String(battery) + "%
";
if (noPower){
html += "Нет сети 220В
";
}
if (unsuccessfullAttempts>0)
html += "Попытка " + String(unsuccessfullAttempts) + " соединиться с MQTT
";
else
html += "Соединение с MQTT OK
";
html += "Температура: " + String(temperature) + "
";
html += "Влажность: " + String(humidity) + "
";
html +="";
html += "";
server.send(200, "text/html", html);
}
void setup() {
pinMode(batteryPin1, INPUT); // Инвертированный вход
pinMode(batteryPin2, INPUT); // Инвертированный вход
pinMode(batteryPin3, INPUT); // Инвертированный вход
pinMode(batteryPin4, INPUT); // Инвертированный вход
pinMode(noPowerPin, INPUT); // Инвертированный вход
pinMode(outputPin, OUTPUT); // Установка пина вывода как выход
digitalWrite(outputPin, HIGH); // Включаем питание на USB
Serial.begin(115200);
delay(10);
WiFi.begin(ssid, password); // Подключение к Wi-Fi сети
secureClient.setTrustAnchors(&cert);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("Connected to WiFi!");
Serial.print("Retrieving time: ");
configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP
time_t now = time(nullptr);
while (now < 24 * 3600)
{
Serial.print(".");
delay(100);
now = time(nullptr);
}
Serial.println(now);
server.on("/", handleRoot);
server.on("/restart", restartUSBdevice);
server.begin();
client.setServer(mqttServer, mqttPort);
bot.sendMessage(chatId, "Умный UPS запущен", "");
}
void loop() {
client.loop();
server.handleClient();
unsigned long currentMillis = millis();
// обработка вх сообщений от бота
if (currentMillis - botLasttime > BOT_MTBS) {
botLasttime = currentMillis;
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
while (numNewMessages){
Serial.println("got response");
handleNewMessages(numNewMessages);
numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}
}
// основная обработка
if (currentMillis - previousMillis > MQTT_SEND_INTERVAL) {
previousMillis = currentMillis;
if (!client.connected()) {
// Переподключение к MQTT-серверу при разрыве соединения
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(mqttClientID, mqttUsername, mqttPassword)) {
Serial.println("connected");
unsuccessfullAttempts=0;
} else {
unsuccessfullAttempts++;
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
//_______________________________________________________________________
//_____________________проверка уровня заряда UPS________________________
//_______________________________________________________________________
bool charge25 = !digitalRead(batteryPin1);
bool charge50 = !digitalRead(batteryPin2);
bool charge70 = !digitalRead(batteryPin3);
bool charge100 = !digitalRead(batteryPin4);
noPower = !digitalRead(noPowerPin);
char payload[50];
if (charge100) {
battery = 100;
} else if (charge70) {
battery = 70;
} else if (charge50) {
battery = 50;
} else if (charge25) {
battery = 25;
}
snprintf(payload, sizeof(payload), "{\"noPower\":\"%s\",\"battery\":%d}", noPower ? "true" : "false", battery); // Форматирование данных в формат JSON
Serial.println("Publishing data to MQTT...");
client.publish(mqttTopic, payload); // Отправка данных на MQTT-сервер
Serial.println("Data published to MQTT");
//_____________________________________________________
//________________датчик температуры___________________
//_____________________________________________________
humidity = dht.readHumidity(); // Чтение влажности
temperature = dht.readTemperature(); // Чтение температуры по Цельсию
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
}
Serial.println("Publishing data to MQTT...");
snprintf(payload, sizeof(payload), "{\"humidity\":%.2f,\"temperature\":%.2f}", humidity, temperature); // Форматирование данных в формат JSON
client.publish(mqttTopicH, payload); // Отправка данных на MQTT-сервер
Serial.println("Data published to MQTT");
if (unsuccessfullAttempts > maxAttempt){
String keyboardJson = "[[{ \"text\" : \"Перезагрузить USB устройство\", \"callback_data\" : \"/rebootUSB\" }]]";
bot.sendMessageWithInlineKeyboard(chatId, "Похоже, что HA не отвечает", "", keyboardJson);
}
}
}