С чего всё началось
Всё начиналось как чисто софтверная затея. Вокруг все говорят про агентов, вайбкодинг и конец эпохи программирования кожаными, а я, как говорится, «не читал, но осуждаю». Поэтому идея была простая: научиться вайбкодить — спроектировать и написать сервис, особо не утруждаясь.
В целом это был уже не первый раз, когда я что-то разрабатывал с помощью ИИ. Но предыдущие попытки организовать совместную разработку с коллегой через Git заканчивались на эмоциях: «О, это работает!», «О, круто!», «Ну всё, галочку поставил», «я у мамы программист», «я у мамы дизайнер» и так далее.
А чтобы я этим заболел и думал над функциями, дизайном, SEO, архитектурой, безопасностью и всем вот этим денно и нощно — такое со мной приключилось впервые. Ощущения прикольные: в моменте чувствуешь, как умираешь.
Сама формула, как словить вайб, довольно-таки простая: нужно брать в разработку идею, которая тебе интересна и действительно близка. Не нужно делать очередной дневник, стопятисотый сервис для зарабатывания денег или змейку с шариками ради дохода с рекламы.
У этого вайба есть, конечно, и побочка: засыпал я в три-четыре утра, дописывая или переделывая ранее сделанное, притом что в семь вставать на работу. Семья и близкие начинают думать, что ты сошёл с ума. Попытки объяснить, почему так происходит, особого доверия у родственников не находят, но это же детали))) В моменте тебя от всего этого очень сильно прёт.
Итак, сама идея — автоматизация пасеки — родилась довольно просто: пчеловодством я занимаюсь ровно по тем же методикам, что деды, их деды и, наверное, деды тех дедов… А у нас же XXI век! Роботы где-то не в России уже, наверное, качают мёд на пасеках! Моя пасека — двенадцать ульев на шести сотках в подмосковном СНТ — верх консерватизма.
Изначально сервисом я выбрал журнал пасеки. И довольно быстро понял, что журнал сам по себе неинтересен почти никому, включая меня самого. Записи об осмотрах — это оцифрованная тетрадь, не более. А вот чего я не нашёл на просторах рунета — так это сервиса, который берёт вес улья с датчиков и рисует по нему графики.
Основная проблема тут, конечно же, весы. Которых у меня не было. Готовые пасечные стоят слишком дорого — в интернете предлагают от 9 до 37 тысяч рублей за один улей. Зато схем для самостоятельной сборки на тензодатчиках хватает с избытком.
Так софтверный проект «просто покодить» привёл меня к паяльнику, флюсу, одному сожжённому стабилизатору и конфликту с ИИ. От идеи до работающего сервиса с железом ушло около двух месяцев — ровно две месячные подписки на ИИ-ассистента, примерно 200 долларов.
Дальше — честный build-log: что купил, как собирал мост из четырёх датчиков, чем тарировал и как всё это работает.

Зачем весы под ульем
Классический осмотр — срез в один момент времени. Открыл, посмотрел рамки, прикинул запасы, закрыл. Что было ночью или пока ты на работе — неизвестно.
Что читается по весу:
Дневной привес — сила взятка прямо сейчас и конкретно с этого места.
Ночная убыль — пчёлы выпаривают воду из нектара, мёд доходит до кондиции.
Резкий провал днём — семья отпустила рой. Если приходит уведомление — есть шанс успеть.
Плавный минус в безвзяточье — семья проедает корм, пора думать о подкормке.
Полный разбор графика с сезонными данными будет отдельной статьёй. Сейчас — про железо.
Немного о моём опыте: пасека и IT
Пчёлами я занимаюсь пятнадцать лет. Начинал с трёх ульев, потом держал от пяти до десяти, сейчас двенадцать. За этот сезон получилось собрать 270 литров мёда — по весу около 390 килограммов. Пасека — это моё хобби, времени занимает немного, поскольку я пчеловод выходного дня: основные работы — весной (расширение и противороевые методы) и в августе (откачка).
Основная моя работа — в IT: технический руководитель с функциями архитектора на комплексных проектах по автоматизации производств. Проекты крупные, часть предприятий — градообразующие.
Другими словами, автоматизация как класс задач для меня не новость. Новостью оказалось всё, что мельче цеха — паяльник за всю жизнь я держал считаное число раз.

