Skip to content

Latest commit

 

History

History
728 lines (539 loc) · 49 KB

SKUD-ACCESS.md

File metadata and controls

728 lines (539 loc) · 49 KB

СКУД: доступ, владельцы карт и группы

Подсистема контроля доступа: кто, куда и когда может пройти. Документ описывает устройство подсистемы, принятые решения и проверенные на живом оборудовании факты.

Дата: 2026-09-29.


Зачем это нужно

До появления подсистемы СКУД работал с картами напрямую: карта была и учётной записью, и владельцем, и носителем прав. Для одного контроллера и десятка карт этого хватало. Когда система разворачивается в организации с отделами и несколькими контроллерами, такого подхода не хватает.

Три причины, по которым сущности разделены:

  1. У человека несколько карт. Основная карта и брелок. Пока карта была владельцем, уход сотрудника требовал найти и удалить все его носители вручную, и одну забытую карту никто бы не заметил.
  2. Права зависят от отдела. Бухгалтерии нужна одна дверь, складу — другая. Заводить права каждому сотруднику значит повторять действия и ошибаться при приёме нового человека.
  3. Дверь — это не контроллер. У одного устройства бывает два проёма, и им нужны разные права.

Как устроено

Пользователь (ФИО, должность, фото)
    │
    ├── карта (основная)     ─┐
    ├── карта (брелок)        │ N карт у одного человека
    └── ...                  ─┘
    │
    ├── группа «Бухгалтерия» ──┐
    ├── группа «Склад»        │ M групп у одного человека
    └── ...                  ─┘
              │
              │ права группы
              ▼
           дверь «Входная» ──── контроллер Z5R
           дверь «Склад»   ──── контроллер SKUD-01

Права живут у нас, на контроллер уходит результат. Контроллер про группы и отделы не знает — он получает коды карт и зоны доступа. Отсюда масштабируемость: новый контроллер достаточно завести и выдать ему базу, а не настраивать права заново.

Расчёт прав

Итоговое право на дверь складывается из двух источников:

  1. Группы. Дверь доступна, если она разрешена хотя бы одной группой владельца.
  2. Личные правила. Перекрывают групповые в обе стороны: разрешают дверь вне групп или забирают её, даже если группа разрешает.

Запрет важнее разрешения. Если группа дверь разрешает, а лично она запрещена — доступа нет. Иначе нельзя было бы закрыть доступ человеку, не убирая его из группы.

В интерфейсе у каждой двери видно происхождение права (source):

Источник Что значит
group разрешено группой (перечислены группы)
personal личное разрешение
denied личный запрет перекрыл групповое право

Дверь с denied остаётся в списке намеренно: оператор должен видеть, что доступ есть у группы, но закрыт персонально, — иначе он будет искать причину в настройках группы.

Блокировка владельца

blocked = true закрывает доступ по всем картам сразу. Это главный сценарий увольнения: одна отметка вместо поиска всех носителей.

Карты при этом не удаляются, а помечаются неактивными: при возвращении доступа (отпуск, восстановление карты) не нужно заводить их заново. Запись владельца тоже сохраняется — на неё ссылается история проходов.


Ограничения диапазонов карт

Ключевой факт, выяснившийся на живом парке: контроллеры работают не с Wiegand-26.

Контроллер Z5R WEB BT использует шестибайтовый код: два старших байта — facility, четыре младших — номер карты. То есть facility занимает 16 бит, номер — 32 бита.

Первая версия схемы ограничивала поля по Wiegand-26 (facility 8 бит, номер 16 бит). На живом парке из 21 карты ни одна не помещалась в эти пределы: номера вида 12570523 больше 65535.

Поэтому:

Поле Диапазон Обоснование
facility 0 … 65535 16 бит, формат контроллера
card 0 … 4294967295 32 бита, формат контроллера

Проверка общая (отсекает то, что не примет ни одно устройство). Свои, более узкие пределы проверяет адаптер конкретного вендора: SKUD работает по Wiegand-26 и откажет в карте с большим номером сам.


Режим Accept

Режим означает «дверь открывается всем, поднесённые карты записываются». Нужен для быстрой записи карт на объекте: оператор обходит бригаду и подносит существующие карты, не заводя их вручную.

