Привет, я собрал небольшой бортовой компьютер для авто, который умеет показывать температуру, время с момента включения и раздавать «Free Wi-Fi». В этой статье приведён код, список компонентов и всё остальное, чтобы собрать такой же. Назвал я его Kruk — от беларуского слова «Крук» (рус. «Крюк»).

Вот видео его работы на YouTube. Здесь его страница на GitHub.

Если вы уже посмотрели видео, то понимаете суть функции «Free Wi-Fi». Как только к Wi-Fi подключаются, система сразу переводит пользователя на captive portal, а сам компьютер начинает показывать, помимо температуры и времени, ещё два дополнительных окна с информацией о количестве пользователей, подключившихся к Wi-Fi, и о количестве пользователей, которые до сих пор к нему подключены (online). В 20-й строке кода можно заменить веб-страницу на свою или кастомизировать вариант по умолчанию. Я сделал два варианта таких веб-страниц — один для Беларуси и второй для России. По умолчанию используется беларуский вариант, а российский находится на GitHub в папке ru. Чтобы его применить, просто замените код веб-страницы.

Логотипы выдуманы GPT-8h и все совпадения с реальностью случайны :) [В реальности логотипы не размыты]
Логотипы выдуманы GPT-8h и все совпадения с реальностью случайны :) [В реальности логотипы не размыты]

Перед тем, как сделать Kruk публичным, я месяц тестировал его на своём автомобиле. Я говорил знакомым, что в городе теперь работает бесплатный Wi-Fi, и те, кто решил проверить, испытали массу «позитивных» эмоций ?. Некоторых приходилось останавливать, потому что они уже набирали потными пальцами номер службы технической и консультационной поддержки, который, кстати, можно изменить и на какой-нибудь другой ?.

Необходимые компоненты:

  1. ESP32C3 (WeActStudio ESP32-C3FH4)

  2. WeActStudio DC/DC DCM1 5V

  3. SSD1306 4 pin display

  4. DS18B20 Temperature Sensor

  5. Резистор 4.7K

  6. Провода 30AWG

Провода:

SSD 1306
SDA -> 8
SCL -> 9
VCC -> 3V3
GND -> G

DS18B20
Красный провод -> 3V3
Чёрный провод -> G
Жёлтый провод -> 10
Резистором соединяем концы красного и желтого проводов. Я паял резистор прямо на выводы платы. (3V3 и 10). Вместо жёлтого провода (DATA) может приехать датчик с другим цветом этого провода.

Питание
Запитывать плату будем через понижайку WeActStudio DC/DC DCM1 5V, плюсовой провод от VOUT понижайки паяем на вывод платы 5V, минусовой на G.

Идеально помещается в корпус аналоговых часов BMW E36
Идеально помещается в корпус аналоговых часов BMW E36
Код для Arduino IDE
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <DNSServer.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>

const byte DNS_PORT = 53;  // порт для DNS-запросов

AsyncWebServer server(80);
DNSServer dnsServer;

const char *ssid = "Free Wi-Fi";  // имя точки доступа (без пароля)

// Веб страница
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="ru-RU"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>BELTELECOM</title>
    <meta name="description" content="Блокировака доступа">
    <meta name="keywords" content="Black List">
    <style>

* {
    box-sizing: border-box;
}

body {
    font-family: Ubuntu,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
    background-color: rgb(255, 255, 255);
}

.navigation {
    background: rgba(212, 212, 212, 0.37);
    position: fixed;
  height: 70px;
  top: 0px;
  left: 0px;
  z-index: 15;
  width: 100%;
  transition: background 200ms ease 0s;
  display: flex;
  justify-content: center;
}

.safespace {
  height: 70px;
  width: 100%;
}

.mobilenav {
    background: rgba(212, 212, 212, 0.37);
    position: fixed;
  height: 70px;
  top: 0px;
  left: 0px;
  z-index: 15;
  width: 100%;
  transition: background 200ms ease 0s;
  display: none;
}