Принцип: как взвесить улей
Тензодатчик — металлическая балка с наклеенными тензорезисторами. Под нагрузкой балка деформируется на микроны, сопротивление резисторов меняется, и с моста Уитстона снимается разница напряжений — микровольты. Их усиливает и оцифровывает 24-битный АЦП HX711, дальше ESP32 переводит отсчёты в килограммы по калибровочному коэффициенту.
Важная тонкость, на которой легко споткнуться: датчик меряет прогиб язычка, а не давление. Значит, конструкция должна быть такой, чтобы весь вес улья шёл строго через выступы датчиков — и никуда больше.
Конструкция: сэндвич из двух щитов
Весы — это два щита водостойкой фанеры (ФСФ) толщиной 18 мм, между ними четыре датчика по углам. Улей стоит на верхнем щите, электроника в коробке снизу. Под Дадан на 12 рамок хватает 45×45 см.
Датчик здесь работает опорой: каждый из четырёх тензодатчиков (YZC-161E, корпус 34×34 мм) целиком прикручен к нижнему щиту четырьмя винтами, под каждым я выбрал фрезером 5 мм под прогиб язычка. Язычок свободен с трёх сторон и выступает над корпусом, на его конце — бугорок; верхний щит опирается только на эти четыре бугорка и корпусов датчиков не касается. Язычок прогибается под весом верхнего щита, на котором стоит улей, — это и есть измерение.
Зазор в 5 мм под язычком нужен дважды: как место для прогиба и как защита от перегруза — при перевесе язычок ляжет на нижний щит, а не сломается.
Проверка механики до всякой электроники простая: надавите на верхний щит рукой. Он должен чуть проседать и возвращаться. Стоит колом — нет зазора или пережат крепёж.


Снизу по центру — распаячная коробка IP55 85×85×40 мм: в ней HX711, ESP32, плата зарядки TP4056, стабилизатор и аккумулятор. Провода датчиков заводятся через отверстие по центру щита, платы внутри распаячной коробки фиксируются термоклеем. Коробка живёт под ульем. Забегая вперёд: конструкция не слишком удачная, обслуживать её — «одно удовольствие». Лучше размещать коробку ближе к краю, на боковой стенке.

Мост Уитстона: главные грабли
Четыре датчика соединяются в мост и дают четыре вывода на HX711: E+, E−, A+, A−. От каждого угла средний (сигнальный) провод идёт на свой контакт платы, а крайние провода соседних углов соединяются кольцом по периметру.
Раскладка цветов у разных производителей разная — паять лучше по картинке из своего комплекта, а не по первой попавшейся из интернета (рекомендация от ИИ, которую получил ещё при пайке))))
Каждый датчик стоит проверить мультиметром до пайки. У живого две пары проводов покажут одинаковое сопротивление (скажем, по 1000 Ом), а третья — их сумму (2000 Ом). Провод, входящий в обе «одинаковые» пары, — средний, сигнальный.
Что видите | Что это значит |
|---|---|
~1k / ~1k / ~2k | датчик и провода живые |
«1» (обрыв) на любой паре | оторван провод, перелом жилы или лак не зачищен |
0 на любой паре | короткое замыкание, датчик под замену |
Когда приходят заказанные на маркетплейсе детали, нет никакого желания собирать сразу как надо: хочется быстро, всё и сразу, чтобы заработало и был виден результат — а переделаем потом, уже на века. Руководствуясь этим, я дошёл до того, что ИИ-агент на меня начал кричать — писал капслоком, что я его обманул и время потрачено впустую)))))
Ключевой момент, где я допустил ошибку, — зачем паять, когда можно собрать всё на скрутках, проверить, что работает, а потом уже сделать основательно.
Собрал мост на скрутках — на вид всё было идеально.
Мост не собирался. В мониторе порта Arduino IDE raw стоял ровным нулём — плата не видела датчиков вообще. Мультиметр честно показывал обрыв.
При дебаге агент несколько раз спросил, всё ли я пропаял. Я отвечал: да, да. А потом признался про скрутки — ну тут и началось: я обманщик, время потеряно зря.
Причина — прозрачный лак на проводах тензодатчиков. Выглядит как голый металл, а по факту это изоляция. Скрутка её не пробивает.
Побочный эффект: ещё несколько дней каждый разбор проблемы начинался с вопроса агента «ты точно припаял, а не скрутил снова?». Наверное, я это заслужил))))
Правильный способ: снять изоляцию, флюс, греть паяльником, пока олово не растечётся по меди — лак при этом выгорает, — и только потом соединять в узел. Каждое соединение — пайка плюс термоусадка. Временных скруток не бывает.