Режим опасен — на время его действия проём фактически не заперт. Поэтому:

  • Срок ограничен: от 5 до 20 минут. Меньше пяти — оператор не успеет дойти до считывателя. Больше двадцати — риск, что о режиме забудут, перевешивает удобство.
  • Выключается сам по истечении срока. Фоновый обход раз в минуту страхует даже те случаи, когда таймер не сработал.
  • Автор записывается. Если проём окажется открытым в нерабочее время, по журналу должно быть видно, кто включил режим.
  • Счётчик записанных карт показывается оператору: если он не растёт, значит карты не считываются.

Как это устроено в контроллере

Важное расхождение с ожиданием: режим управляется через REST-интерфейс POST /offline, а не командой access_mode протокола WEBJSON.

Проверено на живом контроллере: команды access_mode по WEBJSON нет, и контроллер её молча игнорирует. Через REST работает:

POST /offline  {"mode":5,"state":1}   # включить (setup_mode станет "5")
POST /offline  {"mode":0,"state":0}   # выключить (setup_mode вернётся "0")
GET  /offline                          # {"access_mode":"0","setup_mode":"5","time":"0"}

Значения приходят строками, а не числами — "5", а не 5.

После команды адаптер проверяет setup_mode. Проверка обязательна: на живом устройстве команда уходила без ошибки, а режим не менялся — оператор подносил бы карты в уверенности, что они записываются.


Выдача базы на контроллеры

SyncHolderCards выдаёт карты владельца на контроллеры, где у него есть доступ, и убирает оттуда, где доступа больше нет. Вызывается автоматически:

  • после правки владельца (включая блокировку);
  • после правки группы (права всех участников);
  • после привязки или отвязки карты;
  • после удаления группы или двери.

SyncAll выдаёт всю базу на все контроллеры. Нужен при заведении нового устройства: права у людей уже настроены, а карты на нём ещё не заведены.

Поле sync_pending отмечает карты, ожидающие выгрузки. Полная выдача при каждом изменении переписывала бы всю базу, а контроллер подключён по Wi-Fi — это лишние обращения и риск потерять связь в середине записи.


Интерфейс

Страница /access в Web UI. Отдельно от /acs: там железо (контроллеры, их состояние, события), здесь люди, группы и права. Оператору, который выдаёт пропуск, разбираться в настройках контроллеров не нужно.

Четыре вкладки:

  • Сотрудники — карточки владельцев: фотография, ФИО, должность, отдел, телефон, заметка, признак «доступ закрыт», группы, права по дверям с указанием источника и список карт.
  • Группы — отделы с набором дверей.
  • Двери — точки прохода с привязкой к контроллеру и направлением.
  • Запись карт — режим Accept с выбором длительности.

Права в карточке показываются как результат расчёта, а не как хранимые записи: у каждой двери видно, откуда пришёл доступ — из группы или из личного правила. Иначе при разборе жалобы «у него не открывается» непонятно, что править: состав групп или личное исключение.

Ввод карты со считывателя

Настольные USB-считыватели работают как клавиатура: печатают номер карты цифрами и завершают Enter. Отдельный драйвер не нужен — достаточно перехватить нажатия (CardReaderInput в HolderModal.tsx).

Признак считывателя — скорость набора: пауза между символами у него меньше 80 мс, человек так не печатает. Без этого признака ручной ввод номера превращался бы в случайную карту из первых цифр.

Разбор номера: до 5 цифр и 6–8 цифр считаются Wiegand (первые три цифры — facility, остальные — код), длиннее 8 — десятичный код карты целиком. Проверено: код 0001234567 завелся как 0:1234567.

Карта заводится сразу на контроллер и привязывается к владельцу, чтобы оператор приложил её один раз, а не набирал код руками на двух экранах.


Поиск контроллера после сброса к заводским настройкам

Сброшенный контроллер в сети не находится обычным сканированием, и это не поломка — так задумано производителем.

Перемычка определяет режим работы

На плате контроллера есть перемычка с тремя положениями, и она меняет поведение устройства при подаче питания:

Положение Что происходит
CONFIG Контроллер запускается в режиме настройки. Он не работает с локальной сетью — поднимает собственную точку доступа
NORMAL Обычный режим: модуль связи входит в локальную сеть, веб-интерфейс доступен по его адресу
LOCK То же, но веб-интерфейс настройки заблокирован

Отсюда главное: в режиме CONFIG контроллер по Ethernet молчит. Это не поломка и не потеря связи, а нормальное поведение — устройство перешло на другой канал.

Как попасть в режим настройки

Установить перемычку в CONFIG, подать питание, затем подключиться одним из двух способов:

  • по Wi-Fi — дождаться сети с именем Z5RWEB-BT_XXXXXX и подключиться к ней; пароль — AUTH_KEY;
  • по Ethernet — соединить компьютер и контроллер кабелем, дождаться сообщения о подключении (до 45 секунд) и открыть в браузере http://192.168.10.1.

Логин z5rweb, пароль AUTH_KEY.

192.168.10.1 — настоящий адрес, а не пример из руководства. Он относится не к заводским настройкам, а к режиму настройки: контроллер берёт его, когда подано питание с перемычкой CONFIG. Ошибиться здесь легко: тот же адрес встречается в руководстве как образец, и его можно принять за пример, тогда как он указывает на реальное поведение устройства.

Если в машине нет Wi-Fi, соединиться с точкой доступа контроллера не получится: серверу нужен беспроводной адаптер. Настройку выполняют с ноутбука, планшета или телефона. Вариант по Ethernet этого не требует — достаточно кабеля до контроллера.

Сброс: две перемычки, и порядок важен

Перемычка CLR (№2) стирает память контроллера: карты, историю событий и заданное время открывания (возвращается заводское — 3 секунды).

Если при этом дополнительно установить перемычку в положение №3 (ADD), стираются и конфигурации — контроллер возвращается к заводским настройкам.

Процедура в обоих случаях одна: выключить питание → установить перемычки → включить питание. О завершении стирания сообщает серия коротких звуковых сигналов. Без выключения и повторной подачи питания стирание не выполнится — перемычки читаются только при запуске.

Перемычка CONFIG и сброс мешают друг другу. Если вместе с CLR и ADD установлена ещё и CONF, контроллер стартует в режиме настройки, и стирание может не выполниться. Правильный порядок — двумя отдельными запусками: сначала сброс (CLR и ADD, без CONF), затем настройка (CONF).

Что делать по порядку

  1. Выключить питание. Установить перемычки сброса (CLR и ADD), CONF снять. Подать питание, дождаться серии коротких сигналов.
  2. Выключить питание. Снять перемычки сброса, установить CONF. Подать питание — контроллер поднимется в режиме настройки на 192.168.10.1.
  3. Зайти с логином z5rweb и паролем AUTH_KEY с наклейки на корпусе. Задать адрес из подсети сервера либо включить получение адреса по DHCP, и выбрать нужный режим связи.
  4. Выключить питание. Снять CONF, установить NORMAL. Подать питание.
  5. Контроллер доступен по своему адресу обычным образом — ищется сканированием и заводится в систему.

Заводские настройки модуля связи: Ethernet, режим DHCP, способ связи «Сервер», локальный порт 1000.

Что проверено на живом устройстве

Проверка Итог
Перемычка CONF + питание Контроллер поднялся на 192.168.10.1 и отвечает
Заголовок без пароля Basic realm="Z-5R WEB BT 45006509" — тот же серийный номер, что был до настройки
AUTH_KEY с наклейки Отвергнут (401) — значит пароль остался прежним

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

Признак устройства в ответе на запрос без пароля. Контроллер отвечает кодом 401 и заголовком WWW-Authenticate: Basic realm="Z-5R WEB BT <серийный номер без дефиса>". По этому заголовку контроллер опознаётся надёжно, ещё не имея пароля, и по нему же видно, что за устройство: серийный номер в заголовке отличает один контроллер от другого.

Контроллер может быть виден в сети и без рабочего адреса. Устройство отправляет служебные кадры, поэтому его MAC-адрес появляется в таблице коммутатора. Это подтверждает, что устройство подключено и работает, и отличает этот случай от случая, когда оно выключено. Сопоставлять надо по началу MAC-адреса: у IronLogic это 14:13:30. Когда устройство молчит полностью — ни кадров, ни таблицы — это признак того, что оно выключено или находится в режиме настройки с другим каналом связи.


