Что-то часто стал заглядывать в профиль после каждой новой публикации. Так вот я и решил сделать табло, которое стояло бы на столе, и показывало место в рейтинге, карму, ну и само значение очков рейтинга.
Для желающих повторить подразумевается как возможность сборки из модулей, так и нормальная железка. Но устройство в общем очень даже универсальное, полностью совместимое с Arduino IDE, достаточно воткнуть USB и можно шить. Порог вхождения минимальный. А почему универсальное- только изменением кода можно парсить что угодно с любого сайта.
Раз уж устройство будет включено всегда и стоять на столе- оно показывает еще и время, температуру и влажность воздуха.
API хабра
... которого нет :(
Тут я задумался как получать данные. Поддержка сказала что АПИ задача не приоритетная, но где‑то в планах существует. Значит придется парсить по якорям. То‑есть ищем кусок уникальных данных перед нужным значением, и ориентируясь по нему извлекаем нужные.
Собственно код
Весь код написан в среде ArduinoIDE. Основная функция, конечно‑ получение значений. Голый код страницы профиля пользователя весит около 120кБ. Хранить его можно разве что в файловой системе, но особого смысла нет. Для класса Stream в Arduino IDE есть отличная функция find(), которой мы и воспользуемся.
Код функции парсинга с комментариями
if ((WiFi.status() == WL_CONNECTED)) { //Если есть подключение к Wifi
http.begin(client, SURL + USER + "/"); //Открываем HTTP соединение
delay(10);
int httpCode = http.GET(); //Производим GET запрос
delay(10);
Serial.print("httpCode");
Serial.println(httpCode);
if (httpCode==200) { //Если ответ 200
WiFiClient* stream = http.getStreamPtr(); //Пребразуем данные в поток Stream
if (stream->available()) { //Если поток доступен
//----------------карма
stream->find(R"rawliteral(karma__votes_positive">)rawliteral"); //Ищем якорь
for (int i = 0; i < 5; i++) { //Отступ от якоря
stream->read();
}
for (byte i = 0; i < 5; i++) { //Читаем нужное количество символов
KARMA[i] = stream->read();
}
//----------------рейтинг
stream->find(R"rawliteral(tm-rating__counter">)rawliteral");
for (byte i = 0; i < 7; i++) {
RATING[i] = stream->read();
}
//----------------позиция
stream->find("В рейтинге");
for (int i = 0; i < 118; i++) {
stream->read();
}
for (byte i = 0; i < 4; i++) {
RatingPos[i] = stream->read();
}
Serial.println(KARMA);
Serial.println(RATING);
Serial.println(RatingPos);
Serial.println("END");
}
delay(10);
Serial.println();
Serial.print("[HTTP] connection closed or file end.\n");
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
delay(10);
}
и
Весь код
Главный
String USER = "ENGIN33RRR"; //Имя пользователя
const char ssid[] = "Eng"; //SSID
const char password[] = "123456789h"; //Пароль от WiFi
const char* ntpServer1 = "pool.ntp.org"; //Первый сервер времени
const char* ntpServer2 = "time.nist.gov"; //Второй сервер времени
const long gmtOffset_sec = 21600; //Часовой пояс в секундах
String SURL = "https://habr.com/ru/users/"; //Начало адреса до страницы пользователя
//LОбъявляем Дисплей
#include <GxEPD2_BW.h>
#define USE_VSPI_FOR_EPD
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define MAX_DISPLAY_BUFFER_SIZE 65536ul
#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94_V2
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4));
//Шрифты
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include <Fonts/FreeMonoBold24pt7b.h>
#include <Fonts/FreeSerifBoldItalic18pt7b.h>
//Библиотеки Wifi, HTTP и времени
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
WiFiClientSecure client;
#include <HTTPClient.h>
HTTPClient http;
#include "time.h"
#include "sntp.h"
//Библиотека для датчика темпреатуры/влажности
#include <Adafruit_Sensor.h>
#include <DHT.h>
#define DHTPIN 27
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
//Переменные для хранения данных и фиксации изменений
String KARMA = "000";
String RATING = "000.0";
String RatingPos = "999";
String KARMA1;
String RATING1;
String RatingPos1;
float Temp;
float Hum;
float HumR;
float TempR;
char TimeDisp[9];
byte count;
bool flag;
long ms;
long ms1;
bool blink;
bool noWiFi;
byte WS;
void setup() {
xTaskCreatePinnedToCore(
Graph, //Функция потока
"Task2", //Название потока
16000, //Стек потока
NULL, //Параметры потока
1, //Приоритет потока
NULL, //Идентифкатор потока
0); //Ядро для выполнения потока
delay(500);
xTaskCreatePinnedToCore(
FileUpdate, //Функция потока
"Task1", //Название потока
10000, //Стек потока
NULL, //Параметры потока
2, //Приоритет потока
NULL, //Идентифкатор потока
1); //Ядро для выполнения потока
delay(500);
}
void FileUpdate(void* pvParameters) {
Serial.begin(115200); //Инициализация UART
//http.setReuse(true);
http.setTimeout(3000);
http.setReuse(true);
Connect(); //Подключаемся к WiFi
client.setInsecure(); //Игнорируем сертификаты HTTPS
for (;;) //Цикл потока
{
if (WiFi.status() == WL_CONNECTED) { //Если есть подключение
if (WiFi.RSSI() > -60) { //переводим уровень сигнала для значка
WS = 2;
} else if (WiFi.RSSI() > -70) {
WS = 1;
} else {
WS = 0;
}
noWiFi = 0;
findVAR(); //Функция поиска значений
} else {
Reconnect(); //Переподключить Wifi
noWiFi = 1;
}
if (!KARMA1.equals(KARMA) || !RATING1.equals(RATING) || !RatingPos1.equals(RatingPos)) { //Детектируем изменения в переменных
KARMA1 = KARMA;
RATING1 = RATING;
RatingPos1 = RatingPos;
flag = 1; //И поднимаем флаг
}
vTaskDelay(20000); //Пауза 20 секунд
}
}
void Graph(void* pvParameters) { //Поток отрисовки на дисплей
configTime(gmtOffset_sec, 0, ntpServer1, ntpServer2); //Инициализируем службу времени
dht.begin(); //Инициализируем датчик температуры
display.init(); //Инициализируем дисплей
display.setRotation(3);
display.clearScreen(); //Очистка экрана
display.setTextColor(GxEPD_BLACK);
display.fillScreen(GxEPD_WHITE);
Static(); // Отрисовка статического изображения
display.display(false); //Полный вывод на дисплей
for (;;) { //Цикл потока отрисовки на дисплей
updLocalTime(); //Обновляем время в переменной
if (millis() > ms + 1000) { //Обновляем показания датчика раз в секунду
ms = millis();
HumR = dht.readHumidity();
TempR = dht.readTemperature();
}
if (!isnan(HumR) || !isnan(TempR)) { //Если значения не NAN, копируем в перменные
Hum = HumR;
Temp = TempR;
}
if (millis() > ms1 + 1000) { //Моргалка для потери WiFi
ms1=millis();
blink = !blink;
}
Dynamic(); // Функция отрисовки меняющихся данных
}
}
void loop() { //Не используется
}
Работа с WiFi
void Connect(void) {
WiFi.mode(WIFI_STA);
delay(10);
WiFi.begin(ssid, password);
delay(10);
while (WiFi.status() != WL_CONNECTED && count < 15) {
count++;
delay(500);
}
delay(10);
}
void Reconnect(void) {
KARMA = "000";
RATING = "000.0";
RatingPos = "999";
WiFi.disconnect();
vTaskDelay(1000);
WiFi.begin(ssid, password);
count = 0;
while (WiFi.status() != WL_CONNECTED && count < 15 ) {
count++;
delay(500);
}
}
Графика
void Static() {
display.setFont(&FreeMonoBold12pt7b);
display.fillRect(0, 0, 296, 20, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
display.setCursor(10, 16);
display.print("HabraTab");
display.fillRect(10, 80, 276, 2, GxEPD_BLACK);
display.fillRect(15, 50, 73, 2, GxEPD_BLACK);
display.fillRect(103, 50, 90, 2, GxEPD_BLACK);
display.fillRect(208, 50, 73, 2, GxEPD_BLACK);
display.setTextColor(GxEPD_BLACK);
display.setCursor(18, 72);
display.print("Karma");
display.setCursor(115, 72);
display.print("Score");
display.setCursor(215, 72);
display.print("R No");
display.setFont(&FreeSerifBoldItalic18pt7b);
display.fillRect(15, 22, 100, 26, GxEPD_WHITE);
display.setCursor(15, 45);
display.print(KARMA.toInt());
display.fillRect(110, 22, 100, 26, GxEPD_WHITE);
display.setCursor(110, 45);
display.print(RATING.toFloat(), 1);
display.fillRect(220, 22, 85, 26, GxEPD_WHITE);
display.setCursor(220, 45);
display.print(RatingPos.toInt());
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(5, 100);
display.print("@");
display.print(USER);
display.setTextColor(GxEPD_BLACK);
display.fillRect(0, 108, 296, 20, GxEPD_BLACK);
}
void Dynamic() {
display.setFont(&FreeMonoBold12pt7b);
display.fillRect(145, 0, 150, 20, GxEPD_BLACK);
if (!noWiFi || blink) {
display.fillCircle(148, 9, 3, GxEPD_WHITE);
display.fillRect(156, 6, 2, 8, GxEPD_WHITE);
if (WS == 1) {
display.fillRect(162, 4, 2, 12, GxEPD_WHITE);
}
if (WS == 2) {
display.fillRect(162, 4, 2, 12, GxEPD_WHITE);
display.fillRect(168, 2, 2, 16, GxEPD_WHITE);
}
}
display.setTextColor(GxEPD_WHITE);
display.setCursor(175, 16);
display.print(TimeDisp);
vTaskDelay(1);
if (flag) {
display.setFont(&FreeSerifBoldItalic18pt7b);
display.setTextColor(GxEPD_BLACK);
display.fillRect(15, 22, 95, 26, GxEPD_WHITE);
display.setCursor(15, 45);
display.print(KARMA.toInt());
display.fillRect(110, 22, 95, 26, GxEPD_WHITE);
display.setCursor(110, 45);
display.print(RATING.toFloat(), 1);
display.fillRect(220, 22, 76, 26, GxEPD_WHITE);
display.setCursor(220, 45);
display.print(RatingPos.toInt());
flag = 0;
}
display.setFont(&FreeMonoBold12pt7b);
display.fillRect(0, 108, 200, 20, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
display.setCursor(10, 124);
vTaskDelay(1);
display.print("T");
display.print(Temp, 1);
display.setFont(&FreeMonoBold9pt7b);
display.setCursor(82, 118);
display.print("o");
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(120, 124);
display.print("H");
display.print(Hum, 1);
display.print("%");
vTaskDelay(10);
display.display(true);
vTaskDelay(10);
}
Время
void setTime (){
sntp_set_time_sync_notification_cb(timeavailable);
configTime(gmtOffset_sec, 0, ntpServer1, ntpServer2);
}
void updLocalTime()
{
struct tm timeinfo;
getLocalTime(&timeinfo);
strftime(TimeDisp,9, "%H:%M:%S", &timeinfo);
}
// Callback function (get's called when time adjusts via NTP)
void timeavailable(struct timeval *t)
{
}
Все остальное не так интересно‑ работа с дисплеем, датчиком температуры и влажности, время и подключение/пере подключение к WiFi. Ну разве что пара слов о FreeRTOS.
Так как время хочется видеть актуальное, вплоть до секунды, чтобы обращение к серверу ему не мешало‑ отрисовка на дисплей вынесена в отдельный поток. Так у нас все что касается дисплея исполняется на одном ядре, а все что касается сети‑ на другом.
Используемые библиотеки:
Библиотеки для работы с WiFi и HTTP уже есть в ядре ESP32 для Arduino IDE.
Железо
Собираем из модулей
Хотел было поставить TFT на 3.5 дюйма, но что-то лень рисовать новую плату, а из старых проектов особо ничего не подгонишь. Вспомнил что есть у меня E-Ink дисплеи, которые я еще нигде не использовал. А тут как раз- и светится по ночам не будет, и данные часто обновлять не обязательно. Выбор пал на небольшой дисплей диагональю 2.9 дюйма c разрешением 296х128 пикселей. Но версия с TFT конечно будет, и даже будет аватар показывать, но позже.
Сердцем конечно будет ESP32. Для 8266 данных многовато будет, работа со строками и файлами занимает много ресурсов, да и второе ядро выделенное только на работу с сетью уменьшает вероятность глюков. К тому-же на будущее планируется TFT дисплей и графика, а это требует ресурсов. Здесь подойдет любая отладочная плата с ESP32 Wroom на борту.
Подключение
Тут ничего особенного, у модуля 4 проводной SPI + 2 контакта. К ESP32 цепляется просто:
// BUSY -> 4, RST -> 16, DC -> 17, CS -> SS(5), CLK -> SCK(18), DIN -> MOSI(23), GND -> GND, 3.3V -> 3.3V
Датчик DHT22 подключается выходом на 27 ногу ESP32, естественно еще питание.
Железная версия
Для законченной версии была нарисована схема:
Тут стандартная для ESP32 обвязка и UART мост на CH340. По питанию стоит 1117 на 3.3В. Обвязка дисплея стандартная из даташита.
И плата:
Корпуса как такового не подразумевается, плата будет стоять на ножке из металла:
Ножка вырезана на лазере из нержавеющей стали толщиной 1мм. Метал достаточно тонкий и нижняя часть сгибается просто плоскогубцами. Чертеж в формате DXF будет в файлах.
На нижние грани рекомендую наклеить резину или вспененный уплотнитель на самоклеящейся основе.
Навесные сопли на фото выше указывают на мою забывчивость. Первоначально развел плату под CH340C, и уже при сборке оказалось что они у меня кончились, пришлось ставить CH340G и навешивать кварц. Куда делась крышка ESP32 даже не спрашивайте:)
Файлы
Файлы печатной платы и схемы в DipTrace + исходник в Arduino IDE:
https://github.com/ENGIN33RRR/HabraTab
Вопрос к читателям
Предлагаю пройти небольшой опрос, да и ваше мнение в комментариях будет интересно.
Нужен ли такой девайс хабровчанам? Какой функционал хотелось бы увидеть? Может какие уведомления, или данные с сайта для читателей, но не писателей? В общем предлагайте, а я буду пилить софт и выкладывать. Устройство прописалось на столе возле монитора и всегда подключено к компьютеру, так что залить новый код дело одной минуты.
После публикации буду особенно часто поглядывать на табло, показания которого полностью зависят от ваших плюсов ;)
Комментарии (46)
tormozedison
02.02.2023 14:02Девайс нужен, но он будет ещё лучше с возможностью заранее выбрать несколько хабов, например, diy, history и antikvariat, и как только там выходит статья, пищать и показывать заголовок.
Vsevo10d
02.02.2023 15:06Аж свою ардуиновую юность вспомнил 15-летней давности.
За девайс зачет, идея кита "собрать из модулей" - обеими руками за.
ENGIN33RRR Автор
02.02.2023 15:17+1Идея кита- заводская печатная плата и пакетик радиоэлементов. Собрать из модулей предлагается в смысле- отладочная плата плюс модуль дисплея.
Vsevo10d
02.02.2023 15:34Мне в принципе и так, и так нравится. Хотя пакетик рассыпухи лучше - хоть вспомню, как паять.
YMA
02.02.2023 15:18+3Для настоящего
кармадр..ценителя рейтингов стоит еще график кармы добавить и маленький динамик, чтобы при смене показателя девайс мог "Вау!" или "Фууу!" сказать.ENGIN33RRR Автор
02.02.2023 15:27+1На самом деле я работал со звуком на ESP32, правда ставил хороший I2S ЦАП в режиме 24 бита и 2х полосную акустику- с замахом на аудиофильность. Для "Вау" думаю хватит и встроенного ЦАПа.
vconst
02.02.2023 18:09+1DHT22 — зря конечно… Это модуль годится для отладки, проверить — что данные считываются и скетч их нормально обрабатывает, не жалко если сгорит. Как реальный датчик для проекта — его использовать смысла нет
Рекомендую BME680. Бош веников не вяжетJavian
02.02.2023 20:35BME живут от 1 до 3 лет на улице в зависимости от, предположительно, загрязненности воздуха — московские меняю раз в год-полтора, в Крыму — на год реже. Сначала «плывёт» влажность — сильно завышается, становится «дискретной»: 15-70-100%, позже отваливается температура (а от неё зависит всё остальное). Немного помогает перенос такого датчика «на работу» в помещение, но влажность дохнет навсегда.
habr.com/ru/post/525140/#comment_22252464vconst
03.02.2023 10:49+1BME живут от 1 до 3 лет на улице
У автора девайс — чисто комнатного типа. Причем DHT22 /11 вообще не уличного исполнения, в принципе, а бошевские датчики (не обязательно 680) есть в корпусе-капсуле. Точность DHT22 ± 2-3 градуса, у бошевских от 0,5 и до 0,25.
vconst
02.02.2023 18:15Сообществу вопрос, раз уж тут еспшники собрались.
Как оптимально запитать платку от 18650? Платку для зарядки акка найти не проблема, но они все выдают 5 вольт, как правило. Есть что-то, понижающее с 5 до 3, но доступное не с Али через месяц, а в условном чипедипе и через пару дней?ENGIN33RRR Автор
02.02.2023 18:20Оптимально сепик, но это собирать надо. Не совсем оптимально есть платки mini360, в любом ардуиномагазине.
vconst
02.02.2023 18:38А, не… Ошибся
Выход с контроллера лития 3,7, как с самого аккп (TP4056). Вот в чем засада
5 вольт чем угодно можно понизить, а вот платка, принимающая 3,7 и отдающая 3 — это уже не так просто оказалось ((
Хочется обойтись минимумом железок, чтобы не майстрячить трехслойный бутерброд только для того, чтобы взять от акк 3,7 и отдать в есп 3 ровно. Ардуинка у меня питается всего двумя платками размером чуть больше ногтя, потому что не такая разборчивая и берет на вход от 4 до 12 (даже до 20, если случайно перекрутил подстроечный резистор), как таким же набором запитать есп32?sbw
02.02.2023 22:37Можно включить кремниевый диод последовательно с батареей, падение напряжения на диоде 0.6 В, итого на выходе после диода будет 3.1 В. Потери примерно 16%. Интересно, сравнимо ли с потерями китайских импульсных преобразователей 5 В ->3 В ?
MaFrance351
02.02.2023 19:58Прекрасный девайс! Обязательно попробую собрать. Как раз от одного из старых проектов осталась плата на ESP32, как раз будет, куда применить. Только портировать для своего дисплея надо будет.
76popugaev
02.02.2023 20:59Для _реально_ зависимых, надо трекер прикрутить и кнопку, синтегрированной с браузером и по нажатии которой переходит на следующий пункт в трекере.
Плюс аларм на новые статьи из выбранных хабов или от пользователей на которых подписан.
А для _реально_реально_ зависимых, ещё бы кнопку которая добавляет в файервол правило которое блокирует хабр, на два часа.
smart_alex
02.02.2023 21:34+2Проверил — работает.
На мой взгляд привязываться к конкретному железу (дисплей, сенсор) не очень правильно, поскольку у пользователей может не быть такого дисплея или им не нужна температура на шильдике про Хабр. Поэтому первым делом выкинул из кода всё, что связано с дисплеем и сенсором. Теперь это можно прикрутить к любому дисплею.
Данные о хабро-значениях почему-то не всегда корректно обновляются, но это не так важно, с этим можно будет разобраться потом.
В целом, скетч — отличный пример для разбора начинающими и повышения своей квалификации в программировании.
Автор — молодец.
P.S.
Немного фантазии и на этом «движке» можно много чего интересного сделать.
MaFrance351
02.02.2023 22:00Сразу на ум приходит мониторинг данных сразу с нескольких сайтов и отображение всего этого на дисплее.
smart_alex
02.02.2023 22:09+1Не обязательно просто отображение на дисплее - так можно брать данные с сайтов в интернете для вашей "умной системы".
ENGIN33RRR Автор
02.02.2023 22:14Где то в функциях отрисовки, которые вы выбросили, были строчки типа:
display.print(KARMA.toInt());
Эта функция извлекает число и отсеивает мусор. Там дальше правда .toFloat().
Это простейшее решение по фильтрации, ибо значение может быть от 1 до 5 знаков, и в случае короткого значения парсер захватывает мусор вроде кусков HTML тегов. Например после кармы почему то присутствует знак переноса строки.
smart_alex
02.02.2023 22:20@ENGIN33RRR, вы плохо обо мне думаете :) когда я выбрасывал «лишний» код, то делал это достаточно квалифицированно и отделял «мух от котлет» — .toInt и прочее я оставил, заменив только вывод на дисплей выводом в Serial.
smart_alex
03.02.2023 11:07Экспериментирую далее... Возникло 2 вопроса:
1. Почему инициализация Serial находится в функции FileUpdate(), а не как обычно в setup()?
2. Почему в коде:
client.setInsecure(); // игнорируем сертификаты HTTPS Serial.println(F("FileUpdate1...")); for (;;) { // цикл потока Serial.println(F("FileUpdate2...")); if (WiFi.status() == WL_CONNECTED) {
cтрока «FileUpdate2...» выводится всегда, а строка «FileUpdate1...» только при первом вызове функции FileUpdate()?
ENGIN33RRR Автор
04.02.2023 10:51У нас тут Rtos, соответственно у каждого потока типа своего Setup, который вызывается один раз, а потом свой Loop, который For, который крутится в цикле. Serial я инициализирую внутри одного потока. Если использовать UART в двух потоках без мьютексов- будут глюки.
smart_alex
04.02.2023 10:58Да, пока ждал ответ, сам понял как это работает. Просто интуитивно ожидал немного другой логики работы.
Кстати, с получением значений всё в порядке - глюки были связаны с моим выведением в Serial.
smart_alex
04.02.2023 11:12Кстати, ещё вопрос: а как Хабр и прочие сервера отнесутся к постоянным периодическим запросам, особенно если они будут массовыми? Не сочтут за атаку и не будут блокировать?
AlexanderS
02.02.2023 21:48+1Что-то часто стал заглядывать в профиль после каждой новой публикации.
После пары десятков публикаций отпустит. А вот за влажность отдельное спасибо) Кстати, она что-то совсем низкая…ENGIN33RRR Автор
02.02.2023 22:00А какая должна быть влажность? Это большое рабочее помещение около 70 квадратов, к тому же теплый пол.
AlexanderS
02.02.2023 22:28+2По санитарным нормам 40-50%. 20% — это подсыхание слизистой, стягивание кожи, общее самочувствие хуже, сонливость, усталость. Опять же накопление статики в помещении. Поэтому с приходом зимы я достаю увлажнитель и несмотря на то, что в воздух «выливается» 5 л/сут у меня влажность в квартире выше 35% не поднимается. Однако и такое улучшение я чувствую.
76popugaev
02.02.2023 22:31несмотря на то, что в воздух «выливается» 5 л/сут у меня влажность в квартире выше 35% не поднимается
Если расположить увлажнитель достаточно близко, то вполне можно локально повысить до нормальных значений. Чтобы всё помещение увлажнить, тут надо постараться.AlexanderS
02.02.2023 22:49Верно говорите. Поэтому увлажнитель работает у диванов, так как большую часть времени в квартирах мы спим. Это, конечно, если не по удалёнке работать)
Поднять влажность я думаю мог бы и выше. Я специально выбирал модель с относительно большой производительностью, но минимальным уровнем шума на первой скорости вентилятора под ночной режим. На высокой скорости он дует гораздо сильнее, но шум от него никому не нужен даже днём, поэтому он на минимальной скорости весь сезон и отрабатывает.
RV3EFE
03.02.2023 09:49Класс. Респект за питание без микросхемы, но на россыпи! Хорошо когда люди видят такие решения.
ASPtr
03.02.2023 14:37+5Есть готовые модули ESP32 + e-ink. Искать на алиэкспресс: "LILYGO E-paper". Их несколько видов.
marimero
04.02.2023 05:35Хорошие начинания, но у меня на работе это могут не оценить чтение хабра, поэтому было бы неплохо иметь возможность, иметь на работе устройсов без отсылок к хабру, а имитирующее к примеру канал для связи с инопланетянами
Exosphere
После выхода статьи двое суток можно выводить рейтинг, просмотры и счётчик комментариев. Это всегда очень волнительно!