HX711 → ESP32
Четыре провода:
HX711 | ESP32 DevKit V1 |
|---|---|
VCC | 3V3 |
GND | GND |
DT (DOUT) | D19 (GPIO19) |
SCK | D18 (GPIO18) |
Питание HX711 — строго от 3V3, не от 5V/VIN. При пятивольтовом питании линия DT подаёт 5 В на вход ESP32 и может его повредить. Ещё момент: в чужих инструкциях часто фигурируют пины 16/17, которых на гребёнке DevKit V1 просто нет.

Питание — как по совету ИИ я сжёг стабилизатор
Напрямую от аккумулятора плату питать нельзя: заряженный 18650 даёт 4,2 В, а максимум для ESP32 — 3,6 В. Между ними нужен стабилизатор.
Цепь получилась такая: 18650 → TP4056 (модуль заряда с защитой DW01) → MCP1700-3302E → пин 3V3 ESP32. Зарядка через USB-C разъём TP4056, отпаивать ничего не нужно.
Конденсаторы не опциональны: 1 мкФ между VIN и GND стабилизатора, 1 мкФ между VOUT и GND, и 470 мкФ на 16 В между 3V3 и GND возле ESP32 — последний гасит всплески тока при передаче по WiFi. Без него плата перезагружается ровно в момент отправки данных.
Дымная часть. У MCP1700 средняя ножка — не земля.
У большинства стабилизаторов в корпусе TO-92 средняя ножка как раз GND, и это знание, вероятно, стало общим местом и для ИИ-моделей. У MCP1700 распиновка другая: плоской стороной с надписью к себе, ножками вниз — 1 это GND, 2 это VIN, 3 это VOUT.
Я паял по инструкции, которую сгенерировала модель, и распиновку в ней принял на веру: спросил, какая ножка плюс, какая минус, получил ответ, припаял. Минус ушёл на среднюю. Чип задымился. Стабилизаторы взял с запасом: второй, уже по даташиту, завёлся сразу.
Отдельно — контроль заряда. Два резистора по 470 кОм делителем от BAT+, средняя точка на GPIO34, нижний конец на GND, и конденсатор 0,1 мкФ со средней точки на GND. Без конденсатора высокоомный делитель шумит на АЦП. Прошивка видит половину напряжения батареи и шлёт её в личный кабинет вместе с весом.
Из важного: пока плата питается через пин 3V3, USB в ESP32 втыкать нельзя — два источника столкнутся. Для перепрошивки провод от стабилизатора отключается.