Как устроен обмен с контроллером

Этот раздел появился потому, что чтение базы карт с контроллера долго не получалось, а без понимания общей схемы обмена причина не находилась.

Контроллер подключается не к серверу, а к конвертеру

ПК ──USB или Ethernet──> конвертер Z-397 ──собственный интерфейс──> контроллер

Конвертер — отдельное устройство той же линейки. Модели: Z-397 (USB), Z-397 IP (Ethernet), Z-397 Web. Контроллеры подключаются к конвертеру, а не к компьютеру напрямую, и у контроллера есть сетевой адрес 1–254 внутри этой внутренней шины. Именно поэтому у Z-5R WEB BT есть поле сетевого адреса, которое ни на что в IP-сети не влияет — оно относится к шине конвертера.

Из этого следует важное практическое ограничение: по сети контроллер доступен только своим веб-интерфейсом и протоколом событий. Полноценная работа с базой карт (чтение и запись ключей) идёт по интерфейсу конвертера, и проверить её можно только при наличии самого конвертера; у нас сейчас он USB, а не Ethernet.

Что это значит для чтения базы

Наш сервер работает с контроллером через WEBJSON — протокол событий и команд. Он даёт: журнал проходов, активацию, запись карт. Чтения базы он не даёт, хотя документация его описывает (раздел 3.9 — команда read_cards). Проверено на живом устройстве: команда уходит корректно, а в ответ приходят только ping. Подробности — в разделе «Открытые вопросы» ниже.

Рабочая замена чтению — считывание карты со считывателя двери. Оно описано там же.

Как устроена запись ключей в контроллере

Контроллер хранит ключи не как пару «facility + номер», а как записи с дополнительными признаками. Это важно при переносе базы на другое устройство:

Признак Что означает
Размер размер номера ключа в байтах (ключ — массив произвольной длины)
Тип простой, мастер, блокирующий
Банк / ячейка номер банка памяти и ячейки в нём
Доступ «Всегда», «Никогда» или номера временных зон
Короткий сравнение номера по 3 байтам (24 бита)
Двойной ключ состоит из двух частей: карта + код клавиатуры
Заблокирован ключ остаётся в памяти, но доступ всегда запрещён

У нас хранятся только facility и card (плюс тип ключа). Временные зоны, банки и ячейки не сохраняются: пока устройство отдаёт события, потерь не видно, но при переносе базы эти параметры придётся задавать заново.

Почему read_cards не работает через WEBJSON

Причина установлена по документации производителя (описание протокола WEBJSON) и проверкой на живой прошивке 2.41.

Протокол чтение описывает, но контроллер его не выполняет. В разделе 3.9 сказано, что read_cards вычитывает все карты и присылает их пачками по 11 штук, а ответом служит документ {"cards":[...]} без поля operation. Мы разобрали этот формат — и всё равно ничего не пришло.

Проверено на живом устройстве: за один сеанс чтения контроллер прислал 1943 документа, и все — ping. Ни одного с полем cards. Команда read_cards при этом уходила корректно, в ответе на ping, с подставленным id.

Асимметрия подтверждена:

Операция Результат
Запись (add_cards) Работает — контроллер отвечает {"success":1}
Чтение (read_cards) Не работает — 1943 ping и ни одного cards

То есть выдать ключ на контроллер мы можем, а снять базу с него — нет.

Проверь также порты контроллера: открыт только веб-порт 80. Порты 1000 (режим TCP-сервера, server.port в конфигурации) и 25000 (client.remport) закрыты. Значит, канала программирования, который использует штатная программа управления, у нас нет.

Что это значит. Выгрузить базу с чужого контроллера нельзя — он покажет только те карты, которые через него провели. Для резервного копирования это ограничение непреодолимо без конвертера Z-397.

Что работает вместо чтения: считывание карты со считывателя двери (см. ниже). Оно даёт номера карт, но не даёт сведений о том, какие карты уже лежат в памяти устройства.

Считывание карты со считывателя двери

