
Почти всё время существования лаборатории студенческих проектов Висконсинского университета в ней использовалась камера. Есть свидетельства наличия такой системы ещё в 1990-х: на древней версии сайта университета о ней говорится следующее:
…на стену приклеена изолентой камера ценой $15, подключённая к видеомагнитофону, который соединён с видеоразъёмом Mac IIcx, где запущены Timed Video Grabber (TVG) и FTPd. Рабочая станция HP Dax выполняет скрипт, который каждые 60 секунд пытается сохранить на FTP последнее изображение. Из-за отсутствия синхронизации часов время от времени происходят коллизии доступа к файлам, и вся схема ломается.
Прочитав это, я ненароком с восхищением взглянул на камеру, которая теперь установлена наверху аркадного автомата. Система, для создания которой требовалось оборудование на многие тысячи долларов, сегодня реализуема (в бесконечно лучшем качестве) на основе веб-камеры за $50, подключённой к Raspberry Pi.


Эти кадры разделяет примерно двадцать пять лет.
Можно написать отдельный пост (и, вероятно, я этим займусь) о сложной истории лаборатории студенческих проектов (Undergraduate Projects Lab, UPL) с упоминанием того, что старые версии веб-сайта позволяли пользователям управлять поворотом и наклоном камеры при помощи четырёх прикреплённых к ней шаговых двигателей.
Однако в этой статье я расскажу о двух последних итерациях системы в UPL.
«Открыта ли сейчас UPL?»
Я уверен, что каждый член UPL может рассказать страшную историю о том, как он пришёл в лабораторию и увидел закрытую дверь. Если вы живёте не в кампусе, то печально осознавать, что сложный путь до здания computer science завершился неудачей.
Нет сомнений, что уже во времена IRC члены UPL спрашивали друг у друга, открыта ли лаборатория. С появлением мобильных телефонов напрягать друзей стало ещё проще; но они ведь могут и не находиться в кабинете!
Мы с другими членами UPL решили устранить эту проблему, вероятно, самым подходящим для студента CS способом: создать автоматизированную систему для определения того, занята ли лаборатория.
Считаем людей
В первой итерации системы подсчёта людей (разработанной Майклом Берки) использовалась камера Logitech C920, смонтированная в точке с хорошим обзором на кабинет. Discord-бот каждые 15 минут (при помощи discord.py.ext @tasks.loop(minutes=15)
) вызывал модель YOLOv7 с классом 0 (распознавание людей). Бот вызывал камеру, чтобы получить изображение, а затем получал инференс при помощи модели. Он возвращал количество людей в комнате (и с целью отладки аннотировал ограничивающими прямоугольниками те участки, где, по его мнению, находились люди).


…на скотч не обращайте внимания.
Затем он присваивал каналу имя, соответствующее результату (или 1-person-in-upl
, или X-people-in-upl
), которое могли видеть пользователи.

Переходим к датчикам на двери
Какое-то время это прекрасно работало — пользователи смотрели на имя канала Discord и понимали, сколько примерно людей находится в кабинете. Если там было написано «ноль людей», то можно было сделать вывод, что UPL закрыта.
Однако у этого решения начали появляться проблемы. Например, наличие людей в кабинете необязательно означает, что UPL открыта. Там может проходить совещание или отдельная встреча, когда двери закрываются и никого не впускают. Это сбивало с толку тех, кто видел имя канала «В UPL находится 8 человек» и лишь на месте узнавал, что у координаторов проходит совещание.
Также модель иногда интерпретировала стул в углу, как человека1:

Примерно в то же время я наткнулся на домашнюю страницу хакспейса Массачусетского технологического института MITERS. Она сообщала, открыта ли дверь, при помощи геркона, соединённого с Raspberry Pi. Герконы — это маленькие физические детали, способные обнаруживать магнитное поле. Если установить геркон в дверную раму и прикрепить крошечный магнит к самой двери, то можно определять, открыта ли дверь! Мне удалось найти статью бывшего члена хакспейса о реализации системы, но не гарантирую, что она совпадает с тем, что используется в хакспейсе сейчас.
Примечание
После публикации моей статьи я получил кучу вопросов о том, почему мы отслеживаем состояние двери, а не другие атрибуты кабинета. Большую часть этих вопросов задали в моём посте на Hacker News, а также под статьёй, опубликованной на Hackaday. Особенно мне понравился комментарий о том, что я «не смотрю на проект с системной точки зрения». Ниже представлены мои ответы на некоторые из вопросов.Почему бы не...
...направить на двери камеры?
Это бы тоже сработало! Но это было бы намного сложнее, чем прикрепить два датчика Zigbee на двери; мне бы пришлось обучать модель понимать разницу между интерьером UPL и коридором здания.
...направить камеру на источники освещения в кабинете?
Похоже, читатели больше всего путали отслеживание состояния кабинета и его занятости. Различие заключается в том, что наличие в лаборатории людей необязательно означает, что она открыта. Например, там могут проводить совещание координаторы (или прийти уборщица и так далее). Да, можно и другими способами понимать, есть ли люди в кабинете, но ни один из них не указывает на состояние так же хорошо, как двери (или же когда кто-то пишет в Discord, что кабинет закрыт для посещения).
...использовать технологическое решение, обнаруживающее устройства в лаборатории?
Таких предложений было несколько: например, для проверки того, кто вошёл в сеть, zdw советовал команду finger, а zimpenfish — применение rlogin. Дело в том, что в UPL больше нет десктопных компьютеров, которыми могли бы пользоваться студенты. Большинство студентов просто приносит свои ноутбуки, потому что купить компьютер сегодня уже не так сложно! У нас нет инфраструктуры (по крайней мере, пока), позволяющей студентам подключаться по ssh к нашим серверам, но если бы она и была, то мы бы не смогли определить, происходит ли это изнутри лаборатории. Что касается использования WiFi/DHCP для обнаружения устройств (как это рекомендовал сделать q3k), то члены UPL пользуются UWNet или eduroam, а не WiFi-сетью лаборатории. Также есть тонкие моменты, связанные с конфиденциальностью при отслеживании пользователей, пусть и анонимизированных, поэтому нам крайне не хотелось бы создавать нечто подобное.
...использовать онлайн-систему резервации?
UPL — это не конференц-зал. Хотя в ней существует список индивидуальных часов координаторов, кабинет не всегда занят согласно этому графику. Иногда координаторы пропускают свои часы, а иногда кабинет остаётся открытым до пяти утра. К чему это я: невозможно просто заглянуть в расписание для определения состояния кабинета, а указанное времени занятости довольно часто будет оказываться неточным.
...использовать WiFi-сигналы для определения точного количества людей в кабинете?
Ого! Это безумно круто. Но таким образом всё равно нельзя определить состояние использования кабинета. Однако всё равно было бы здорово иметь возможность подсчёта количества людей вместе с состоянием кабинета, так что эту мысль я запомню.
Надеюсь, это примечание ответило на самые частые вопросы о проекте.
Я подумывал использовать похожие компоненты для определения состояния двери UPL — было не так уж сложно купить модули ESP32 с поддержкой WiFi и герконы для монтажа не двери. Тогда чипы бы просто отправляли POST-запрос с состоянием каждый раз, когда дверь открывается или закрывается.
Я решил отказаться от такой системы по нескольким причинам:
В UPL нет оборудования для поддержки такой системы. Я не умею паять, а приклеивание макетных плат к стенам — не особо удобное и красивое решение.
Если после моего ухода из университета система поломается, то будет сложно найти человека, способного её починить. В UPL по большей мере работают с ПО!
Для логина в WiFi университета (UWNet) необходимо зайти на портал авторизации, где можно зарегистрировать устройство. Без вмешательства время от времени требуется выполнять повторную регистрацию, чтобы восстановить подключение2. Хотя запросы, которые обычный браузер выполняет для аутентификации с NetID, можно эмулировать, для этого потребуются существенные постоянные усилия (а после выпуска студента логин необходимо будет менять)!
Поэтому я решил, что датчики должны работать автономно и просто передавать своё состояние какому-то устройству в кабинете. К счастью, Raspberry Pi, где выполнялся код счётчика людей, можно было с лёгкостью использовать и для этой цели. Я установил Home Assistant — опенсорсную платформу для взаимодействия с различными сетевыми устройствами.
Существует множество устройств для отслеживания состояния дверей, производимых такими компаниями, как Ring и ADT, которые специализируются на безопасности дома. Однако для проверки их состояния обычно требуется проприетарный концентратор и у них нет удобных API для взаимодействия с устройством. К счастью, есть решение получше!
Zigbee!
Встречайте Zigbee — низкочастотный беспроводной протокол mesh-сетей, позволяющий умным устройствам общаться через персональную сеть. Преимущество протокола заключается в том, что можно было использовать один концентратор для общения с разнообразными устройствами, даже если они изготовлены разными производителями. Вместо того, чтобы искать определённый бренд контактных датчиков дверей, мне достаточно найти датчики, поддерживающие протокол Zigbee, после чего я смогу отображать их состояние в дэшборде Home Assistant.
Важно отметить, что радиооборудование Zigbee работает независимо от антенн WiFi и Bluetooth. Для взаимодействия с Zigbee-устройствами необходимо приобрести специальный приёмник, поддерживающий этот протокол. Для этого проекта я купил устройство SONOFF. Интеграция Home Assistant с Zigbee называется Zigbee Home Automation, она поддерживает широкий спектр Zigbee-координаторов (USB-донглов, обеспечивающих возможность подключения). При использовании этой интеграции Home Assistant автоматически создаёт сеть Zigbee, к которой могут подключаться устройства.
Для этого проекта я решил использовать дверные и оконные датчики Aqara. Из всех просмотренных мной дверных Zigbee-датчиков у них были лучше отзывы и они могут работать от батареек (CR1632) два года.
После получения координатора и датчиков я создал логин Home Assistant и установил интеграцию ZHA. Для сопряжения устройств достаточно было лишь удерживать на датчиках кнопку сброса, пока Home Assistant не распознал их и не добавил на дэшборд соответствующие элементы.