Логика прошивки: спать, чтобы экономить заряд
Плата просыпается раз в 10 минут, меряет вес, отправляет и засыпает в deep sleep. После пробуждения ESP32 стартует заново, поэтому вся работа живёт в setup(), а loop() пуст.
Три решения, которые стоит подсветить:
WiFi поднимается параллельно с измерением. Соединение запускается первым делом, и пока оно устанавливается, снимаются отсчёты с датчиков. Эфирное время — самое дорогое по батарее, и так его получается срезать на пару секунд.
HX711 усыпляется явно. scale.power_down() перед сном: иначе модуль ест около 1,5 мА круглосуточно, что для одного 18650 существенно.
tare() не вызывается никогда. Это неочевидно, но принципиально: если обнулять весы при каждом пробуждении, вес улья станет нулём после первой же перезагрузки. Ноль зашит константой и снимается один раз.
Вместе с весом плата шлёт телеметрию: разброс отсчётов, RSSI, счётчик перезагрузок, число неудачных отправок и причину последнего сброса. По ней в кабинете сервиса видно, что происходит с железом, — например, BROWNOUT означает просадку питания, а не случайный глюк.
Прошивка целиком
Программировать не нужно: скетч вставляется в Arduino IDE как есть, меняются три строки в самом верху — имя WiFi-сети, пароль и токен из личного кабинета. Плата — DOIT ESP32 DEVKIT V1, библиотека HX711 by Bogdan Necula.
Скрытый текст
/* * onlybee.ai — весы улья: ESP32 DevKit v1 + HX711. * Плата просыпается раз в 10 минут, меряет вес, шлёт в сервис и засыпает. * После deep sleep ESP32 стартует заново, поэтому вся работа в setup(), loop() пуст. * Ноль зашит константой (tare() НЕ вызываем — иначе вес улья обнулится при пробуждении). * Токен привязан к улью в личном кабинете, номер улья в прошивке не нужен. * * Железо: DOIT ESP32 DEVKIT V1, HX711 (DT→GPIO19, SCK→GPIO18, VCC→3V3, GND→GND). */ #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <esp_system.h> #include <esp_sleep.h> #include <limits.h> #include "HX711.h" // ─────────── ЗАПОЛНИ ЭТИ 3 СТРОКИ ─────────── const char* WIFI_SSID = "МОЯ_СЕТЬ"; // имя WiFi-сети — только 2.4 ГГц! const char* WIFI_PASS = "ПАРОЛЬ_СЕТИ"; const char* PAT = "onbee_ВАШ_ТОКЕН"; // токен из мастера «Подключить весы» // ──────────────────────────────────────────── const char* API_URL = "https://onlybee.ai/api/sensor/readings"; // Калибровка — снимается на шаге 5 гайда, в финальной сборке: // OFFSET = значение raw при пустой платформе; CAL_FACTOR = units ÷ вес гири (кг). // До калибровки оставьте как есть — вес будет в «попугаях», это нормально. const long OFFSET = 0; const float CAL_FACTOR = 1.0; const uint64_t SLEEP_MINUTES = 10; // период отправки, минут const int HX_DT = 19, HX_SCK = 18, LED_PIN = 2; const int SAMPLES = 10; const long SPREAD_WARN = 5000; const uint32_t WIFI_TIMEOUT_MS = 20000; // не ждём сеть вечно — это батарея // Переменные в RTC-памяти: переживают deep sleep (обычные — нет) RTC_DATA_ATTR uint32_t bootCount = 0; RTC_DATA_ATTR uint32_t sendFails = 0; RTC_DATA_ATTR uint32_t totalUptimeSec = 0; // время с первого включения (сон + работа) HX711 scale; const char* resetReasonStr() { switch (esp_reset_reason()) { case ESP_RST_POWERON: return "POWERON"; case ESP_RST_SW: return "SW"; case ESP_RST_PANIC: return "PANIC"; case ESP_RST_INT_WDT: return "INT_WDT"; case ESP_RST_TASK_WDT: return "TASK_WDT"; case ESP_RST_DEEPSLEEP: return "DEEPSLEEP"; // норма при пробуждении case ESP_RST_BROWNOUT: return "BROWNOUT"; // просадка питания — тревога case ESP_RST_EXT: return "EXT"; default: return "OTHER"; } } // Заснуть. Дальше код НЕ идёт: после таймера плата стартует с setup(). void goSleep() { scale.power_down(); // HX711 в сон: иначе жрёт ~1.5 мА круглосуточно WiFi.disconnect(true); WiFi.mode(WIFI_OFF); totalUptimeSec += millis() / 1000 + SLEEP_MINUTES * 60; // копим «возраст» устройства Serial.printf("Сплю %llu мин\n", SLEEP_MINUTES); Serial.flush(); esp_sleep_enable_timer_wakeup(SLEEP_MINUTES * 60ULL * 1000000ULL); esp_deep_sleep_start(); } bool sendReading(float kg, long rawAvg, long spread) { WiFiClientSecure client; client.setInsecure(); client.setHandshakeTimeout(15); HTTPClient http; http.setConnectTimeout(10000); http.setTimeout(10000); if (!http.begin(client, API_URL)) return false; http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", String("Bearer ") + PAT); char body[256]; snprintf(body, sizeof(body), "{\"weightKg\":%.2f,\"rawAdc\":%ld,\"spreadRaw\":%ld,\"rssiDbm\":%d," "\"freeHeap\":%u,\"uptimeSec\":%lu,\"bootCount\":%lu,\"sendFails\":%lu," "\"resetReason\":\"%s\"}", kg, rawAvg, spread, WiFi.RSSI(), ESP.getFreeHeap(), totalUptimeSec + millis() / 1000, bootCount, sendFails, resetReasonStr()); int code = http.POST((uint8_t*)body, strlen(body)); if (code > 0) Serial.printf("POST -> %d %s\n", code, http.getString().c_str()); else Serial.printf("POST ERR %d: %s\n", code, http.errorToString(code).c_str()); http.end(); return code == 201; } void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); delay(100); bootCount++; Serial.printf("\n=== onlybee scale === пробуждение #%lu | reset=%s\n", bootCount, resetReasonStr()); // WiFi запускаем СРАЗУ и не ждём: пока он поднимается, читаем вес. // Так эфирное время (самое дорогое по батарее) короче на пару секунд. WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); scale.begin(HX_DT, HX_SCK); scale.set_scale(CAL_FACTOR); scale.set_offset(OFFSET); // ноль зашит — tare() НЕ вызываем! if (!scale.wait_ready_timeout(3000)) { Serial.println("HX711 не отвечает — проверь DT/SCK/питание"); sendFails++; goSleep(); } long mn = LONG_MAX, mx = LONG_MIN; double sum = 0; for (int i = 0; i < SAMPLES; i++) { long r = scale.read(); if (r < mn) mn = r; if (r > mx) mx = r; sum += r; } long rawAvg = (long)(sum / SAMPLES); long spread = mx - mn; float kg = (rawAvg - OFFSET) / CAL_FACTOR; Serial.printf("вес=%.2f кг | raw=%ld | spread=%ld\n", kg, rawAvg, spread); if (rawAvg == 0) Serial.println(" ! raw=0 — DT/SCK/питание HX711"); else if (rawAvg > 8000000 || rawAvg < -8000000) Serial.println(" ! рейл — мост открыт, ищи обрыв"); else if (spread > SPREAD_WARN) Serial.println(" ! большой разброс — контакт/качает улей"); // теперь дожидаемся сеть (она поднималась параллельно) uint32_t t0 = millis(); while (WiFi.status() != WL_CONNECTED && millis() - t0 < WIFI_TIMEOUT_MS) delay(200); if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi FAIL — пропускаю отправку, сплю дальше"); sendFails++; goSleep(); } Serial.printf("WiFi OK IP=%s RSSI=%d dBm\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); if (sendReading(kg, rawAvg, spread)) { sendFails = 0; digitalWrite(LED_PIN, HIGH); delay(60); digitalWrite(LED_PIN, LOW); } else { sendFails++; } goSleep(); } void loop() { } // пусто: между циклами плата спит, работа — в setup()
Калибровка гирей
Два числа превращают «попугаи» в килограммы.
OFFSET — значение raw на пустой платформе. Смотрится в мониторе порта, вписывается в прошивку.
CAL_FACTOR — масштаб. На платформу ставится груз известной массы, значение units делится на вес. Эталон нашёлся дома.