Задача: оператору нужно назначить человеку пропуск, но номера карты он не знает — на самой карте номер не напечатан.

Решение: поймать карту из потока событий. Считыватель на двери отдаёт карту событием доступа, поэтому «прочитать карту» здесь означает «подождать событие». Механизм работает всегда, в отличие от read_cards.

Как это устроено:

  1. Оператор нажимает «Ждать карту» на странице «Доступ», вкладка «Запись карт».
  2. Сервер запоминает ожидание на этом контроллере (CardCaptureManager).
  3. Карта подносится к считывателю двери. Контроллер присылает событие.
  4. Обработчик потока событий забирает карту из события до записи в журнал — иначе карта, поднесённая для назначения пропуска, выглядела бы в журнале проходом человека.
  5. Интерфейс опрашивает состояние раз в 2 секунды и показывает номер.

Дверь при этом не открывается всем — этим считывание и отличается от режима записи карт (Accept).

Проверено на живом контроллере: карта 0:16717700 поймана со считывателя двери, тип события access_granted (карта контроллеру известна), в журнал проходов запись не попала.

Ожидание одно на контроллер: считыватель физически один, и второе ожидание означало бы, что карта уйдёт не тому оператору. Срок ограничен (1–30 минут) и истекает сам — если оператор закрыл браузер, следующая карта не попадёт в брошенный сеанс.

Настольный считыватель у оператора

USB-считыватели работают как клавиатура: печатают номер цифрами и завершают Enter. Отдельного драйвера не нужно — интерфейс перехватывает нажатия (CardReaderInput в HolderModal.tsx).

Признак считывателя — скорость набора: пауза между символами у него меньше 80 мс, человек так не печатает. Без этого признака ручной ввод номера превращался бы в случайную карту из первых цифр.

Типы ключей

У контроллеров IronLogic ключ несёт не только «кому можно», но и «для чего он». Три типа, как их называет веб-интерфейс устройства и штатная программа управления:

Тип Значение
simple обычный пропуск: открывает дверь по правам владельца
master служебный: программирует контроллер, дверь не открывает
blocking переключатель режима: в обычном состоянии работает как пропуск

Ограничение протокола. WEBJSON назначение ключа отдельным полем не передаёт: в разделе 3.6 описаны только два бита — 8 (блокирующая карта) и 32 (короткий код, три байта). Поэтому:

  • при записи мастер-ключ выдаётся с флагом блокировки — так контроллер опознаёт ключ, но не открывает дверь, что и требуется от мастер-ключа;
  • при чтении с устройства тип восстанавливается как «простой»: знать, какой ключ был мастером, серверу неоткуда. Мастер-ключом контроллер программируют через штатную программу управления.

Тип хранится в нашей базе, и оператор видит назначение ключа в карточке владельца. Если тип не задан, ключ считается простым — поле появилось позже самих карт, и требовать его от старых клиентов нельзя.

Обходной путь (режим Accept + события access_granted) остаётся рабочим для первичного наполнения, но выгрузить базу с чужого контроллера им нельзя — он покажет только те карты, которые через него провели.

Что это значит для резервных копий

Полное снятие образа контроллера требует канала к конвертеру, которого у нас нет. Поэтому копию имеет смысл строить из нашей базы, которая является источником истины, и дополнять чтением того, что контроллер отдаёт сам.

Список «на выброс» (если чтение с железа так и не получится): карты, заведённые на контроллере в обход сервера. Их видно только через штатную программу управления — то есть при замене контроллера такие карты придётся заводить заново вручную.


Выдача базы на контроллеры

Работает. Проверено на живом Z5R: команды add_cards уходят в ответ на ping контроллера, устройство подтверждает их ответом {"success":1}.

Как проверялась выдача (и на чём легко ошибиться)

Порядок выдачи важен, и две ловушки выглядят как «запись не работает»:

  1. Права считаются от владельца. Контроллеры, где у человека есть доступ хотя бы к одной двери, получают его карты. Карта с привязкой к другому контроллеру не выдаётся — это не ошибка.
  2. Заблокированный владелец теряет доступ везде, и его карты снимаются с контроллеров. В базе оставался тестовый флаг блокировки, и на вопрос «почему карта не выдаётся» отвечал не код выдачи, а права.