#logo {
    height: 26px;
    width: auto;
    animation-name: beltelecom;
    animation-duration: 5s;
    animation-iteration-count: 1;
    margin: 22px;
    cursor: pointer;
        }

.menubutton {
  display: inline-block;
  cursor: pointer;
  margin:auto 0%;
}

.menuspace {
    width: 35px;
}

.promo {
  max-width: 1260px;
  background-color: rgb(255, 255, 255);
  display: flex;
  margin: auto;
}

.promo > div {
  color: #000000;
  text-align: center;
  width: 50%;
}

@keyframes beltelecom {
   0% {opacity: 0.0;}
   100% {opacity: 1.0;}
   }

@font-face {
  font-family: Ubuntu;
}

@media only screen and (orientation: portrait) {
    .mobilenav {display:flex; justify-content: space-around;}
    .navigation {display:none;}
    .mobilemenu {display: flex; flex-direction: column;}
    .promo {flex-direction: column;}
    .promo > div {width: 100%; padding: 10px;}
}

@media only screen and (max-width: 900px) {
  .mobilenav {display:flex; justify-content: space-around;}
  .navigation {display:none;}
  .mobilemenu {display: flex; flex-direction: column;}
  .promo {flex-direction: column;}
  .promo > div {width: 100%; padding: 10px;}
}

</style>
    </head>

<!--
**************************
***Sabas Solutions 2025***
**************************
-->

<body>
    <div class="navigation">
        <svg id="logo" width="155" height="17" viewBox="0 0 155 17" fill="none" xmlns="http://www.w3.org/2000/svg" alt="Home" onclick="javascript:location.href='/';">
<path d="M5.06422 9.59808H7.96662C8.81051 9.59808 10.4809 9.71355 10.0486 11.6123C9.70562 13.0987 8.41635 13.3913 7.04025 13.3913H4.19255L5.06422 9.59808ZM6.40299 3.74119H14.4304L15.1241 0.672104H3.60391L0 16.456H7.07671C10.5052 16.456 13.0569 14.9236 13.7931 11.6991C14.9452 6.64446 10.566 6.53767 9.34185 6.53767H5.76052L6.40299 3.74119Z" fill="#00408E"/>
<path d="M28.095 6.93531L27.4326 9.85073H20.1892L19.3817 13.3913H27.8102L27.1079 16.4551H15.1848L18.7861 0.672104H30.5051L29.7993 3.74119H21.5791L20.8507 6.93531H28.095Z" fill="#00408E"/>
<path d="M28.5933 13.3913C31.7779 13.8436 32.5784 10.8639 33.2287 8.20201L35.0545 0.671234H46.0234L42.416 16.4551H38.9258L41.8343 3.74032H37.6591L36.2934 9.24472C35.5337 12.2852 33.9067 17.3876 27.8953 16.4551L28.5933 13.3913Z" fill="#00408E"/>
<path d="M47.2727 3.74119L47.9707 0.672104H60.4225L59.7227 3.74119H55.2549L52.3525 16.4551H48.8606L51.763 3.74119H47.2727Z" fill="#00408E"/>
<path d="M71.6179 6.93531L70.9555 9.85073H63.7112L62.9021 13.3913H71.3288L70.6325 16.4551H58.6982L62.3056 0.672104H74.022L73.3187 3.74119H65.1012L64.3685 6.93531H71.6179Z" fill="#00408E"/>
<path d="M72.1119 13.3913C75.293 13.8436 76.0979 10.8639 76.7525 8.20201L78.5748 0.671234H89.5394L85.9389 16.4551H82.4496L85.3494 3.74032H81.176L79.8146 9.24472C79.055 12.2852 77.4253 17.3876 71.4139 16.4551L72.1119 13.3913Z" fill="#00408E"/>
<path d="M102.111 6.93531L101.446 9.85073H94.2042L93.3968 13.3913H101.827L101.124 16.4551H89.1964L92.7977 0.672104H104.518L103.813 3.74119H95.5951L94.8701 6.93531H102.111Z" fill="#00408E"/>
<path d="M115.441 0.672104H119.553L111.957 7.88686L116.283 16.4551H112.01L108.521 8.73683H108.473L106.713 16.4551H103.221L106.823 0.672104H110.32L108.8 7.29821H108.844L115.441 0.672104Z" fill="#00408E"/>
<path d="M125.057 13.8436C123.109 13.8436 121.593 12.6958 122.536 8.56492C123.354 4.95668 125.07 3.29146 127.467 3.29146C131.047 3.29146 130.313 7.16451 129.989 8.56492C129.688 9.89153 128.611 13.8436 125.057 13.8436ZM124.363 16.904C129.458 16.904 132.666 13.3374 133.759 8.56579C134.848 3.78026 133.266 0.218033 128.166 0.218033C122.323 0.218033 119.601 4.8933 118.77 8.56579C117.925 12.24 118.514 16.904 124.363 16.904Z" fill="#00408E"/>
<path d="M140.629 5.17981H140.567L137.991 16.4551H134.501L138.1 0.672104H143.011L144.072 11.5142L150.089 0.672104H155L151.4 16.4551H147.91L150.478 5.17981H150.413L144.13 16.4551H141.761L140.629 5.17981Z" fill="#00408E"/>
</svg>
    </div>
    <div class="mobilenav">
        <svg id="logo" width="155" height="17" viewBox="0 0 155 17" fill="none" xmlns="http://www.w3.org/2000/svg" alt="Home" onclick="javascript:location.href='/';">