322608 ÷ 16 = 20163 — вот и весь коэффициент.
Проверка: пустая платформа около нуля, гиря — свой вес. Нажать RST на ESP32 с установленной гирей на платформе: должен снова показать вес гири, а не ноль. Если показывает ноль — где-то остался tare().
Погрешность в граммах для моих задач некритична — интересен не абсолютный вес улья, а его изменение за сутки, и там счёт идёт на сотни граммов и килограммы.
Одно соображение из практики: коэффициент получается тем достовернее, чем ближе эталон к рабочему весу. Гиря 16 кг для улья, который весит десятки килограммов, — пойдёт.
Диагностика: raw расскажет всё
Прошивка пишет в монитор Arduino IDE сырое значение с датчиков. По нему находится почти любая проблема:
Симптом | Причина | Что делать |
|---|---|---|
raw = 0 всегда | линия DT не читается | проверить DT→D19, SCK→D18, питание HX711 |
raw = 8388607 или −1 | мост разорван, обрыв E+ или E− | пары E+/E−/A+/A− должны давать сотни Ом |
raw есть, но не реагирует на нажатие | датчики не в мосте, перепутана схема | сверить со схемой из своего комплекта |
raw скачет тысячами в покое | холодная пайка | пошевелить провода: где прыгает — там пропаять |
плата перезагружается при WiFi | просадка питания | конденсатор 470 мкФ у ESP32 |
Всё работает, но графики опубликую позже))))
Весы собрал, проверил на пасеке — работают. Но без интернета в СНТ довольно грустно: прошивка умеет ровно одно — проснуться, снять вес, отправить по WiFi и уснуть. Сейчас решаю вопрос интернета на пасеке и надеюсь порадовать весёлыми графиками. Над логикой на ESP ещё поработаю, плюс есть идея передачи через GSM.
Как только WiFi на пасеке появится, опубликую результаты осенней жизни пчелиной семьи с точки зрения веса.
Солнечная панель. Целевая схема — уйти от ручной подзарядки весов совсем. Панель пока выбираю — подойдёт любая стандартная, совместимая с ESP32 по напряжению.
Спецификация и сколько это стоит

