Практика внедрение ИИ в HR1
Шеф поручил применить ИИ для выявления
наименее эффективных сотрудников. По заключению ИИ минимальную эффективность показал сам шеф. В результате уволили айтишника, отвечавшего за ИИ
"Не бросайте учебу": топ-менеджер Google — о том, кто выиграет от ИИ
Пока одни ждут, что нейросети обнулят дипломы программистов — код и так пишет ИИ по описанию на обычном языке, — глава Google DeepMind Демис Хассабис говорит обратное. По его словам, техническое образование не теряет ценности, а дает фору: те, кто понимает, как все устроено внутри, будут использовать ИИ-инструменты в десять раз эффективнее остальных. Об этом он заявил на бизнес-конференции в Лондоне, видео выступления выложили на этой неделе.
Хассабис основал DeepMind в 2010-м — через четыре года лабораторию купила Google, — а в 2024-м получил Нобелевскую премию по химии за предсказание структуры белков. На вопрос, стоит ли сегодня идти в STEM (естественные науки, технологии, инженерию и математику), он отвечает однозначно.
"Вам совершенно точно нужно налегать на STEM и информатику", — сказал Хассабис. Программирование, по его логике, никуда не денется — оно поднимется на уровень выше. Сначала люди писали машинным кодом, потом на C, потом на Python. Следующим "языком программирования" может стать обычный английский: описываешь задачу словами, а код пишет модель.
Но именно поэтому база становится важнее, а не наоборот. "Нужно по-прежнему понимать, как проектируются системы и что такое хорошая инженерная практика, — говорит он. — Те, кто разбирается в глубокой технике, смогут использовать эти инструменты в десять раз эффективнее тех, у кого такого знания нет".
Отдельно Хассабис советует не списывать со счетов гуманитариев. По его словам, в мире, в который мы входим, особенно нужны философия и экономика — кому-то придется думать про этику и социальные последствия того, что строят инженеры.
И он не одинок. Джеффри Хинтон, которого зовут "крестным отцом ИИ", ранее говорил: должность просто крепкого среднего программиста скоро исчезнет, это ИИ уже умеет. Но диплом по информатике ценен далеко не только кодингом и останется востребованным надолго. Глава платежного сервиса Affirm Макс Левчин добавляет: без прочной базы не отличишь элегантный код от мусора.
Тренд у всех троих читается одинаково. ИИ не отменяет техническое образование — он превращает его в множитель: чем лучше понимаешь, что под капотом, тем больше выжимаешь из инструмента.
P.S. Поддержать меня можно подпиской на канал "сбежавшая нейросеть", где я рассказываю про ИИ с творческой стороны.
"Беспрецедентная нагрузка": китайский ИИ Kimi захлебнулся от собственного успеха
Китайский стартап Moonshot AI остановил набор новых платных подписчиков на свою нейросеть Kimi. Причина — не провал, а обратное: спрос на свежую модель K3 оказался таким, что серверов перестало хватать.
Как сообщает Reuters, за первые двое суток после запуска число запросов выросло примерно в шесть раз и подошло к пределу вычислительных кластеров компании. Сама Moonshot назвала это "беспрецедентной вычислительной нагрузкой".
Решение простое: новых подписчиков пока не берут, а всю доступную мощность отдают тем, кто уже платит, — их лимиты и скорость не пострадают. Набор обещают открыть, когда разгребут инфраструктуру.
K3 — тяжеловес даже по меркам топовых моделей: 2,8 триллиона параметров и открытые веса, то есть модель можно скачать и поднять у себя (после 27 июля). По ряду тестов на программирование она обходит конкурентов, и именно это, судя по всему, и вызвало наплыв — разработчики бросились пробовать.
За стремительным ростом стоят деньги. Выручка Moonshot в пересчете на год выросла со $100 до $300 млн всего за три месяца — с марта по июнь. В мае компанию оценили в $20 млрд, а сейчас она готовит выход на биржу в Гонконге — тот самый момент, когда акции сможет купить любой инвестор — с прицелом на оценку выше $30 млрд.
Пауза в наборе — обратная сторона этой гонки. Модель, которая привлекает столько пользователей, что под ними ложатся серверы, — ровно та витрина, которую компания хочет показать инвесторам перед листингом. Проблема с мощностями здесь выглядит не аварией, а доказательством спроса.
P.S. Поддержать меня можно подпиской на канал "сбежавшая нейросеть", где я рассказываю про ИИ с творческой стороны.
Ruby и встраиваемые системы
Казалось бы, какое отношение «хипстерские скрипты для веб» могут иметь к жестким реалиям встариваемых систем, со всей их низкоуровневой работой и ограниченными ресурсами?
Увы, но реальность в очередной раз оказалась куда интересней предубеждений, так и появилась на свет эта статья.
Картинка для привлечения внимания, была выложена на ЛОР.
Что это и зачем
Начну как обычно с цитаты:
mruby is the lightweight implementation of the Ruby language complying with part of the ISO standard. mruby can be linked and embedded within your application.
Словом, это такая особенная реализация языка Ruby, с упором на встраивание и встраиваемые системы — да да, тот самый «кровавый embedded», где царствует чистый C, ссылочная арифметика, malloc() и прочие кошмары и ужасы для современного разработчика.
И вдруг в этом царстве Аида появляетесь вы весь в белом и пишете что-то такое, высокоуровневое:
extend Yeah::DSL
set port: 3000
get '/hi/{name}' do |name|
"Привет #{name}"
end
ENV['SHELF_ENV'] = 'production'
puts "Запуск.."
__main__ [0]
И.. оно просто работает:
И даже вот так:
Круто?
Сколько там пудов соли нужно скушать, чтобы так просто работать с юникодом из чистого С, тем более в embedded среде?
Кстати вся эта «радость хипстера» еще и собирается в очень небольшой бинарник:
Внутри будет «все и сразу»:
MRuby, все используемые библиотеки и само приложение.
Хотя на ЛОРе заметили, что «640кб хватит на всех» это как‑то многовато будет, все же напомню что мы живем в мире копеечных 128Гб флешек и битва за каждый байт свободного места уже не так актуальна как 10 лет назад.
Вполне допускаю, что подобное приложение показывает веб‑интерфейс в вашем домашнем роутере, показывает меню в телевизоре или крутит рекламу в автобусе — словом находит применение в большинстве мест, где используются встраиваемые системы.
Поддерживаемые платформы
К сожалению не удалось найти одним списком все поддерживаемые MRuby платформы, поэтому ограничусь только конкретными найденными примерами.
Из самого интересного:
разработка для Sega Dreamcast и Nintendo Wii, для POS-терминалов, встраивание в iOS приложения.
Ну и собственно встраиваемые системы:
Внешний вид платы ESP32.
Тестовый проект
Поскольку «малинки» в очередной раз под рукой не оказалось, было решено реализовать тестовый проект на банальном x86 — была собрана вся цепочка разработки, включая фреймворки, был реализован «Hello world» в виде веб‑приложения, работающего на встроенном веб‑сервере.
Все манипуляции производились на неподдерживаемой никем и нигде FreeBSD, так что скорее всего описанных ниже проблем со сборкой в более обычном Linux не будет.
Для тестового проекта использовалось вот это «чудо»:
Yeah! is a DSL for quickly creating shelf applications in mruby with minimal effort
Если кратко, то это своеобразная попытка реализовать «мини‑Rails, работающий на мини‑Ruby». Вполне себе успешная, надо отметить.
А теперь самое важное:
Фреймворки для mruby представляют собой надстройку, которая в процессе собирает сам mruby и добавляет себя в собираемые бинарники.
Звучит сложно и выглядит страшно, но для embedded-среды является привычным делом.
Так что нам будет нужно получить бинарники mruby и mrbc с упакованным внутрь фреймворком yeah и всеми библиотеками, а затем использовать этот билд для сборки уже своего приложения.
Для сборки фреймворка Yeah! требуется внешний «большой» Ruby и rake, будут работать как 2.x так и 3.x версии.
Клонируем проект с фреймворком Yeah!:
git clone https://github.com/katzer/mruby-yeah.git
Поскольку по‑умолчанию собирается только компилятор mirbc, без интерактивной консоли (mirb) и интерпретатора (mruby), чего не хватит для нормальной разработки конечного приложения, добавляем в файл build_config.rb:
conf.gem :core => 'mruby-bin-mruby'
conf.gem :core => 'mruby-bin-mirb'
conf.gem :core => 'mruby-bin-mrbc'
И запускаем сборку:
rake compile
Эта команда автоматически скачает зависимые репозитории, в том числе нужную ветку самого mruby. На данной стадии у автора появлялись две ошибки.
Первая — про заголовочный файл mingw.h:
In file included from /opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/memory.c:34:
/opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/mman.h:15:10: fatal error: _mingw.h: No such file or directory
15 | #include <_mingw.h>
| ^~~~~~~~~~
compilation terminated.
rake aborted!
В файле mman.h есть вот такая строка:
/* All the headers include this file. */
#ifndef _MSC_VER
#include <_mingw.h>
#endif
Переменная _MSC_VER не задается при сборке на FreeBSD, поэтому срабатывает вариант по умолчанию — для Windows и MinGW. В качестве исправления, я просто закомментировал этот блок, не заморачиваясь дальнейшими изысканиями.
Вторая ошибка также достаточно банальна и происходит из-за разницы в реализации функции mmap:
/opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/mman.h:52:9: error: conflicting types for 'mmap'; have 'void *(void *, size_t, int, int, int, OffsetType)' {aka 'void *(void *, long unsigned int, int, int, int, unsigned int)'}
52 | void* mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off);
| ^~~~
In file included from /opt/work/tmp/mruby-yeah/mruby/build/repos/host/mruby-r3/r3/src/memory.c:27:
/usr/include/stdio.h:444:10: note: previous declaration of 'mmap' with type 'void *(void *, size_t, int, int, int, __off_t)' {aka 'void *(void *, long unsigned int, int, int, int, long int)'}
444 | void *mmap(void *, size_t, int, int, int, __off_t);
| ^~~~
rake aborted!
В этом же файле mman.h заменяем:
void* mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off);
на:
void* mmap(void *addr, size_t len, int prot, int flags, int fildes, __off_t);
И повторно запускаем сборку.
Если сборка прошла успешно то в папке build/host/bin будут готовые бинарники:
ls ./mruby/build/host/bin/
mirb mrbc mruby
Теперь с их помощью запускаем сборку уже нашего тестового приложения:
/opt/work/mruby-yeah/mruby/build/host/bin/mrbc -Btest_symbol ~/test.rb
Это сгенерирует файл test.c с вот таким контентом:
#include <stdint.h>
#ifdef __cplusplus
extern
#endif
const uint8_t test_symbol[] = {
0x52,0x49,0x54,0x45,0x30,0x33,0x30,0x30,0x00,0x00,0x01,0x3e,0x4d,0x41,0x54,0x5a,
0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x01,0x0c,0x30,0x33,0x30,0x30,
0x00,0x00,0x00,0xcd,0x00,0x01,0x00,0x05,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x3d,
0x1d,0x02,0x01,0x1f,0x02,0x00,0x2d,0x01,0x02,0x01,0x10,0x02,0x03,0x0e,0x03,0x0b,
0xb8,0x2d,0x01,0x04,0x10,0x51,0x02,0x00,0x57,0x03,0x00,0x2e,0x01,0x05,0x01,0x1d,
0x01,0x06,0x51,0x02,0x01,0x51,0x03,0x02,0x24,0x01,0x51,0x02,0x03,0x2d,0x01,0x07,
0x01,0x06,0x02,0x47,0x02,0x01,0x2d,0x01,0x08,0x01,0x38,0x01,0x69,0x00,0x04,0x00,
0x00,0x0a,0x2f,0x68,0x69,0x2f,0x7b,0x6e,0x61,0x6d,0x65,0x7d,0x00,0x00,0x00,0x09,
0x53,0x48,0x45,0x4c,0x46,0x5f,0x45,0x4e,0x56,0x00,0x00,0x00,0x0a,0x70,0x72,0x6f,
0x64,0x75,0x63,0x74,0x69,0x6f,0x6e,0x00,0x00,0x00,0x0e,0xd0,0x97,0xd0,0xb0,0xd0,
0xbf,0xd1,0x83,0xd1,0x81,0xd0,0xba,0x2e,0x2e,0x00,0x00,0x09,0x00,0x03,0x44,0x53,
0x4c,0x00,0x00,0x04,0x59,0x65,0x61,0x68,0x00,0x00,0x06,0x65,0x78,0x74,0x65,0x6e,
0x64,0x00,0x00,0x04,0x70,0x6f,0x72,0x74,0x00,0x00,0x03,0x73,0x65,0x74,0x00,0x00,
0x03,0x67,0x65,0x74,0x00,0x00,0x03,0x45,0x4e,0x56,0x00,0x00,0x04,0x70,0x75,0x74,
0x73,0x00,0x00,0x08,0x5f,0x5f,0x6d,0x61,0x69,0x6e,0x5f,0x5f,0x00,0x00,0x00,0x00,
0x33,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x34,0x04,0x00,
0x00,0x51,0x03,0x00,0x01,0x04,0x01,0x52,0x03,0x38,0x03,0x00,0x01,0x00,0x00,0x0d,
0xd0,0x9f,0xd1,0x80,0xd0,0xb8,0xd0,0xb2,0xd0,0xb5,0xd1,0x82,0x20,0x00,0x00,0x00,
0x4c,0x56,0x41,0x52,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x01,0x00,0x04,0x6e,0x61,
0x6d,0x65,0x00,0x00,0xff,0xff,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};
Длинная "сопля" выше - ни что иное как готовый байткод mruby, записанный в виде статичного массива байт.
Параметр -Btest_symbol как нетрудно догадаться отвечает за название этого массива.
А вот так выглядит само приложение для запуска (файл test_stub.c):
#include <mruby.h>
#include <mruby/irep.h>
#include <test.c>
int
main(void)
{
mrb_state *mrb = mrb_open();
if (!mrb) { /* handle error */ }
mrb_load_irep(mrb, test_symbol);
mrb_close(mrb);
return 0;
}
Все что тут происходит это просто выдача байткода интерпретатору mruby при запуске обертки. Обратите внимание на строчку:
#include <test.c>
Это и есть включение файла с байткодом mruby.
Ну и наконец сборка конечного тестового приложения:
gcc -std=c99 -static -Os -s -I/opt/work/mruby-yeah/mruby/include -I. test_stub.c -o test_program /opt/work/mruby-yeah/mruby/build/host/lib/libmruby.a -lm -lpthread
Если все пройдет успешно, в текущей папке появится финальный бинарник test_program, который я запускал в самом начале.
Эпилог
Думаю изложенного материала хватит для того чтобы те из читателей, кто занимается встраиваемыми системами попробовали MRuby для своих задач, благо автору данная штука видится крайне перспективной.
Также как и правительству Японии, которая этот проект финансирует.
Просто потому что убирает целый класс проблем, связанных с разработкой прикладных систем на чистом Си — управление памятью, юникод, строки и так далее.
С нетерпением жду отзывов о реальном использовании.
P.S.
Статья была опубликована на Хабре, оригинал которой доступен в нашем блоге.
Более 70% IT-специалистов задумываются о смене работы
Доля IT-специалистов, рассматривающих возможность сменить работу, за год выросла на 6 п.п. и составила 73%. Основные причины — несоответствие зарплатным ожиданиям и отсутствие карьерного и профессионального роста. Об этом говорится в исследовании агентства «Марк Аналитик», проведенном для ИКС Холдинга.
Несоответствие между ожидаемым и фактическим доходом в среднем по рынку составляет 43%. Средняя заработная плата участников исследования — 181 122 рубля, в то время как желаемый уровень — 258 520 рублей. Самый большой разрыв — 56% — отмечают специалисты в области аппаратной разработки.
Среди тех, кто думает о смене работы, уровнем зарплаты недовольны 45% специалистов, невозможность карьерного и профессионального роста на текущем месте отмечают 31% и 28% соответственно, а 23% связывают желание сменить работу с усталостью и выгоранием.
При этом текущая рыночная ситуация корректирует завышенные ожидания после периода бурного роста отрасли. Кандидаты менее критично подходят к выбору нового работодателя. 5 из 10 ключевых факторов выбора — зарплаты, возможности карьерного роста, спектр задач, интересная сфера деятельности и работа с профессионалами высокого уровня — потеряли в значимости 3-6 п.п. Это может быть обусловлено ростом конкуренции среди кандидатов, развитием искусственного интеллекта и подтверждает тенденцию перехода к рынку работодателя.
Искать новую работу IT-специалисты предпочитают внутри профессионального сообщества — 58% — через личные знакомства, 38% — через тематические профессиональные сайты. Доля таких участников исследования выросла на 5 п.п. Специализированные сервисы онлайн-рекрутинга теряют позиции: за год к ним стало обращаться на 3% меньше IT-специалистов, и в целом они обращаются к ним значительно реже, чем представители других специальностей (66% против 78%).
В сфере технологий сформировалось разделение сфер на инженерию и IT. Большинство специалистов четко разделяют эти профессиональные роли. Респонденты выделяют отличия между IT и инженерией как в самой деятельности — инженеры работают с физическими системами и сетями коммуникаций (22%) — так и в задачах и подходах к их решению (21%).
IT-специалисты все отчетливее осознают стратегическую важность профессии инженера. 85% участников исследования ответили, что инженерные профессии — это профессии будущего, играющие важную роль в технологическом прогрессе.
«Мы видим изменения ожиданий и подходов к выбору работодателя на своем опыте. Заработная плата — один из ключевых факторов при выборе работодателя, но в нашей сфере недостаточный. Предлагаемый нами доход в большинстве случаев соответствует ожиданиям кандидатов, и мы выстраиваем свою работу так, чтобы не только привлекать специалистов, но и давать возможность реализовать свой потенциал, постоянно развиваясь в компании. Команда ИКС Холдинга очень быстро растет — за прошлый год с 11 до 17 тысяч сотрудников, из которых большая часть — инженеры. Для таких специалистов важна возможность участия в амбициозных проектах и понятный трек профессионального развития. Мы выстраиваем его с самого старта карьеры, привлекая большую часть специалистов напрямую через стажировки и работу с вузами, и уделяем большое внимание развитию через передачу экспертизы опытных инженеров новым поколениям специалистов», — отметила директор по персоналу ИКС Холдинга Наталия Пуговкина.
Исследование агентства «Марк Аналитик» проводилось в марте-апреле 2026 года в крупнейших городах России и РБ методом онлайн-интервью, было опрошено более 2000 человек.
Подключаю свой перчик к умному дому, или как я датчик влажности почвы для Home Assistant делал
Привет! Я люблю выращивать острый перчик халапеньо в домашних условиях, но есть небольшая проблема в моем домашнем «садоводстве»: я периодически забываю его поливать. Почему бы не делегировать напоминания о поливе своему умному дому, а в перспективе и автоматизировать полив — подумал я. И чтобы реализовать данную задумку, для начала нужно разработать и собрать датчик влажности почвы. А что из этого получилось — читайте далее.
❯ Начало
Для начала нужно разобраться с тем, чего мы хотим и как это делать. Затем подумаем об аппаратной части датчика, чтобы она была экономически оправдана и проста в реализации. И преимущественно я буду собирать датчик из тех компонентов, которые у меня есть в наличии.
После непродолжительного обдумывания вырисовывается следующий перечень основных компонентов для реализации нашего DIY-проекта:
Теперь поговорим о питании, в датчике я решил использовать батарейный вариант питания с двумя элементами типа АА. Данный вариант опробован в моей метеостанции и это отличный и долгоиграющий вариант питания. При периодичности отправки данных датчиком каждые два часа питания от двух элементов АА должно хватить на пару лет.
В качестве вычислительной платформы, как видно из таблицы, я буду использовать модуль ESP-02S с чипом ESP8285 — отличный вариант, который я использую в своих IoT-устройствах.
В качестве датчика влажности почвы я использую ёмкостный сенсор HW-390 на базе таймера TLC555. В отличие от более дешевого резистивного датчика, ёмкостной надежнее, так как не имеет прямого контакта измерительных электродов с почвой, соответственно, нет коррозии и других сопутствующих проблем — думаю, это очевидно.
Также стоит обратить внимание на используемый тип таймера при покупке датчика. В нашей схеме напряжение питания соответствует уровню в 3 вольта, поэтому необходимо убедиться, что в датчике используется таймер TLC555, у которого более широкий диапазон питания — от 2 до 15 В.
Использование АЦП ADS1115. У ESP-02S имеется один аналоговый вход (ADC/АЦП), который мы не сможем использовать для подключения аналогового выхода датчика, так как он задействован для внутренних механизмов замера питающего напряжения ядра, а оно нам нужно для мониторинга уровня заряда батареи питания. Поэтому простым и недорогим решением будет использование дополнительного I2C-модуля с АЦП ADS1115.
Замер микроклимата на поверхности почвы. Изначально я не планировал использование дополнительных датчиков в данном устройстве, кроме датчика влажности почвы, но позже подумал о масштабировании и решил, что было бы хорошо ещё и мониторить температуру и влажность на поверхности почвы, например, для использования нашего устройства в системах автоматизации теплиц. Тем более добавление нового датчика не сильно влияет на экономику проекта — ни в техническом, ни в экономическом плане.
❯ Железо и схемы
Давайте перейдём ближе к схеме. Сама принципиальная схема довольно проста, а модульность элементов позволит собрать её даже начинающим DIY-щикам. На скорую руку набросал принципиальную схему, которую вы можете наблюдать ниже:
На схеме вы можете видеть, кроме описанного в таблице набора компонентов, дополнительные элементы: кнопки (SW1 и SW2) и светодиод. Кнопка SW1 нужна для перехода в режим конфигурации, а светодиод — для индикации режима.
При использовании модуля ESP-02S есть небольшая проблема: модуль изначально не может работать в режиме DeepSleep, а он нам крайне необходим при батарейном питании для экономичной работы датчика. Однако есть решение, как это исправить, и ниже оно показано на фото:
Проблема в том, что на модуле ESP-02S не выведен GPIO16, а он как раз является выходом RTC-таймера, который должен выводить устройство из режима глубокого сна. Поэтому на фото вы видите, как я подпаял тонкую жилу медного провода к данному контакту. Далее он будет соединён с контактом RST. И для изоляции и фиксации данного соединения я использую термоклей.
И для большей энергоэффективности питание всех датчиков подаётся через пин GPIO4, который отключается при переходе в режим глубокого сна (Deep Sleep).
❯ Корпус устройства
Корпус датчика, как обычно, проектировался во FreeCAD. Постарался сделать максимально красиво и компактно. Ниже представлены рендеры моего творчества:
Далее корпус был напечатан на моём 3D-принтере FlashForge 5M. Ниже представлен скриншот из слайсера для оценки расхода филамента:
Модель корпуса печаталась из пластика PETG и с конфигурацией толщины слоя 0,24 мм. Ниже представлен результат печати:
На фото представлен один из вариантов печати. Для доработки корпуса мне пришлось выполнить шесть итераций печати.
❯ Сборка устройства
Сборка достаточно проста. Само собой, я за переиспользование электронных компонентов, поэтому и в этом проекте я использую б/у элементы, а именно микропереключатели (кнопки), которые я взял из старой сломанной компьютерной мыши, а зелёный светодиод — из сломанного сетевого коммутатора. Также хорошо подходят для проекта провода из старого LPT-кабеля.
Для начала давайте прикрутим модуль датчиков для мониторинга микроклимата на поверхности почвы. Для работы датчика в корпусе предусмотрена специальная решётка:
Как можно видеть на фото, так были установлены микропереключатели и светодиод. Их установка выполняется просто — они запрессованы в корпус небольшим усилием, но при этом держатся достаточно надёжно.
А до этого выполнялась примерка датчика влажности почвы, и это выглядело так:
И чтобы освободить пространство внутри корпуса, в дальнейшем разъем с датчика был удален легким взмахом паяльника.
Соединяем всё согласно представленной выше принципиальной схеме:
Закрываем весь этот электронный ужас бэкенд крышкой — и в результате получаем следующее:
И, само собой, перед сборкой устройства нам необходимо загрузить прошивку — о ней и пойдёт речь дальше.
❯ Дела программные. Микро ПО и интеграция в Home Assistant
Микропрограмма, она же прошивка. Здесь всё стабильно: микропрограмма нашего датчика реализована на базе моей прошивки для IoT. Идеально подходит прошивка для метеостанции из прошлой статьи с небольшими изменениями — её и будем использовать. DIY-вариант написан в популярной среде разработки Arduino IDE, ссылка на исходный код прошивки будет указана в конце статьи.
Затрону лишь некоторые моменты использования датчика почвы в прошивке. Замер аналогового сигнала выполняется с помощью АЦП ADS1115, для работы с которым я использовал первую попавшуюся библиотеку ADS1115_WE. Сам датчик имеет обратную зависимость выходного сигнала от измеряемой влажности почвы: то есть если влажность высокая, то уровень выходного сигнала приближается к нулю, и наоборот. Ниже представлена функция замера и преобразования сигнала с датчика:
float soil_hum(){
float mes_voltage = 0;
if(sens_stat){
mes_voltage = ADC.getResult_V();
}
return progress(mes_voltage, 0, 1.72, 100, 0); // 1.72 - это значение напряжения сухого датчика
}
Где с помощью метода ADC.getResult_V() мы получаем данные с АЦП по каналу A0 в вольтах. Преобразование полученного сигнала выполняется с помощью аналога функции map():
float progress(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Соответственно, функция soil_hum() возвращает уже преобразованное значение датчика в процентах. Чтобы инвертировать шкалу влажности почвы, мы просто в аргумент out_min передаём значение 100, а в out_max — 0. Значение 1.72 — это калибровочное значение, уровень сигнала сухого датчика. Оно и будет точкой отсчёта.
Для мониторинга уровня заряда батареи используется встроенный метод:
voltage = ESP.getVcc() * 0.001;
Чтобы это работало, нужно перевести режим работы встроенного АЦП на замер напряжения шины питания:
ADC_MODE(ADC_VCC);
В данном режиме нет необходимости подключать аналоговый вход к элементам питания.
Первоначальная прошивка выполняется стандартными средствами среды разработки Arduino IDE и RS232-программатора, а последующие обновления — через веб-интерфейс датчика в режиме конфигурации. Остальные стандартные пользовательские настройки устройства выполняются с помощью встроенного веб-интерфейса. Ниже представлены скриншоты нескольких ключевых страниц:
По умолчанию пароль — admin, далее вы можете изменить стандартный пароль на свой в соответствующем разделе меню.
Для экономии заряда батареи работа устройства ограничена по времени 15 минутами, далее устройство перезагрузится и перейдёт в режим глубокого сна. На основной странице отображены данные датчиков в реальном времени и конфигурация таймера сна — он же отвечает за периодичность отправки данных, если эта функция включена в настройках.
На данной странице выполняются настройки передачи данных — она же отвечает за интеграцию с умным домом Home Assistant. Связь с умным домом выполняется по протоколу MQTT, а за автоматическую интеграцию устройства отвечает метод MQTT Discovery.
Интеграция в Home Assistant
Для экономии вашего времени, с вашего позволения, здесь, пожалуй, я не буду расписывать в подробностях, что и как, — это уже было много раз описано в прошлых статьях. Я лишь приведу несколько скриншотов из Home Assistant:
Здесь вы можете наблюдать список автоматически интегрированных устройств, среди которых находится и наш датчик.
А вот и наш датчик во всей красе, где мы видим его данные, которые можно использовать в сценариях автоматизации. Также здесь реализован переход в веб-интерфейс устройства, если по каким-то причинам вы не помните его адрес. Веб-интерфейс устройства доступен только в режиме конфигурации.
Выше размещено фото теста на сухой почве, чтобы наглядно объяснить переход датчика в режим конфигурации.
Правая кнопка отвечает за функцию сброса, а левая — за режим конфигурации. Как я уже упоминал выше, светодиод отвечает за индикацию режима. Для перехода в режим конфигурации необходимо нажать левую кнопку и, не отпуская её, кратковременно нажать правую. Не отпуская левую кнопку, дождитесь мигания зелёного светодиода. Всё, режим конфигурации активирован. Если вам нужно просто отправить данные, кратковременно нажмите правую кнопку.
❯ Итоги
Давайте подведём итоги. За небольшую стоимость и удовольствие от разработки мы получили датчик, с помощью которого Home Assistant сообщит мне, что я плохой садовод и что пора бы полить перец. А для долгих поездок стоит подумать о системе автополива — но это уже совсем другая история. А портативность и автономность питания позволяет применить данный датчик для автоматизации теплиц, ведь в нём уже предусмотрены датчики для мониторинга микроклимата на поверхности почвы, что позволяет реализовать целый измерительный кластер.
Кстати, насчет автономности. Ниже представлены фото с замерами токов потребления в разных режимах работы:
Если датчик будет использовать литиевые батареи при режиме отправки данных каждые два часа, то расчётное время работы на одном комплекте батарей составит 7–8 лет. Конечно, это фантастические сроки, но если устройство проработает на одном комплекте батарей 1–2 года, то это вполне удовлетворительные сроки.
На этой позитивной ноте можно и завершать статью. Спасибо, что дочитали.
И, как всегда, если у вас есть желание что-то добавить, обсудить, осудить, похвалить или поделиться опытом — добро пожаловать в комментарии! Всем успехов!
Ссылки к статье:
Автор текста: CyberexTech
Написано при поддержке Timeweb Cloud ↩
Больше интересных статей и новостей в нашем блоге на Хабре и телеграм-канале.
📚 Вам может быть интересно:
Реклама. ООО «ТАЙМВЭБ.КЛАУД», ИНН: 7810945525
Я написал свой self-hosted MDM для смешанного парка корпоративных устройств
Полгода назад я пришёл в новую организацию, и мне достался парк машин. Десяток на Windows, несколько маков, пачка линуксовых ноутов у разработчиков. Невыносимым было другое. Навешанные до меня политики и блокировки со стороны ИБ связывали руки так, что здраво администрировать домен было попросту нельзя: любое рутинное действие упиралось в чужие ограничения. Тогда я и полез искать решение, которое помогло бы мне нормально админить этот парк.
Готового, что легло бы на мою ситуацию, я так и не нашёл — и в итоге сел писать своё. Ниже — разбор инженерных решений, которые по дороге пришлось принять, и карта того, где у этой конструкции проходит настоящая граница безопасности. Последнее для меня важнее всей остальной механики: MDM по определению даёт слишком много власти над чужими машинами, и делать вид, что это не так, я не стал. Поэтому архитектуру ниже я описываю как ответ на вопрос «где это ломается».
Откуда взялась задача
Требований у меня было немного, но именно они всё и отсеяли: сервер должен стоять у меня, телеметрия парка не утекать на сторону, и всё это работать на смешанном парке сразу. Self-hosted-вариантов под такое оказалось немного, и те, что были, спотыкались об одно и то же: либо только маки, либо платные и при этом недоступны в России, либо разваливались на простом вопросе — а что с устройством, если агент две недели просидел офлайн. Ближе всего в этом поиске подобрался Fleet — серьёзный открытый проект, к нему я ещё вернусь ниже. Но развернуть его у себя оказалось тем ещё квестом: я в России, а fleetdm на российский IP не але, так что и сервер, и сборку агентов приходилось поднимать через VPN. Вдобавок агент на macOS в моём случае вставал через раз, а то и вовсе не ставился. Инструмент, который в итоге получился, я назвал RoutineOps; дальше по тексту буду говорить «агент», «сервер» и «панель».
Ещё одно соображение, которое я держал в голове: базовую защиту управляющего канала — mTLS, аудит, вменяемую политику паролей, подпись обновлений — я считаю БАЗОЙ, и держать её за пейволлом мне казалось неправильным. Это моё мнение, не претензия к рынку. Проект в итоге открыт под Apache-2.0; платный уровень существует, но вся базовая защита — в открытой части, и это не тема статьи.
Почему постоянный gRPC/mTLS-канал, а не опрос через VPN
Вечная головная боль: как дотянуться до ноутбука, который сейчас сидит в кафе за чужим NAT. Классических ответов два, и оба мне не понравились. Загнать всех в VPN — это лишняя инфраструктура и ещё одна точка отказа. Сделать агент, который раз в N минут дёргает HTTP-эндпоинт «не прилетело ли чего», — это шторм пустых запросов, а задержка команд упирается в период опроса.
Я пошёл третьим путём. Агент — Go-бинарь, который держит постоянный gRPC-стрим поверх mTLS наружу, по обычному интернету. Соединение инициирует сам агент: оно исходящее, а значит дружит с NAT. По этому же двунаправленному стриму сервер в любой момент проталкивает команду — запусти скрипт, заблокируй экран. Задержка доставки тут — это задержка сети, а не интервал, на который выставлен опрос.
Стек намеренно скучный — скучное не будит меня в три ночи.
Агент и сервер — Go, сервер монолитом, без зоопарка микросервисов.
Связь — gRPC + Protocol Buffers поверх mTLS: TLS 1.3, приватный CA с пиннингом на всём канале агентов.
База — PostgreSQL 16, источник правды.
Очередь задач — Redis + Asynq, с ретраями.
Веб-интерфейс — React + TypeScript (Vite), раздаётся nginx-контейнером.
Развёртывание — Docker Compose.
Порты минимальны: 443 — веб-интерфейс, REST API, enroll, отдача бинарей; 50051 — постоянный gRPC-канал агентов. Postgres и Redis наружу не торчат вообще. На парк до 50 устройств хватает 1 vCPU / 2 GB RAM / 20 GB SSD — и это стартовая планка. Heartbeat дешёвый, инвентарь редкий, поэтому один узел спокойно тянет тысячи устройств: предел задаёт железо машины, не архитектура.
Сертификат — это идентичность, и всё
Самый важный архитектурный вопрос: как агент доказывает, что он именно то устройство, за которое себя выдаёт. Ответ короткий. device_id — это CN клиентского сертификата, и только он. Идентификаторам в теле сообщений сервер не верит вообще. Прилетел heartbeat, а внутри указан чужой device_id? Игнорируется. Значение имеет одно — чем подписан TLS-хендшейк.
Отсюда и enrollment, устроенный так, чтобы приватный ключ устройства никогда не покидал устройство:
Админ в панели заводит устройство и получает одноразовый токен: TTL 24 часа, single-use, гонка при погашении закрыта на уровне БД.
Агент локально генерирует пару ключей и отправляет CSR на POST /api/v1/enroll.
Сервер подписывает сертификат своим приватным CA и сам проставляет CN. Повлиять на свой CN агент не может.
Подделать чужое устройство без его ключа не выйдет — и не потому, что «мы проверяем поля», а потому, что проверять тут в принципе нечего. Криптография здесь вырезает целый класс авторизационной логики. Решение нравится мне тем, что после него кода становится меньше.
Heartbeat и инвентаризация разведены нарочно
Наивно было бы слить всё в один поток: раз в минуту слать полный отчёт о железе и заодно сообщать, что жив. На парке это дорого и бессмысленно — список установленного софта не меняется каждые тридцать секунд. Поэтому потока два, независимых.
Heartbeat — лёгкий и частый, примерно раз в 30 секунд, по постоянному стриму; по нему же едут задачи. Дёшево, потому что данных в нём кот наплакал. Инвентаризация — тяжёлая и редкая, примерно раз в 5 минут: ОС, железо, серийник, установленное ПО (на Linux — из dpkg/rpm/pacman/apk), версия агента.
Такое разделение держит постоянный канал дешёвым при любом размере парка. Если устройство пропало с радаров и heartbeat не приходит дольше порога (порог настраивается через AGENT_UNREACHABLE_MINUTES) — поднимается алерт agent_unreachable, с подавлением дребезга от сна и modern standby, иначе каждый закрытый на ночь ноут спамил бы в Telegram Max :).
Результаты скриптов идемпотентны. У каждого запуска есть run_id, и повторная доставка дедуплицируется на сервере. Связь рвётся, ack теряется, агент шлёт результат заново — без этой механики ловил бы дубли, а на неидемпотентных командах ещё и двойное исполнение.
Агенту не нужна постоянная связь
Постоянный канал удобен, но агент не должен превращаться в кирпич, стоит серверу отвалиться. Cron-скрипт-политики крутит локальный планировщик прямо на устройстве: сервер лёг на обновление — политики по расписанию всё равно отработают.
Сложнее всего с блокировкой экрана. В офлайне полноэкранный overlay держится, а разблокировка идёт по локально хранимому bcrypt-хешу пароля — ходить за ней к серверу не нужно. Это осознанный компромисс: если бы разблокировка требовала сервер, любой обрыв связи превращал бы блокировку в невозможность войти в систему — а это уже хуже самой угрозы. Результаты и события тем временем копятся локально и досылаются, когда связь вернётся.
Временные админ-права — без поездки к машине
Бытовой, но постоянный сценарий: пользователю нужно поставить программу, которая требует прав администратора. Раньше это значило либо дойти до машины ногами, либо подключиться к ней удалённо и вбить админский пароль руками — и так на каждую установку.
Теперь пользователь запрашивает временные локальные админ-права прямо из трея агента, я одобряю заявку в панели — и он ставит нужное сам, а по истечении срока повышение снимается. Ни подходить к машине, ни поднимать удалённую сессию ради одной инсталляции не надо. Мелочь на фоне остальной механики, но именно из таких мелочей и складывалось то самое «здраво администрировать парк», ради которого всё и затевалось.
Fail-closed self-update и миграции
Самообновление — самый опасный канал из всех. Тот, кто им рулит, кладёт свой бинарь на все устройства как root. Поэтому здесь всё fail-closed. Примерно раз в 6 часов агент тянет манифест и проверяет sha256 и ed25519-подпись по полному манифесту: версия, ОС, архитектура, хеш подписаны одним набором сразу. Так нельзя подсунуть валидный бинарь под чужую версию или платформу. Дальше агент атомарно заменяет себя и перезапускается. Даунгрейд невозможен — есть anti-rollback floor: битый релиз чинится только версией вперёд, назад дороги нет. Приватный ключ подписи уникален для конкретной инсталляции; потеря не катастрофа — новый раздаётся через переэнролл.
На сервере тот же принцип. Схему накатывает отдельный migrate-сервис — до старта сервера. Не прошла миграция — сервер просто не поднимется на несовместимой схеме. Down-миграций нет, и это намеренно: откат — только из бэкапа, причём бэкап БД update.shснимает сам перед каждым обновлением. Пережить факап из снапшота я предпочту тому, чтобы полагаться на корректность down-скрипта, накатываемого поверх наполовину применённого состояния.
Модель доверия: god-mode by design
А вот тут льстить не буду — и это, пожалуй, самая важная часть текста. MDM по своей природе — это god-mode над парком. Скрипт-канал исполняет произвольный bash -c / powershell -Command как root/SYSTEM на каждом устройстве. Это не дыра, которую я забыл заткнуть. Это и есть продукт: весь смысл MDM в том, чтобы раскатать одну команду на сотню машин разом. А такой инструмент по определению — санкционированный RCE.
Скажу прямо.
Подписи на скрипт-канале нет. И не будет. Ed25519-подпись защищает только канал самообновления — анти-тампер и анти-даунгрейд бинаря. Она никак не ограничивает то, что вы запускаете на устройствах. Тезис «скомпрометированный сервер не сможет выполнить код на парке» — неверное прочтение. Ещё как сможет: выполнение произвольного кода на парке — это его штатная функция.
JWT_SECRET (симметричный HS256) — единственный корень доверия панели. Кто прочитал этот секрет, тот печатает себе сколько угодно валидных admin-токенов. Не «подобрать пароль», не «обойти MFA» — просто сгенерировать подписанный токен и зайти админом.
Отсюда единственный вывод: реальный периметр безопасности — это хост, на котором крутится сервер. Не TLS, не RBAC, не аудит. Они важны, но вторичны. Увели сервер — увели весь парк.
Харденинг этого хоста — работа оператора, и в SECURITY.md под неё лежит чеклист: SSH только по ключам, наружу открыты только два порта, Postgres и Redis — на localhost, JWT_SECRET генерируется через openssl rand -base64 48 и лежит в режиме 600, панель — за VPN или IP-allowlist, аудит-лог — в append-only хранилище с алертом на появление новых админов.
Я специально не прячу этот раздел в мелкий шрифт. Любой MDM устроен ровно так же — просто не каждый проговаривает это вслух. Мне важно, чтобы человек ставил такой инструмент с открытыми глазами: в безопасности честность — это техническое свойство, а не тон голоса.
Что закрыто по умолчанию
Периметр — на операторе. Но всё, что можно закрыть кодом, закрыто по умолчанию, без единой галочки:
Не стартует на слабом секрете. Требует JWT_SECRET от 32 байт и минимум 16 различных байт. Случайно уехать в прод на changeme не получится.
Admin-JWT живёт 8 часов. Logout реально ревокирует токен через jti-блоклист. Смена или сброс пароля обнуляет все ранее выданные токены пользователя разом — через token-epoch.
Lockout по IP и по аккаунту, bcrypt cost 12. Форма входа не выдаёт, какие аккаунты вообще существуют.
Политика сложности пароля — для всех, включая seed-админа: от 8 символов, минимум 3 класса символов из 4. Даже первый администратор не заведётся с admin/admin.
RBAC на две роли — it_admin (всё) и viewer (только чтение), с проверкой на сервере. Viewer, дёрнувший мутирующий эндпоинт прямо из DevTools, упрётся в 403 ещё на сервере.
Плюс одноразовые enroll-токены, журнал аудита на каждое привилегированное действие (retention по умолчанию 365 дней), security-заголовки (HSTS/CSP/X-Frame-Options/nosniff), rate-limit и cap на размер запроса.
Ни одна из этих механик не спасёт скомпрометированный хост — см. раздел про модель доверия. Они закрывают то, что реально можно закрыть на уровне приложения, и не притворяются, что закрывают больше.
Честные ограничения
Раз обещал честность — вот граница, без прикрас.
Один узел — одна точка отказа. Для парка в режиме «поставил и работает» этого достаточно, но иллюзий про отказоустойчивость держать не стоит.
Нет SSO и MFA. Вход по паролю плюс RBAC. Пока разумно держать панель за VPN или allowlist.
macOS .pkg не подписан Apple. По двойному клику встретит Gatekeeper — ставится через installer из терминала.
Скрипт-канал — это RCE by design. Подробно — в разделе про модель доверия выше.
Про Fleet
Ближайший открытый аналог, который я смотрел, — Fleet, тоже open source, ядро под MIT. Инструмент серьёзный и зрелый, и во многом он сильнее моего: настоящий нативный MDM с профилями конфигурации и zero-touch-энроллментом, live-запросы osquery по всему парку, расчёт на масштаб в сотни тысяч машин. Построен вокруг osquery, инфраструктура — MySQL плюс Redis за балансировщиком, под горизонтальное масштабирование.
Под мой случай он просто не сошёлся по устройству. Чтобы реально управлять маками, Fleet опирается на нативный Apple MDM — а это APNs-сертификат от Apple с ежегодным продлением плюс Apple Business Manager для zero-touch. Мне не нужна была SQL-аналитика по всему парку; нужен был лёгкий агент, офлайн-лок без APNs, инвентарь и скрипты. А поверх этого архитектурного несовпадения легли те самые бытовые проблемы с развёртыванием из России и капризным macOS-агентом, о которых я писал в начале.
Чему это меня научило
Самое неожиданное в проекте — что львиная доля инженерных решений оказалась не про «как добавить», а про «как убрать». Идентичность через CN вырезала целый класс серверной авторизации; fail-closed на миграциях и обновлениях — ветки «а что если накатилось наполовину». Разведённые heartbeat и инвентарь сняли лишний трафик. А карта модели доверия избавила меня самого от иллюзии, будто TLS и RBAC — это и есть безопасность. Настоящий периметр оказался в одном месте — на хосте сервера.
Исходники я открыл под Apache-2.0; прямую ссылку на репозиторий оставлю тут :). Если вам будет интересно я бы с радостью пообщался по замечаниям именно по модели доверия — по местам, где приложение должно что-то enforce-ить, но не делает.


