<path d="M5.06422 9.59808H7.96662C8.81051 9.59808 10.4809 9.71355 10.0486 11.6123C9.70562 13.0987 8.41635 13.3913 7.04025 13.3913H4.19255L5.06422 9.59808ZM6.40299 3.74119H14.4304L15.1241 0.672104H3.60391L0 16.456H7.07671C10.5052 16.456 13.0569 14.9236 13.7931 11.6991C14.9452 6.64446 10.566 6.53767 9.34185 6.53767H5.76052L6.40299 3.74119Z" fill="#00408E"/>
<path d="M28.095 6.93531L27.4326 9.85073H20.1892L19.3817 13.3913H27.8102L27.1079 16.4551H15.1848L18.7861 0.672104H30.5051L29.7993 3.74119H21.5791L20.8507 6.93531H28.095Z" fill="#00408E"/>
<path d="M28.5933 13.3913C31.7779 13.8436 32.5784 10.8639 33.2287 8.20201L35.0545 0.671234H46.0234L42.416 16.4551H38.9258L41.8343 3.74032H37.6591L36.2934 9.24472C35.5337 12.2852 33.9067 17.3876 27.8953 16.4551L28.5933 13.3913Z" fill="#00408E"/>
<path d="M47.2727 3.74119L47.9707 0.672104H60.4225L59.7227 3.74119H55.2549L52.3525 16.4551H48.8606L51.763 3.74119H47.2727Z" fill="#00408E"/>
<path d="M71.6179 6.93531L70.9555 9.85073H63.7112L62.9021 13.3913H71.3288L70.6325 16.4551H58.6982L62.3056 0.672104H74.022L73.3187 3.74119H65.1012L64.3685 6.93531H71.6179Z" fill="#00408E"/>
<path d="M72.1119 13.3913C75.293 13.8436 76.0979 10.8639 76.7525 8.20201L78.5748 0.671234H89.5394L85.9389 16.4551H82.4496L85.3494 3.74032H81.176L79.8146 9.24472C79.055 12.2852 77.4253 17.3876 71.4139 16.4551L72.1119 13.3913Z" fill="#00408E"/>
<path d="M102.111 6.93531L101.446 9.85073H94.2042L93.3968 13.3913H101.827L101.124 16.4551H89.1964L92.7977 0.672104H104.518L103.813 3.74119H95.5951L94.8701 6.93531H102.111Z" fill="#00408E"/>
<path d="M115.441 0.672104H119.553L111.957 7.88686L116.283 16.4551H112.01L108.521 8.73683H108.473L106.713 16.4551H103.221L106.823 0.672104H110.32L108.8 7.29821H108.844L115.441 0.672104Z" fill="#00408E"/>
<path d="M125.057 13.8436C123.109 13.8436 121.593 12.6958 122.536 8.56492C123.354 4.95668 125.07 3.29146 127.467 3.29146C131.047 3.29146 130.313 7.16451 129.989 8.56492C129.688 9.89153 128.611 13.8436 125.057 13.8436ZM124.363 16.904C129.458 16.904 132.666 13.3374 133.759 8.56579C134.848 3.78026 133.266 0.218033 128.166 0.218033C122.323 0.218033 119.601 4.8933 118.77 8.56579C117.925 12.24 118.514 16.904 124.363 16.904Z" fill="#00408E"/>
<path d="M140.629 5.17981H140.567L137.991 16.4551H134.501L138.1 0.672104H143.011L144.072 11.5142L150.089 0.672104H155L151.4 16.4551H147.91L150.478 5.17981H150.413L144.13 16.4551H141.761L140.629 5.17981Z" fill="#00408E"/>
</svg>
    </div>
    <div class="safespace"></div>
    <div class="promo">
    <div id="info" style="margin:auto;">
    <br><span><h2>Доступ заблокирован!</h2></span>
    <br><p>Наш ИИ фильтр обнаружил попытки установить соединение с ресурсами ЛГБТ и гомосексуальной направленности с вашего устройства. Постановлением Министерства Культуры Республики Беларусь от 19 марта 2024 г. №24 <b>запрещено</b> распространение информационной продукции ЛГБТ и гомосексуального характера.<br><b>Вы добавлены в реестр пользователей ЛГБТ ресурсов.</b> Если вы считаете, что произошла ошибка или у вас есть дополнительные вопросы, обратитесь в службу технической и консультационной поддержки по номеру 123.</p>

    </div>