Компонент | Что брать | Кол-во | Цена, ₽ |
|---|---|---|---|
Тензодатчики + HX711 | комплект «4 × 50 кг, полумост + модуль HX711» | 1 компл. | 260 |
HX711 запасной | модуль АЦП 24 бит | 1 | 115 |
Контроллер | ESP32-WROOM DEVKIT V1, 30 pin | 1 | 540 |
Плата заряда | TP4056 + DW01, USB Type-C | 1 | 240 |
Стабилизатор | MCP1700-3302E, TO-92 | 1 | 120 |
Аккумулятор | 18650 Li-ion 3000 мА·ч | 1 | 180 |
Держатель АКБ | отсек 18650 с проводами | 1 | 80 |
Конденсаторы | набор керамики + электролит 470 мкФ 16 В | набор | 130 |
Резисторы | 470 кОм, набор | 2 шт | 80 |
Коробка | распаячная IP55, 85×85×40 (DKC 53785 и аналоги) | 1 | 120 |
Фанера ФСФ 18 мм | лист 1220×2440 | 1 | 1650 |
Итого | без расходников | ≈ 3 515 | |
Расходники | гермоввод, термоусадка, припой, флюс, термоклей | — | — |
Тензодатчики почти всегда продаются комплектом вместе с HX711, и это дешевле, чем собирать по частям. Отдельный HX711 имеет смысл брать только запасным — что, как показала история со скрутками, не лишнее.
Для сравнения — готовые пасечные весы
Решение | Цена, ₽ | Что даёт |
|---|---|---|
Scales4Bee | 8 900 | Bluetooth и приложение, 0,1–200 кг |
ApiMonitor Mobile 1.3 | 13 000 | GSM, данные на сервер каждые 25 минут |
ApiMonitor Mobile 1.3 DUO | 17 000 | то же на два улья |
Весы пасечные беспроводные | 23 500 | беспроводная передача |
Bees_V9 | 36 750 | GSM и веб-сервер |
Разброс — от 9 до 37 тысяч за один улей. Самоделка выходит ощутимо дешевле.
Куда данные едут дальше
Сырые граммы бесполезны сами по себе — их нужно превратить в выводы: «взяток начался», «пора качать», «возможен рой». Этот слой я делаю в Onlybee.ai. Сервис бесплатный и изначально проектировался под приём телеметрии с весов, но в процессе я увлёкся и добавил функционал, который, по моему мнению, пригодится как начинающим, так и опытным пчеловодам.
О других функциях сервиса расскажу в отдельных статьях. Если есть идеи по его развитию — пишите в комментарии и добавляйтесь в ТГ сообщество, где будем обсуждать цифровизацию пасек и разработку сервисов для пчеловодов.

