Реализация управления системой контроля доступа (СКУД) через мобильное приложение
Контроллеры СКУД — Suprema BioStar, HID OSDP, Ironlogic Z-5R, RusGuard — управляются через проприетарные API или стандарт OSDP v2. Мобильное приложение в такой системе решает несколько несвязанных задач одновременно: мониторинг событий прохода, управление правами (добавить/удалить карту), открытие двери удалённо, просмотр видео с камеры у входа.
Интеграция с контроллерами: REST API vs OSDP
Большинство современных СКУД предоставляют REST API поверх своей базы данных. Suprema BioStar 2 — типичный пример: /api/v1/doors, /api/v1/events, /api/v1/users. Аутентификация через JWT с коротким TTL (обычно 30 минут), рефреш по refresh_token.
Прямой OSDP через TCP — редкость в мобильных приложениях, это скорее территория промышленных интеграторов. Для мобильного клиента всегда есть API-слой.
Структура типичного события прохода из BioStar 2 API:
data class AccessEvent(
val id: Long,
val timestamp: Instant,
val deviceId: Long,
val doorId: Long,
val userId: Long?, // null если карта не опознана
val cardNumber: String?,
val eventCode: Int, // 0=вход разрешён, 1=вход запрещён, 2=выход
val temperature: Double?, // если терминал с измерением температуры
val imageData: String?, // base64 фото с камеры терминала
)
Живая лента событий через WebSocket (BioStar 2 поддерживает /ws/events):
class AcsEventStream(private val url: String, private val token: String) {
fun observe(): Flow<AccessEvent> = callbackFlow {
val client = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.build()
val ws = client.newWebSocket(
Request.Builder().url(url)
.header("Authorization", "Bearer $token").build(),
object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val event = Json.decodeFromString<AccessEvent>(text)
trySend(event)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
close(t)
}
}
)
awaitClose { ws.close(1000, null) }
}
}
Удалённое открытие двери
Самая чувствительная функция — открытие двери по нажатию в приложении. Три требования: авторизация запроса, аудит действия, защита от replay-атаки.
suspend fun unlockDoor(doorId: Long) {
// Подпись запроса timestamp + doorId + userId чтобы исключить replay
val timestamp = System.currentTimeMillis()
val payload = "$timestamp:$doorId:${authService.userId}"
val signature = hmacSha256(payload, authService.apiSecret)
api.unlockDoor(DoorUnlockRequest(
doorId = doorId,
timestamp = timestamp,
signature = signature,
reason = "manual_unlock_mobile",
))
// Событие логируется на сервере автоматически через API
}
На сервере проверяем: timestamp не старше 30 секунд, подпись валидна, у пользователя есть право на эту дверь. Без этих проверок перехваченный запрос можно воспроизвести.
Управление правами и картами
Добавление карты пользователю — типичная задача для HR или охраны. Форма в приложении: выбор пользователя, ввод или считывание номера карты (NFC через android.nfc.NfcAdapter или CoreNFC на iOS), выбор дверей и временного расписания доступа:
Future<void> assignCard(int userId, String cardNumber, List<int> doorIds,
AccessSchedule schedule) async {
await _api.post('/users/$userId/credentials', body: {
'type': 'card',
'card_number': cardNumber,
'doors': doorIds,
'schedule_id': schedule.id,
'valid_from': schedule.from.toIso8601String(),
'valid_until': schedule.until?.toIso8601String(),
});
}
NFC-считывание номера карты Mifare Classic — удобная фича: охранник прикладывает карту к телефону вместо ручного ввода 10-значного числа.
Фото и видео у входа
Терминалы с камерой (Suprema FaceStation, ZKTeco SpeedFace) отдают фото в событии прохода (base64 JPEG ~15-30 KB). Отображаем в ленте событий через CachedNetworkImage или декодируем base64 напрямую. IP-камеры у входа (Hikvision, Dahua) через RTSP-стрим — во Flutter через flutter_vlc_player или media_kit.
Разработка мобильного приложения для СКУД с лентой событий, удалённым управлением дверями и управлением картами: 4–7 недель. Стоимость рассчитывается индивидуально после изучения API конкретной системы и требований к безопасности.