</div>
</div>

</body>
</html>
)rawliteral";

// Параметры OLED-дисплея
#define SCREEN_WIDTH 128  // ширина дисплея в пикселях
#define SCREEN_HEIGHT 64  // высота дисплея в пикселях
#define OLED_RESET -1     // пин сброса (если используется общий, то -1)
#define SCREEN_ADDRESS 0x3C  // I2C-адрес дисплея

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Указываем пин для датчика температуры
#define ONE_WIRE_BUS 10

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Флаги для предотвращения повторного отображения предупреждающих сообщений
bool tempHighShown = false;
bool tempLowShown = false;
int victims = 0;
int victimsonline = 0;

// Логотип (bitmap). 
#define LOGO_HEIGHT 64
#define LOGO_WIDTH  128
#define GHOST_WIDTH  67
static const unsigned char PROGMEM logo_bmp[] = 
{
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x0c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x1c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 
	0x1f, 0xc0, 0x00, 0x3c, 0x00, 0x00, 0x1c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 
	0x3f, 0xf0, 0x00, 0x3c, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 
	0x7f, 0xf0, 0x00, 0x3c, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0xf8, 0x00, 
	0x78, 0xf8, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x7e, 0x00, 
	0x78, 0x78, 0xfe, 0x3f, 0xe0, 0xfe, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x1f, 0x00, 
	0x7c, 0x01, 0xff, 0x3f, 0xf1, 0xff, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x0f, 0x00, 
	0x7f, 0x81, 0xff, 0x3f, 0xf9, 0xff, 0x3c, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x1e, 0x00, 
	0x3f, 0xf3, 0xc7, 0xbe, 0x7b, 0xc7, 0xbc, 0x70, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x7c, 0x00, 
	0x1f, 0xf8, 0x1f, 0xbc, 0x38, 0xff, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0xfe, 0xf8, 0x00, 
	0x03, 0xf9, 0xff, 0xbc, 0x3d, 0xff, 0xbf, 0xf0, 0x00, 0x00, 0x00, 0x7e, 0x03, 0xff, 0xe0, 0x00, 
	0x00, 0x79, 0xff, 0xbc, 0x3b, 0xff, 0x8f, 0xf0, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0xff, 0x80, 0x00, 
	0x70, 0x7b, 0xc7, 0xbc, 0x7b, 0xc7, 0x80, 0xf0, 0x00, 0x00, 0x01, 0xf0, 0x3f, 0x87, 0x00, 0x00, 
	0x7c, 0xfb, 0xcf, 0xbe, 0xff, 0xcf, 0xb8, 0xf0, 0x00, 0x00, 0x03, 0xe0, 0xfc, 0x02, 0x00, 0x00, 
	0x7f, 0xfb, 0xff, 0xbf, 0xfb, 0xff, 0xbf, 0xf0, 0x00, 0x00, 0x07, 0x81, 0xf0, 0x01, 0xc0, 0x00, 
	0x3f, 0xf1, 0xff, 0xbf, 0xf1, 0xff, 0xbf, 0xf0, 0x00, 0x00, 0x0f, 0x03, 0xc0, 0x01, 0xf0, 0x00, 
	0x0f, 0xc0, 0xf3, 0x19, 0xc0, 0xf3, 0x0f, 0xc0, 0x00, 0x00, 0x1e, 0x07, 0x80, 0x01, 0xbc, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x0f, 0xff, 0xff, 0x9f, 0x00, 
	0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0x87, 0xe0, 
	0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 
	0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 
	0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 
	0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0xf8, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0xf0, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0xc0, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf8, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfe, 0x06, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc7, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xfc, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xfe, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf8, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static const unsigned char ghost_army [] PROGMEM = {
	0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 
	0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 
	0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x1e, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x18, 0x00, 0x00, 
	0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x0e, 0x00, 0x18, 
	0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x18, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x3e, 
	0x00, 0x18, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x7e, 0x3c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x7e, 0x7c, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7c, 0xe0, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x79, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x80, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 
	0x00, 0x1f, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x7c, 0x78, 0x00, 0xe0, 0x00, 0x00, 
	0x00, 0x38, 0x01, 0xf0, 0x38, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0xc0, 0x0c, 0x00, 0x60, 
	0x00, 0x00, 0x01, 0xc0, 0x3e, 0x00, 0x0c, 0x00, 0xe0, 0x00, 0x00, 0x03, 0x80, 0xf8, 0x00, 0x0c, 
	0x00, 0xe0, 0x00, 0x00, 0x03, 0x07, 0xe0, 0x00, 0x0c, 0x00, 0xc0, 0x00, 0x00, 0x06, 0x1f, 0x80, 
	0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xfc, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x0f, 
	0xf0, 0x03, 0x80, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0f, 0x80, 0x18, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x1f, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x30, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 
	0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc3, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 
	0x9f, 0xf0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x9c, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x78, 0xf0, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xe0, 0x18, 0x60, 0x00, 0x00, 
	0x00, 0x00, 0x03, 0xc0, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x70, 0x18, 0x70, 
	0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0xfc, 0x18, 0x38, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0xfc, 
	0x18, 0x1c, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x03, 0xf8, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x80, 
	0x03, 0xf0, 0x0c, 0x8c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0xe0, 0x04, 0xce, 0x00, 0x00, 0x00, 
	0x07, 0x00, 0x0f, 0xe0, 0x06, 0x66, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0f, 0xc0, 0x03, 0x3c, 0x00, 
	0x00, 0x00, 0x06, 0x00, 0x1f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x3f, 0xf8, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x3f, 0xf1, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x7f, 
	0xe7, 0xe0, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x00, 0xff, 0xe7, 0xe3, 0x30, 0x00, 0x00, 0x00, 0x0c, 
	0x00, 0xff, 0xc7, 0xe7, 0x33, 0x00, 0x00, 0x00, 0x0c, 0x01, 0xff, 0xc7, 0xef, 0x3f, 0x00, 0x00, 
	0x00, 0x0c, 0x01, 0xdf, 0x87, 0xff, 0x3e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x3f, 0x07, 0xff, 0x3e, 
	0x00, 0x00, 0x00, 0x0c, 0x00, 0x7e, 0x07, 0xff, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x7c, 0x07, 
	0x7f, 0x07, 0x80, 0x00, 0x00, 0x0c, 0x00, 0xf8, 0x06, 0x7f, 0x07, 0x80, 0x00, 0x00, 0x0c, 0x01, 
	0xff, 0x80, 0x7f, 0x07, 0xe0, 0x00, 0x00, 0x0c, 0x03, 0xff, 0x80, 0x7f, 0x07, 0xe0, 0x00, 0x00, 
	0x0c, 0x03, 0xff, 0x00, 0x7f, 0x00, 0xe0, 0x00, 0x00, 0x0c, 0x07, 0xfe, 0x00, 0x7f, 0x00, 0xe0
};

void drawlogo(void);
void showinfo(void);

void beltelecom(AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html);
}