Используем состояние дверей
После настройки этой системы в дэшборде Home Assistant начало отображаться актуальное состояние дверей3! Честно говоря, было очень захватывающе открывать и закрывать двери, наблюдая за изменением дэшборда в реальном времени (хотя проходящие мимо кабинета, наверно, думали, что я сошёл с ума).
Здесь важно отметить, что UWNet обеспечивает полную изоляцию точки доступа. Ни одно из устройств в сети не может видеть другие (и это здорово, ведь иначе бы возникла огромная уязвимость устройств с открытыми портами). Если бы не это ограничение, я бы настроил систему так, чтобы веб-сайт напрямую отправлял запросы RPi.
Поначалу я решил использовать интеграцию RESTful Command Home Assistant для отправки POST-запроса на веб-сервер при изменении состояния дверей. Для этого необходимо, чтобы каждая команда была подготовлена заранее в файле configuration.yml
Home Assistant:
rest_command:
door1_opened:
url: "https://doors.amoses.dev/door1/open"
method: POST
headers:
content-type: "application/json"
payload: '{"door": "door1", "state": "open"}'
content_type: "application/json; charset=utf-8"
door1_closed:
url: "https://doors.amoses.dev/door1/close"
method: POST
headers:
content-type: "application/json"
payload: '{"door": "door1", "state": "closed"}'
content_type: "application/json; charset=utf-8"
door2_opened:
url: "https://doors.amoses.dev/door2/open"
method: POST
headers:
content-type: "application/json"
payload: '{"door": "door2", "state": "open"}'
content_type: "application/json; charset=utf-8"
door2_closed:
url: "https://doors.amoses.dev/door2/close"
method: POST
headers:
content-type: "application/json"
payload: '{"door": "door2", "state": "closed"}'
content_type: "application/json; charset=utf-8"
…но я вскоре осознал, что это решение неидеально. Во-первых, когда я опубликовал исходный код в GitHub, какие-то весёлые студенты решили, что можно вручную симулировать POST-запросы, чтобы произвольно менять состояние дверей. Вот, что получается, если не защищать конечную точку!4
Позже я узнал, что у Home Assistant наряду с веб-дэшбордом есть RESTful API. Если настроить его, то можно будет запрашивать у инстанса состояния подключённых устройств5. Для этого достаточно лишь добавить к HA URL маршрут /api/
.
Все маршруты API аутентифицируются bearer-токеном (скорее всего, чтобы отзеркаливать разрешения фронтенда, требующего логина пользователя для отображения данных). Учитывая то, что я хотел отображать состояние дверей на странице UPL, мне стала понятна потенциальная опасность выпуска bearer-токена с сайтом. Любой хитроумный пользователь сможет взять его и получить доступ к любому другому маршруту Home Assistant API. Учитывая уровень информации и контроля инстансов HA, это может иметь катастрофические последствия.
Я создал небольшой веб-сервер на основе Express, проксирующий запрос с bearer-токеном и передающий только релевантную информацию о дверях. Так как она отображается отдельно, пользователь никак не сможет просмотреть или изменить ничего другого.
server.js
const express = require("express");
const axios = require("axios");
const cors = require("cors");
const app = express();
const PORT = 3500;
const apiUrl = "https://HOMEASSISTANT-URL-HERE/api/states";
const token = "Bearer TOKEN-GOES-HERE";
app.use(cors());
app.get("/door-status", async (req, res) => {
try {
const response = await axios.get(apiUrl, {
headers: {
Authorization: token,
},
});
const data = response.data;
// получаем элементы с соответствующими id сущностей HA
const doors = data.filter(
(item) =>
item.entity_id === "binary_sensor.back" ||
item.entity_id === "binary_sensor.front"
);
// точная информация о состоянии и последнем обновлении
const doorStatus = doors.map((door) => ({
door: door.attributes.friendly_name,
status: door.state,
last_updated: door.last_updated,
}));
// отправляем отфильтрованные данные в виде json-ответа
res.json(doorStatus);
} catch (error) {
res.status(500).send("Error fetching data");
}
});
// :P
app.get("/", async (req, res) => {
res
.status(200)
.send("<html><body><b>wow upl door status endpoint 443</b></body></html>");
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Сервер будет от нашего лица запрашивать Home Assistant API (с соответствующим bearer-токеном), а тот будет возвращать объект JSON с состоянием дверей и временем их последнего изменения:
response.json
[
{
"door": "back",
"status": "on",
"last_updated": "2024-10-12T20:01:54.353657+00:00"
},
{
"door": "front",
"status": "on",
"last_updated": "2024-10-12T20:02:10.132178+00:00"
}
]
…а Discord-бот/веб-сайт UPL может использовать эти данные, чтобы отображать состояние.


Заключение
Я очень доволен результатами проекта. Было очень увлекательно разрабатывать систему, которой сам будешь пользоваться ежедневно, и испытывал приятное чувство каждый раз, когда я проверял, открыта ли UPL.
Примечания
Я уверен, что есть способы преобразования изображения, чтобы замаскировать эту область от распознавания. Но иногда в ней сидят люди!
Человек, живший в общежитиях Винсконсинского университета, знает, о чём я. Каждое устройство без браузерного доступа должно иметь MAC-адрес из белого списка сетевой системы. Срок этой авторизации истекает через шесть месяцев, поэтому через полгода ты теряешь доступ к Интернету и его нужно обновлять.
В UPL есть передний и задний вход, поэтому и «двери», а не «дверь».
Можете не пытаться, эти конечные точки больше не используются.
Внимательные читатели могут задаться вопросом: «а что насчёт той проблемы с изоляцией точки доступа?». Я нашёл замечательный аддон для Home Assistant, позволяющий при помощи туннелей Cloudflare получать доступ к дэшборду (а значит, и к API), когда отсутствует подключение к LAN Raspberry Pi. У него есть репозиторий на GitHub.
TimsTims
Ситуация: лаборатория рандомно занята рандомными людьми. И всем она нужна.
Вместо написания какого-то ресурса по букированию мест (и вообще поговорить с ректором, чтоб никто не закрывал лабораторию(!) университета, или не использовал лабораторию в качестве комнаты для совещаний), вместо всего этого, кто-то решил сделать это через камеру на скотче.
И в то время, как одни определяют точное местоположение по сигналу wifi, другие говорят что это сложно и пишут какую-то героическую статью как они подключили zigbee датчик к дискорд-каналу. Не говоря уже про прокачку очень умного машинного зрения, можно было бы совсем немножко и слушать звуки, определять разговаривает ли кто-то в комнате (сигнализируя о том, что там идет совещание).
То есть, для студента, который вроде как одержим технологиями, хочет попиариться в университете и есть свободное время, он выбирает самый простой способ определения. Потом пишет про это статью. И вместо хотя бы попытки использования других способов (и прокачки себя), он просто говорит: "это сложно". Весь его подвиг do it yourself таким образом нивелируется.