Проверять выдачу надо после снятия блокировки, иначе ошибку будешь искать не там.

Отметка о выдаче

sync_pending снимается только после успешной записи на устройство. В UpsertCardOnController была ошибка: при успешном обновлении карты функция выходила до отметки, и карта навсегда оставалась «ожидающей выдачи». В интерфейсе она выглядела невыданной, хотя на контроллере уже была, и оператор выдавал её повторно. Отметка теперь ставится в обоих случаях — и при обновлении, и при добавлении.

Адрес сервера в контроллере

Контроллер обращается к нам по адресу, который ему когда-то записали. Если он указывает на 127.0.0.1, контроллер опрашивает сам себя: ping в логах не появляется, выдача карт висит до таймаута, а /stat показывает conn":"http://127.0.0.1:8080/...".

Адрес берётся из PUBLIC_URL. Она должна быть проброшена в контейнер backend в docker-compose.yml: переменная, лежащая только в .env, внутрь не попадает, и сервис молча подставляет localhost. Проверка:

docker compose exec backend printenv | grep PUBLIC_URL

Вернуть адрес контроллеру можно из интерфейса или запросом POST /acs/controllers/{id}/workmode/server — он перечитает PUBLIC_URL и перезапишет настройку контроллера.


Домофон Beward как устройство доступа

Домофон DS07P-LP заведён в СКУД как контроллер доступа с вендором beward. Причина не в удобстве, а в устройстве объекта.

Как включено на объекте

Реле домофона подключено к входу «кнопка выхода» контроллера Z5R. Это значит: когда дверь открывают с домофона, для Z5R это выглядит нажатием кнопки выхода, а не проходом по карте.

Следствие видно в журнале. Открытие с домофона оставляет в Z5R запись exit_button — и по ней нельзя понять, кто открыл дверь и откуда: с домофона, из веб-интерфейса Z5R или с настоящей кнопки у двери. Все три случая выглядят одинаково.

Поэтому журнал ведём у себя. Открытие через наш интерфейс попадает в журнал СКУД с причиной и указанием, что оно отправлено домофоном. Без этого о таком открытии не остаётся следа.

Важно учитывать при разборе журнала: одно открытие с домофона даёт две записи — нашу (с причиной) и exit_button от Z5R. Это не два разных прохода, а одно действие, увиденное с двух сторон.

Дверь одна

Устройство про двери ничего не знает: у него реле, а не дверь. Поэтому в списке одна дверь с адресом relay1 и названием «Дверь (реле домофона)». Выдавать три выхода, которыми никто не управляет, значило бы показать оператору кнопки, ведущие в никуда.

Открытие даёт импульс на одну секунду — ровно то, что делает кнопка выхода. Длительность задаётся в самом устройстве, поэтому в команде не передаётся.

Состояние двери — «неизвестно», и это честно

Устройство сообщает только тревоги, и на проверенном экземпляре они всегда отвечают NO Alarm. Положение двери — открыта она или заперта — из домофона узнать нельзя.

Поэтому поля «заперто» и «открыто» намеренно не выставляются, а состояние в списке показывается словом unknown. Показать «заперто» там, где мы этого не знаем, — хуже, чем показать неизвестность: оператор не пойдёт проверять то, что ему подтвердили как исправное. На такой ошибке в этом проекте уже попадались часы камеры, показывавшие «точно» при непрочитанном времени.

События от домофона пока не приходят

Домофон не отдаёт журнал событий по запросу — он сам обращается к серверу, когда что-то происходит (вызов, открытие двери, прочитанный номер). На проверенном экземпляре эта отправка выключена.

Опрос вместо отправки не годится: единственное доступное чтением — состояние тревог, и оно отвечает NO Alarm всегда. Опрос давал бы пустоту, но с постоянными обращениями к устройству.

Поэтому подписка на события возвращает открытый молчащий канал. Выдуманных записей в журнале не будет: запись о проходе должна означать, что событие действительно произошло.

Что проверено на живом устройстве

Что Итог
Связь (Ping) Работает — в списке контроллеров значится online
Список дверей Работает — одна дверь найдена
Состояние двери Отвечает NO Alarm (положение двери недоступно)
Открытие двери Не проверялось — проверка означает открыть реальную дверь