// Считаем количество подключившихся
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
  switch (event) {
    case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
      victims++;
      victimsonline++;
      break;
      
    case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
      if (victimsonline > 0) victimsonline--;
      break;
      
    default:
      break;
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

   // Инициализация OLED-дисплея
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Останавливаем выполнение, если дисплей не инициализировался
  }
  
  display.clearDisplay();
  display.dim(true);

  // Показ логотипа при старте
  drawlogo();

  // Инициализация датчика температуры
  sensors.begin();

  // Небольшой эффект инверсии экрана
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  // Настройка ESP32 в режиме точки доступа (AP)
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid);  // открытая точка доступа
  
  // Получаем IP-адрес, который выдан точке доступа
  IPAddress apIP = WiFi.softAPIP();
  Serial.print("IP-адрес точки доступа: ");
  Serial.println(apIP);

  // Настройка DNS-сервера для Captive Portal:
  // Все DNS-запросы перенаправляются на IP-адрес точки доступа
  dnsServer.start(DNS_PORT, "*", apIP);

  // Настраиваем веб-сервер:
   server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });

  server.onNotFound(beltelecom);
  
  server.begin();
  
WiFi.onEvent(WiFiEvent);

}

void loop() {
  dnsServer.processNextRequest();  // обработка DNS-запросов
  showinfo();
}

