HTML Diff
1 added 1 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Сегодня поговорим о системе контроля управления доступом (СКУД), которая, будучи подключенной к платформе интернета вещей<a>Rightech IoT Cloud</a>(далее по тексту -- платформа), является базовым элементом в системе подсчета количества человек в офисе.</p>
1 <p>Сегодня поговорим о системе контроля управления доступом (СКУД), которая, будучи подключенной к платформе интернета вещей<a>Rightech IoT Cloud</a>(далее по тексту -- платформа), является базовым элементом в системе подсчета количества человек в офисе.</p>
2 <h2>С чего все началось</h2>
2 <h2>С чего все началось</h2>
3 <p>Задача обеспечить контроль за входом-выходом в офисе возникла еще в те времена, когда у нас был выход на крышу с отличным видом на Москву, и поэтому часто приходили люди, не являющиеся нашими сотрудниками. Тогда мы решили принять какие-то меры по безопасности и установить СКУД.</p>
3 <p>Задача обеспечить контроль за входом-выходом в офисе возникла еще в те времена, когда у нас был выход на крышу с отличным видом на Москву, и поэтому часто приходили люди, не являющиеся нашими сотрудниками. Тогда мы решили принять какие-то меры по безопасности и установить СКУД.</p>
4 <h2>Архитектура</h2>
4 <h2>Архитектура</h2>
5 <h3>Система открытия дверей по карте</h3>
5 <h3>Система открытия дверей по карте</h3>
6 - <p>В качестве модуля, отвечающего за обработку информации по считыванию бесконтактных карт, выбрали GATE-8000.</p>
6 + <p>В качестве модуля, овечающего за обработку информации по считыванию бесконтактных карт, выбрали GATE-8000.</p>
7 <p>Основные достоинства контроллера:</p>
7 <p>Основные достоинства контроллера:</p>
8 <ul><li>формирование и хранение необходимой информации о факте открытия двери по карте и времени прохода человека в офис;</li>
8 <ul><li>формирование и хранение необходимой информации о факте открытия двери по карте и времени прохода человека в офис;</li>
9 <li>возможность автономной работы и защиты от зависания;</li>
9 <li>возможность автономной работы и защиты от зависания;</li>
10 <li>хранение 16 тысяч ключей и 8 тысяч событий;</li>
10 <li>хранение 16 тысяч ключей и 8 тысяч событий;</li>
11 <li>простое подключение и управление;</li>
11 <li>простое подключение и управление;</li>
12 <li>российский производитель, хорошая документация с описанием работы контроллера.</li>
12 <li>российский производитель, хорошая документация с описанием работы контроллера.</li>
13 </ul><p>Основной принцип работы: контроллер обрабатывает информацию, поступающую со считывателя, и с помощью встроенного реле коммутирует исполнительное устройство - электромагнитный замок двери.</p>
13 </ul><p>Основной принцип работы: контроллер обрабатывает информацию, поступающую со считывателя, и с помощью встроенного реле коммутирует исполнительное устройство - электромагнитный замок двери.</p>
14 <h3>Система взаимодействия с платформой</h3>
14 <h3>Система взаимодействия с платформой</h3>
15 <p>После того, как контроллер установили в систему по общей схеме подключения, а карты записали в память, мы уже обезопасили себя от прохода в офис посторонних. А дальше возник вопрос, как подключить этот контроллер к платформе Rightech ioT Cloud. Ведь очень здорово иметь а) графический интерфейс, в котором можно полистать историю всех проходов, б) возможность отправки команд на открытие двери удаленно, не отходя от рабочего места, например, для гостей или доставщика еды.</p>
15 <p>После того, как контроллер установили в систему по общей схеме подключения, а карты записали в память, мы уже обезопасили себя от прохода в офис посторонних. А дальше возник вопрос, как подключить этот контроллер к платформе Rightech ioT Cloud. Ведь очень здорово иметь а) графический интерфейс, в котором можно полистать историю всех проходов, б) возможность отправки команд на открытие двери удаленно, не отходя от рабочего места, например, для гостей или доставщика еды.</p>
16 <p>У контроллера нет выхода в Интернет и видимой возможности подключения к платформе, к тому же все события нужно принудительно считывать из его циклического буфера. Однако у него есть свой протокол обмена данными с компьютером управления, благодаря которому можно отправлять на контроллер команды, такие как считать из контроллера, записать в контроллер, открыть/закрыть замок и другие. Значит, нужно сделать некоторую программно-аппаратную прослойку между контроллером и платформой - агента, который будет отправлять команды на чтение событий и управление контроллером.</p>
16 <p>У контроллера нет выхода в Интернет и видимой возможности подключения к платформе, к тому же все события нужно принудительно считывать из его циклического буфера. Однако у него есть свой протокол обмена данными с компьютером управления, благодаря которому можно отправлять на контроллер команды, такие как считать из контроллера, записать в контроллер, открыть/закрыть замок и другие. Значит, нужно сделать некоторую программно-аппаратную прослойку между контроллером и платформой - агента, который будет отправлять команды на чтение событий и управление контроллером.</p>
17 <p>Обращаем внимание, что в данной архитектуре функция открытия двери при прикладывании карты не перестанет выполняться при отсутствии Интернета. За открытие двери и сбор информации отвечает контроллер СКУД, поэтому в случае потери Интернета, единственное, что нам грозит, - это то, что агент, который доставляет данные на платформу, не будет функционировать, так как не сможет получать команды на чтение буфера. Однако при восстановлении соединения все события считаются в полном объеме и не потеряются, так как они будут сохранены в буфере контроллера СКУД.</p>
17 <p>Обращаем внимание, что в данной архитектуре функция открытия двери при прикладывании карты не перестанет выполняться при отсутствии Интернета. За открытие двери и сбор информации отвечает контроллер СКУД, поэтому в случае потери Интернета, единственное, что нам грозит, - это то, что агент, который доставляет данные на платформу, не будет функционировать, так как не сможет получать команды на чтение буфера. Однако при восстановлении соединения все события считаются в полном объеме и не потеряются, так как они будут сохранены в буфере контроллера СКУД.</p>
18 <p><strong>▍Аппаратная часть</strong>Для начала нужно было выбрать устройство, которое будет всегда в активном состоянии с включенной программой-агентом в непосредственной близости от платы СКУД. Из многообразия микрокомпьютеров первое что попало под руку выбор пал на Raspberry Pi.</p>
18 <p><strong>▍Аппаратная часть</strong>Для начала нужно было выбрать устройство, которое будет всегда в активном состоянии с включенной программой-агентом в непосредственной близости от платы СКУД. Из многообразия микрокомпьютеров первое что попало под руку выбор пал на Raspberry Pi.</p>
19 <p>Дальше возник вопрос, как подсоединить GATE-8000 к Raspberry - то есть как подключить последовательный интерфейс RS485 от GATE к USB от микрокомпьютера. Начались поиски переходника USB-RS485. Первый вариант, который мы испробовали, - Espada за 200 рублей. Надежда на то, что маленький хлипкий китайский переходник заработает, была небольшой. Он и не заработал. Вместо нужных данных приходило что-то похожее по виду и размеру, но… всё же не то. В чем было дело: в отсутствии гальванической развязки, невозможности поддерживать скорость 19200 bps или же просто в некачественной элементной базе, - загадка. Но после обращения к производителю GATE-8000, мы получили рекомендацию на более дорогой (в 10 раз) и громоздкий (но аккуратный и корпусированный) переходник Z-397, который заработал тут же как надо.</p>
19 <p>Дальше возник вопрос, как подсоединить GATE-8000 к Raspberry - то есть как подключить последовательный интерфейс RS485 от GATE к USB от микрокомпьютера. Начались поиски переходника USB-RS485. Первый вариант, который мы испробовали, - Espada за 200 рублей. Надежда на то, что маленький хлипкий китайский переходник заработает, была небольшой. Он и не заработал. Вместо нужных данных приходило что-то похожее по виду и размеру, но… всё же не то. В чем было дело: в отсутствии гальванической развязки, невозможности поддерживать скорость 19200 bps или же просто в некачественной элементной базе, - загадка. Но после обращения к производителю GATE-8000, мы получили рекомендацию на более дорогой (в 10 раз) и громоздкий (но аккуратный и корпусированный) переходник Z-397, который заработал тут же как надо.</p>
20 <p><strong>▍Программная часть</strong>Начинаем разработку программы с определения, какие функции она должна выполнять.</p>
20 <p><strong>▍Программная часть</strong>Начинаем разработку программы с определения, какие функции она должна выполнять.</p>
21 <ol><li>Что нужно - взаимодействие с GATE-8000 для отправки команд и получения данных.</li>
21 <ol><li>Что нужно - взаимодействие с GATE-8000 для отправки команд и получения данных.</li>
22 <li>Как решим - изучим протокол GATE, напишем сериализатор и десериализатор данных, подключим библиотеку для работы с последовательным портом.</li>
22 <li>Как решим - изучим протокол GATE, напишем сериализатор и десериализатор данных, подключим библиотеку для работы с последовательным портом.</li>
23 <li>Что нужно - взаимодействие с платформой для получения команд и отправки данных.</li>
23 <li>Что нужно - взаимодействие с платформой для получения команд и отправки данных.</li>
24 <li>Как решим - выберем для общения протокол MQTT, в коде воспользуемся готовой библиотекой Paho MQTT.</li>
24 <li>Как решим - выберем для общения протокол MQTT, в коде воспользуемся готовой библиотекой Paho MQTT.</li>
25 </ol><p>Итак, мы приступили к изучению протокола GATE-8000, который является закрытым, поэтому ниже рассказано только о некоторых его особенностях. Он достаточно низкоуровневый, и необходимо обращаться напрямую к регистрам памяти устройства. В документе по этому протоколу описаны кадры запросов от компьютера контроллеру и кадры ответов контроллера компьютеру. Меняя поля в кадре запроса, можно отправлять на устройство различные команды и получать информацию из регистров.</p>
25 </ol><p>Итак, мы приступили к изучению протокола GATE-8000, который является закрытым, поэтому ниже рассказано только о некоторых его особенностях. Он достаточно низкоуровневый, и необходимо обращаться напрямую к регистрам памяти устройства. В документе по этому протоколу описаны кадры запросов от компьютера контроллеру и кадры ответов контроллера компьютеру. Меняя поля в кадре запроса, можно отправлять на устройство различные команды и получать информацию из регистров.</p>
26 <p>Одна из особенностей протокола в том, что инициатором обмена всегда является компьютер. Поэтому есть два подхода работы с устройством:</p>
26 <p>Одна из особенностей протокола в том, что инициатором обмена всегда является компьютер. Поэтому есть два подхода работы с устройством:</p>
27 <p>1) задавать всю логику работы в агенте;</p>
27 <p>1) задавать всю логику работы в агенте;</p>
28 <p>2) использовать внешние запросы (от платформы).</p>
28 <p>2) использовать внешние запросы (от платформы).</p>
29 <p>Мы выбрали второй вариант и вынесли логику с конечного устройства на платформу. Так ее легко адаптировать и подстроить, при этом код программы остается компактным и позволяет просто формировать команды для устройства, а платформа в свою очередь координирует отправку команд и их периодичность.</p>
29 <p>Мы выбрали второй вариант и вынесли логику с конечного устройства на платформу. Так ее легко адаптировать и подстроить, при этом код программы остается компактным и позволяет просто формировать команды для устройства, а платформа в свою очередь координирует отправку команд и их периодичность.</p>
30 <p>Всегда ли нужно выносить логику работы с устройства?</p>
30 <p>Всегда ли нужно выносить логику работы с устройства?</p>
31 <p>Такое решение, конечно, не подойдет, если от устройства требуется предпринять действия мгновенно (например, если идет речь о жизни человека), но даже в таком случае часть логики можно вынести на платформу. Как вариант, для выбора заранее запрограммированных шаблонов поведения.</p>
31 <p>Такое решение, конечно, не подойдет, если от устройства требуется предпринять действия мгновенно (например, если идет речь о жизни человека), но даже в таком случае часть логики можно вынести на платформу. Как вариант, для выбора заранее запрограммированных шаблонов поведения.</p>
32 <p>После того как мы внимательно изучили этот протокол, формат кадров и список команд, возникла первая сложность. Команды для чтения буфера, в котором содержатся события о том, кто и во сколько пришел, не оказалось. А ведь получить эту информацию - первоочередная задача. Понадобилось изучить карту памяти контроллера, чтобы определить адреса, по которым нужно считывать данные.</p>
32 <p>После того как мы внимательно изучили этот протокол, формат кадров и список команд, возникла первая сложность. Команды для чтения буфера, в котором содержатся события о том, кто и во сколько пришел, не оказалось. А ведь получить эту информацию - первоочередная задача. Понадобилось изучить карту памяти контроллера, чтобы определить адреса, по которым нужно считывать данные.</p>
33 <p>Следующая особенность работы с контроллером в том, что за один цикл чтения можно получить только 12 событий, по 8 байт на каждое. А на каждый проход человека в офис генерируется уже два события:</p>
33 <p>Следующая особенность работы с контроллером в том, что за один цикл чтения можно получить только 12 событий, по 8 байт на каждое. А на каждый проход человека в офис генерируется уже два события:</p>
34 <p>1) найден ключ в банке ключей (банк ключей - еще один блок в распределенной памяти контроллера);</p>
34 <p>1) найден ключ в банке ключей (банк ключей - еще один блок в распределенной памяти контроллера);</p>
35 <p>2) состоялся проход (если он, конечно, состоялся).</p>
35 <p>2) состоялся проход (если он, конечно, состоялся).</p>
36 <p>Ниже представлен фрагмент кода на С++, реализующий метод одного цикла чтения буфера.</p>
36 <p>Ниже представлен фрагмент кода на С++, реализующий метод одного цикла чтения буфера.</p>
37 bool SerialPortInlet::readBufferCycle(unsigned short&amp; bottom, unsigned short const&amp; top, unsigned char&amp; u_lowerBound, unsigned char&amp; l_lowerBound, std::vector&lt;unsigned char&gt;&amp; readBuffer, std::string&amp; result) { // Подсчет байтов, которые необходимо считать unsigned short byteCountTmp = top - bottom; BOOST_LOG_SEV(log_, logging::info) &lt;&lt; "Need read " &lt;&lt; byteCountTmp &lt;&lt; " byte"; unsigned char byteCount; // За один цикл нельзя прочитать более 12 событий (96 байт) byteCount = byteCountTmp &gt; 0x60 ? 0x60 : (unsigned char)byteCountTmp; BOOST_LOG_SEV(log_, logging::info) &lt;&lt; "Read " &lt;&lt; +byteCount &lt;&lt; " byte"; // Описываем тело команды std::vector&lt;unsigned char&gt; body = {0x02, 0xA0, byteCount, u_lowerBound, l_lowerBound}; std::vector&lt;unsigned char&gt; command; // Получаем полный текст команды generateComplexCommand(command, Command::BYTE_CODE_READ, body); // Если не удалось по каким-то причинам отправить команду (например, конечное устройство не подключено), возвращается false if (!sendCommand(command, result)) { return false; } // Иначе отправляем ответ с устройства на парсинг по событиям SerialPortType::Answer answerEvents; if(!Parsers::parserAnswer(log_, result, answerEvents, Command::BYTE_CODE_READ)) { BOOST_LOG_SEV(log_, logging::error) &lt;&lt; "Failed parse buffer reading"; return false; } readBuffer.insert(readBuffer.end(), answerEvents.body.begin(), answerEvents.body.end()); // Сдвигаем нижнюю границу буфера для чтения следующих событий bottom = bottom + byteCount; u_lowerBound = (unsigned char)(bottom &gt;&gt; 8) ; l_lowerBound = (unsigned char)bottom; return true; }<p>Немного добавило хлопот то, что, наконец вытащив нужные байты, на месте информации о карте, мы увидели не номер карты, а адрес, по которому он находится. Поэтому потом каждый номер ключа приходится отдельно считывать по адресу. Также не сразу заметили наличие байтстаффинга, его обработку мы ввели уже после первого тестирования с платой.</p>
37 bool SerialPortInlet::readBufferCycle(unsigned short&amp; bottom, unsigned short const&amp; top, unsigned char&amp; u_lowerBound, unsigned char&amp; l_lowerBound, std::vector&lt;unsigned char&gt;&amp; readBuffer, std::string&amp; result) { // Подсчет байтов, которые необходимо считать unsigned short byteCountTmp = top - bottom; BOOST_LOG_SEV(log_, logging::info) &lt;&lt; "Need read " &lt;&lt; byteCountTmp &lt;&lt; " byte"; unsigned char byteCount; // За один цикл нельзя прочитать более 12 событий (96 байт) byteCount = byteCountTmp &gt; 0x60 ? 0x60 : (unsigned char)byteCountTmp; BOOST_LOG_SEV(log_, logging::info) &lt;&lt; "Read " &lt;&lt; +byteCount &lt;&lt; " byte"; // Описываем тело команды std::vector&lt;unsigned char&gt; body = {0x02, 0xA0, byteCount, u_lowerBound, l_lowerBound}; std::vector&lt;unsigned char&gt; command; // Получаем полный текст команды generateComplexCommand(command, Command::BYTE_CODE_READ, body); // Если не удалось по каким-то причинам отправить команду (например, конечное устройство не подключено), возвращается false if (!sendCommand(command, result)) { return false; } // Иначе отправляем ответ с устройства на парсинг по событиям SerialPortType::Answer answerEvents; if(!Parsers::parserAnswer(log_, result, answerEvents, Command::BYTE_CODE_READ)) { BOOST_LOG_SEV(log_, logging::error) &lt;&lt; "Failed parse buffer reading"; return false; } readBuffer.insert(readBuffer.end(), answerEvents.body.begin(), answerEvents.body.end()); // Сдвигаем нижнюю границу буфера для чтения следующих событий bottom = bottom + byteCount; u_lowerBound = (unsigned char)(bottom &gt;&gt; 8) ; l_lowerBound = (unsigned char)bottom; return true; }<p>Немного добавило хлопот то, что, наконец вытащив нужные байты, на месте информации о карте, мы увидели не номер карты, а адрес, по которому он находится. Поэтому потом каждый номер ключа приходится отдельно считывать по адресу. Также не сразу заметили наличие байтстаффинга, его обработку мы ввели уже после первого тестирования с платой.</p>
38 <p>Полная структурная схема разработанной системы выглядит так.</p>
38 <p>Полная структурная схема разработанной системы выглядит так.</p>
39 <p>Работоспособность всех устройств было очень удобно проверять с помощью графического последовательного терминала СuteCom. После успешного тестирования программа была поставлена на автозапуск, а Raspberry отправилась жить на потолке рядом с платой СКУДа.</p>
39 <p>Работоспособность всех устройств было очень удобно проверять с помощью графического последовательного терминала СuteCom. После успешного тестирования программа была поставлена на автозапуск, а Raspberry отправилась жить на потолке рядом с платой СКУДа.</p>
40 <h2>Работа на платформе Rightech IoT Cloud</h2>
40 <h2>Работа на платформе Rightech IoT Cloud</h2>
41 <h3>Модель</h3>
41 <h3>Модель</h3>
42 <p>Основные данные с контроллера - это события, на платформу они приходит в формате JSON и включают в себя поля:</p>
42 <p>Основные данные с контроллера - это события, на платформу они приходит в формате JSON и включают в себя поля:</p>
43 <ul><li>eventTime - время наступления события;</li>
43 <ul><li>eventTime - время наступления события;</li>
44 <li>eventCode - код события;</li>
44 <li>eventCode - код события;</li>
45 <li>keyNumber - номер карты сотрудника (поле может быть пустым, если событие вызвано не картой).</li>
45 <li>keyNumber - номер карты сотрудника (поле может быть пустым, если событие вызвано не картой).</li>
46 </ul><p>Модель устройства выглядит следующим образом.</p>
46 </ul><p>Модель устройства выглядит следующим образом.</p>
47 <p>Возможные события:</p>
47 <p>Возможные события:</p>
48 <ul><li>нажата кнопка звонка;</li>
48 <ul><li>нажата кнопка звонка;</li>
49 <li>неопознанный ключ на входе;</li>
49 <li>неопознанный ключ на входе;</li>
50 <li>неопознанный ключ на выходе;</li>
50 <li>неопознанный ключ на выходе;</li>
51 <li>ключ найден в банке ключей при входе;</li>
51 <li>ключ найден в банке ключей при входе;</li>
52 <li>ключ найден в банке ключей при выходе;</li>
52 <li>ключ найден в банке ключей при выходе;</li>
53 <li>открывание оператором по сети;</li>
53 <li>открывание оператором по сети;</li>
54 <li>дверь заблокирована оператором;</li>
54 <li>дверь заблокирована оператором;</li>
55 <li>дверь оставлена открытой после входа;</li>
55 <li>дверь оставлена открытой после входа;</li>
56 <li>дверь оставлена открытой после выхода;</li>
56 <li>дверь оставлена открытой после выхода;</li>
57 <li>проход состоялся на вход;</li>
57 <li>проход состоялся на вход;</li>
58 <li>проход состоялся на выход;</li>
58 <li>проход состоялся на выход;</li>
59 <li>перезагрузка контроллера.</li>
59 <li>перезагрузка контроллера.</li>
60 </ul><h3>Объект</h3>
60 </ul><h3>Объект</h3>
61 <p>Интерфейс объекта полностью формируется согласно разработанной модели.</p>
61 <p>Интерфейс объекта полностью формируется согласно разработанной модели.</p>
62 <p>Ура, теперь, собравшись на кухне офиса в ожидании пиццы на праздник, можно никуда не идти, а просто открыть мобильное приложение и нажать кнопку открытия двери для курьера!</p>
62 <p>Ура, теперь, собравшись на кухне офиса в ожидании пиццы на праздник, можно никуда не идти, а просто открыть мобильное приложение и нажать кнопку открытия двери для курьера!</p>
63 <h3>Автомат</h3>
63 <h3>Автомат</h3>
64 <p>Можно заметить, что есть команда не только на чтение буфера событий, но и на запись новых границ. В памяти контроллера хранятся границы буфера - начало и конец. Когда на устройство приходит команда чтения, из памяти берутся эти границы и в их пределах происходит чтение из буфера событий. Граница конца буфера сдвигается автоматически на контроллере при получении новых событий. А вот начальную границу буфера нужно перезаписать (указав конечную границу после прошедшего чтения), чтобы не прочитать одни и те же данные повторно. Но это необходимо сделать только после того, как данные о событиях успешно отправлены на платформу.</p>
64 <p>Можно заметить, что есть команда не только на чтение буфера событий, но и на запись новых границ. В памяти контроллера хранятся границы буфера - начало и конец. Когда на устройство приходит команда чтения, из памяти берутся эти границы и в их пределах происходит чтение из буфера событий. Граница конца буфера сдвигается автоматически на контроллере при получении новых событий. А вот начальную границу буфера нужно перезаписать (указав конечную границу после прошедшего чтения), чтобы не прочитать одни и те же данные повторно. Но это необходимо сделать только после того, как данные о событиях успешно отправлены на платформу.</p>
65 <p>Зафиксировать успешное получение данных и затем отправить команду на перезапись начальной границы удобно в автомате.</p>
65 <p>Зафиксировать успешное получение данных и затем отправить команду на перезапись начальной границы удобно в автомате.</p>
66 <p>Здесь виден цикл &lt;чтение&gt;-&lt;запись новой границы буфера&gt;-&lt;ожидание таймера&gt; (сейчас события считываются каждые 30 секунд).</p>
66 <p>Здесь виден цикл &lt;чтение&gt;-&lt;запись новой границы буфера&gt;-&lt;ожидание таймера&gt; (сейчас события считываются каждые 30 секунд).</p>
67 <ol><li>В состоянии “Read events” читаем новые события.</li>
67 <ol><li>В состоянии “Read events” читаем новые события.</li>
68 <li>В состоянии “Clear buffer” записываем новую границу.</li>
68 <li>В состоянии “Clear buffer” записываем новую границу.</li>
69 <li>В состоянии “Await timer …” ожидаем начала нового цикла.</li>
69 <li>В состоянии “Await timer …” ожидаем начала нового цикла.</li>
70 </ol><p>Также есть дополнительные обратные связи для состояний, в которых отправляются команды. Если в течение работы таймера не произошло успешное выполнение команды, таймер срабатывает и отправляется соответствующее сообщение оператору, после чего происходит повторная отправка команды.</p>
70 </ol><p>Также есть дополнительные обратные связи для состояний, в которых отправляются команды. Если в течение работы таймера не произошло успешное выполнение команды, таймер срабатывает и отправляется соответствующее сообщение оператору, после чего происходит повторная отправка команды.</p>
71 <h2>Дальнейшее использование собранных данных</h2>
71 <h2>Дальнейшее использование собранных данных</h2>
72 <p>Данный проект нашел свое продолжение в интеграции с нашей внутренней CRM системой, в которой на вкладке информации о сотрудниках всегда видны актуальные сведения о том, кто находится или отсутствует в офисе.</p>
72 <p>Данный проект нашел свое продолжение в интеграции с нашей внутренней CRM системой, в которой на вкладке информации о сотрудниках всегда видны актуальные сведения о том, кто находится или отсутствует в офисе.</p>
73 <p>Также отображается время входа/выхода из офиса, считается суммарное количество часов в месяц.</p>
73 <p>Также отображается время входа/выхода из офиса, считается суммарное количество часов в месяц.</p>
74 <p>В мессенджер Slack каждый день пишется о том, что офис открыт, когда приходит первый человек, взявший ключи на ресепшене.</p>
74 <p>В мессенджер Slack каждый день пишется о том, что офис открыт, когда приходит первый человек, взявший ключи на ресепшене.</p>
75 <p>Забор данных из платформы производится по REST API. API платформы предоставляет возможность работы, взаимодействия и использования сущностей платформы и их данных в таких внешних системах, как веб-порталы, мобильные и веб-приложения или, как в нашем случае, - CRM системах.</p>
75 <p>Забор данных из платформы производится по REST API. API платформы предоставляет возможность работы, взаимодействия и использования сущностей платформы и их данных в таких внешних системах, как веб-порталы, мобильные и веб-приложения или, как в нашем случае, - CRM системах.</p>
76 <p>Теперь мы знаем немного больше о том, как может работать СКУД в IoT-проектах. В следующих материалах рассмотрим, как рассчитать на базе полученной информации количество человек в офисе и какие практические применения есть у этой идеи.</p>
76 <p>Теперь мы знаем немного больше о том, как может работать СКУД в IoT-проектах. В следующих материалах рассмотрим, как рассчитать на базе полученной информации количество человек в офисе и какие практические применения есть у этой идеи.</p>
77  
77