Адрес, имена параметров и значения команды открытия выверены по описанию интерфейса и покрыты проверками на подставном устройстве: команда собирается именно та, что замыкает реле. Но сработает ли она на живой двери, покажет только открытие.

Двусторонний звук и SIP

Устройство уже настроено на объекте: регистрация на SIP-сервере 192.168.1.68, счёт 101, вызовы уходят на номера 200 и 111. Нажатие цифр 123 во время разговора замыкает реле и завершает вызов.

Двусторонний звук со стороны устройства включён (DoubleAudio=on): микрофон и динамик отдаются в RTSP-поток. То есть звук уже есть в потоке, который мы и так принимаем, — отдельная настройка устройства не нужна.

Что для этого нужно с нашей стороны — отдельная работа, в этот заход не входила.


API

Метод Путь Назначение
GET/POST /acs/holders список и создание владельцев
GET/PUT/DELETE /acs/holders/{id} карточка владельца
POST/GET /acs/holders/{id}/photo фотография (загрузка телом запроса)
POST /acs/holders/{id}/cards привязать карту
DELETE /acs/holders/cards/{cardID} отвязать карту
GET/POST /acs/groups группы доступа
GET/PUT/DELETE /acs/groups/{id} группа с правами
GET/POST /acs/doors двери
PUT/DELETE /acs/doors/{id} дверь
POST /acs/sync-all выдать всю базу
POST /acs/check-access проверка доступа по карте
GET/POST/DELETE /acs/controllers/{id}/accept режим Accept
GET/POST/DELETE /acs/controllers/{id}/capture считывание карты со считывателя двери

Про фотографии

Формат хранения — minio:<key> или local:<путь>, как у эталонных снимков лиц. В базе хранится путь, а не двоичные данные: снимки по 200–500 КБ на несколько сотен карт превратили бы базу в архив изображений.

Отдаётся фотография через сервер, а не ссылкой на MinIO: presigned-ссылка привязана к имени хоста и не работает снаружи, а снимок нужен в интерфейсе по обычному тегу img.


Открытые вопросы

Чтение карт с контроллера (read_cards)

Не работает. Команда уходит с форматом, точно совпадающим с документацией (раздел 3.9):

{"id":1073741825,"operation":"read_cards"}

Но контроллер не отвечает ни подтверждением {"id":N,"success":1}, ни документом cards. При этом он присылает ping каждые 10 секунд, то есть активен и связь есть.

Наблюдение: контроллер не подтверждает ни одну команду сервера, кроме set_active. Команды через REST (open_door, режим Accept) при этом работают.

Гипотеза для проверки: команды чтения и записи карт у Z5R доступны только через REST-интерфейс, а WEBJSON поддерживает лишь события и активацию. Прямые REST-запросы /cards, /read_cards, /keys возвращают 404.

Обходной путь, который работает: включить режим Accept, поднести карты — они приходят событиями access_granted с кодом карты, и оттуда записываются в базу. Это надёжнее для первичного наполнения, но не заменяет чтение: без него нельзя снять резервную копию с контроллера, который работал автономно.

Проверено на живом контроллере: режим Accept (POST /offline {"mode":5,"state":1}) переводит setup_mode в 5 и возвращает в 0 при выключении. Кнопки интерфейса проверены end-to-end — состояние на железе меняется.

Причина установлена (см. раздел «Как устроен обмен с контроллером»): чтение ключей идёт через конвертер Z-397, а не через WEBJSON. Наш контроллер подключён к USB-конвертеру, поэтому канала для программирования у сервера нет. Возвращаться к этой задаче имеет смысл только при появлении доступа к конвертеру.

Резервное копирование контроллеров

Не реализовано. План: снимок контроллера (карты + настройки) в JSON в MinIO, восстановление заливкой на новый контроллер. Упирается в ту же проблему чтения карт.

Расписания доступа

Схема готова (ACSSchedule, ACSZone), но не используется: сейчас доступ либо есть, либо нет, и контроллеру выдаётся полный доступ по времени (tz = 255). Добавляется без изменения структуры прав.