void showinfo(void) {
  
// Чтение и отображение температуры
  sensors.requestTemperatures();
  float temperature = sensors.getTempCByIndex(0);
  
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(4);
  display.setCursor(0, 0);
  display.println(F("Temp"));
  display.println(temperature);
  display.display();
  delay(2000);

  // Предупреждение о высокой температуре
  if (temperature >= 30.00 && tempHighShown == false) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println(F("Are we in"));
    display.println(F("Muspelheim"));
    display.println(F("? It's too"));
    display.println(F("HOT! =("));
    display.display();
    delay(5000);
    tempHighShown = true;
  }
  
  // Сообщение о нормализации температуры, если температура опустилась ниже порогового значения
  if (temperature <= 26.00 && tempHighShown == true) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println(F("Now the"));
    display.println(F("Temp is OK"));
    display.println(F("=)"));
    display.display();
    delay(5000);
    tempHighShown = false;
  }
  
  // Предупреждение о низкой температуре в первые 180 секунд работы
  if (temperature <= 15.00 && tempLowShown == false && (millis() / 1000) <= 180) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println(F("Wunderwaffe must be"));
    display.println(F("warmed up."));
    display.display();
    delay(5000);
    tempLowShown = true;
  }

  // Отображаем время работы устройства
  unsigned long mseconds = millis();
  int minutes = (mseconds / 60000);
  
  display.clearDisplay();
  display.setTextSize(4);
  display.setCursor(0, 0);
  display.println(F("Time"));
  display.print(minutes);
  display.print("m");
  display.print((mseconds / 1000) % 60);
  display.print("s");
  display.display();

  delay(2000);

    if (victims > 0) {
    display.clearDisplay();
    display.drawBitmap(0, 0, ghost_army, GHOST_WIDTH, LOGO_HEIGHT, 1);
    display.display();
    delay(1000);

    display.clearDisplay();
    display.drawBitmap(0, 0, ghost_army, GHOST_WIDTH, LOGO_HEIGHT, 1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(85, 21);
    display.setTextSize(3);
    display.println(victims);
    display.setCursor(78, 54);
    display.setTextSize(1);
    display.println(F("Total"));
    display.display();
    delay(1000);

    display.clearDisplay();
    display.drawBitmap(0, 0, ghost_army, GHOST_WIDTH, LOGO_HEIGHT, 1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(85, 21);
    display.setTextSize(3);
    display.println(victimsonline);
    display.setCursor(78, 54);
    display.setTextSize(1);
    display.println(F("Online"));
    display.display();
    delay(1000);
  }
}

void drawlogo(void) {
  display.clearDisplay();
  display.drawBitmap(0, 0, logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(3000);
}

Код есть на GitHub, но на всякий случай продублировал его и здесь.

Arduino IDE

В Arduino IDE вам необходимо в первую очередь в менеджере плат установить пакет ESP32 от автора Espressif Systems.

Далее необходимо установить все библиотеки:
Adafruit_BusIO
Adafruit-GFX-Library
Adafruit_SSD1306
AsyncTCP
DallasTemperature
ESPAsyncWebServer
OneWire

При этом обязательно проверьте чтобы библиотеки AsyncTCP и ESPAsyncWebServer были от автора ESP32Async в Arduino IDE. При прошивке в Arduino IDE выбирайте плату ESP32C3 Dev Module.

Бууу
Бууу

Итого

Дисплей настроен на работу на пониженной яркости, что сделано для уменьшения выгорания OLED. У меня Kruk включается по кнопке, чтобы его можно было оставить включённым на 24 часа, например. За месяц пользования не выявлено багов, зависаний и прочих «фич». Датчик температуры работает достаточно точно, хотя имеется некоторая инерция из-за металлического наконечника.

Когда я начал это делать, я просто хотел создать устройство, которое будет показывать напряжение сети, время и температуру, но всё получилось немного иначе ?. В первый раз я работал на ESP8266, и датчик напряжения (делитель напряжения на резисторах) был подключён, и даже удалось его откалибровать так, чтобы он показывал достаточно точные значения. Однако он работал хорошо только при условии, что источник питания и источник замера напряжения были разными. Как только питание и датчик были подключены к одному источнику, из ESP8266 пошёл дым, и тогда я понял, что следует использовать изолированный с помощью гальванической развязки понижающий источник питания.

Продолжить разработку я решил на базе ESP32C3, потому что он был новее и компактнее; от датчика напряжения пришлось отказаться, так как я не нашёл подходящую изолированную понижающую схему, да и сам датчик уже не подходил, поскольку максимальное напряжение на аналоговом выводе ESP32C3 оказалось меньше, чем мог уменьшить делитель напряжения. Вместо часов я решил сделать секундомер — так удобнее, тем более что часы у меня уже есть: на руке, в телефоне и даже в брелке от сигнализации. Кстати, пишите в комментариях, если узнали логотип из окошка счётчика пользователей — посмотрим, сколько нас здесь любителей истории. У меня всё.

Комментарии (2)


  1. Carbonium
    22.06.2025 16:31

    Это потрясающе! Даёшь линейку продуктов)


    1. assdestr0yer Автор
      22.06.2025 16:31

      Спасибо) Насчёт линейки не знаю, но ещё один интересный продукт в обозримом будущем будет)