От диплома до продакшена: Как я создавал архитектуру ИИ-проекта для…
Автор: Алексей Бобрешов, руководитель отдела искусственного интеллекта в федеральном холдинге Категория: Искусственный интеллект, безопасность, умный дом, приватность *Это продолжение серии статей.
Введение: Ошибки, которые я осознал (слишком поздно - нет, нет ничего слишком, есть цена ошибки)
Когда я начинал работу над дипломным проектом «Умный дом» в 2020–2021 годах, моя голова была забита другими вопросами:
Как добиться точности распознавания выше 90%?
Как оптимизировать нейросеть для работы на слабом железе?
Как интегрировать распознавание команд с реальными устройствами?
**О безопасности я практически не думал. Ну это логично… Зачем - это же диплом-проект?! **
Сегодня, имея опыт работы над коммерческими ИИ-проектами в крупных компаниях, я понимаю: безопасность — это не базовый набор, это фундамент. И если вы не заложили его с самого начала, перестройка будет болезненной.
В этой статье (Часть 6) я расскажу:
Какие уязвимости я обнаружил в своем дипломном проекте постфактум
Как защитить голосовое управление от утечек и взломов (ИМХО)
Какие архитектурные решения я бы изменил, начни я проект сегодня
Практические рекомендации для тех, кто создает ИИ-системы
Глава 1. Взгляд в прошлое: Что я упустил при прототипировании
1.1. Мои «слепые зоны» в 2020–2021
Давайте честно: когда ты студент, работающий над дипломом, твои приоритеты выглядят так:
Функциональность — чтобы работало
Точность — чтобы работало хорошо
Производительность — чтобы работало быстро
Безопасность — а что это вообще такое?
В моем дипломе были реализованы:
Распознавание голосовых команд с точностью 94.06%
Интеграция с устройствами умного дома
Обучение до 250 эпох, в борьбе с переобучением
Но не было:
Шифрования голосовых данных
Защиты от replay-атак
Аудита и логирования доступа
Изоляции сетевых сегментов
1.2. Уязвимости, которые я обнаружил позже
Когда я начал работать над коммерческими проектами, мой взгляд на безопасность кардинально изменился. Вот что я понял:
Уязвимость №1: Голосовые данные передаются в открытом виде
В моем дипломе аудиопоток передавался от микрофона к нейросети без шифрования. В локальной сети это еще норм, но если представить, что система выходит в интернет…
Риск: Перехват голосовых команд, включая потенциально чувствительную информацию (пароли, адреса, персональные данные).
Уязвимость №2: Нет разграничения прав доступа
Система выполняла команды любого, кто их произнес. Нет понятия «пользователь», «администратор», «гость». (поправочка: предусматривалась разработка, т.е. я думал про этот пункт)
Риск: Любой человек в радиусе слышимости может выключить сигнализацию, открыть дверь или получить доступ к конфиденциальной информации.
Уязвимость №3: Отсутствие защиты от replay-атак
Если злоумышленник запишет вашу голосовую команду «открой дверь», он сможет воспроизвести ее позже.
Риск: Обход системы аутентификации через запись и воспроизведение команд.
Уязвимость №4: Нейросеть как black box
Я не логировал, какие команды были распознаны, кто их отдал, когда и при каких обстоятельствах.
Риск: Невозможность расследования инцидентов, отсутствие аудита.
Глава 2. Безопасность голосового управления: Угрозы и решения
2.1. Типы угроз для голосовых систем ИИ
Давайте систематизируем угрозы:
Тип угрозы | Описание | Пример |
|---|---|---|
Перехват данных | Перехват голосовых команд при передаче | Сниффинг трафика в Wi-Fi сети |
Replay-атаки | Запись и воспроизведение команд | Запись команды «открой дверь» |
Spoofing | Подделка голоса | Deepfake аудио, синтез голоса |
Несанкционированный доступ | Выполнение команд посторонними | Гость отдает команды хозяина |
Утечка данных | Компрометация хранимых данных | Кража базы голосовых профилей |
Adversarial attacks | Специально созданные команды | Скрытые команды в аудио |
2.2. Архитектура безопасности: Многоуровневая защита
На основе моего опыта, я рекомендую следующую архитектуру:
┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 1: ФИЗИЧЕСКАЯ │ ├─────────────────────────────────────────────────────────────┤ │ • Изоляция микрофонов (аппаратное отключение) │ │ • Индикаторы активности микрофона (LED) │ │ • Физическая защита устройств │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 2: СЕТЕВАЯ БЕЗОПАСНОСТЬ │ ├─────────────────────────────────────────────────────────────┤ │ • Шифрование трафика (TLS 1.3) │ │ • Сегментация сети (VLAN для IoT) │ │ • Firewall и фильтрация трафика │ │ • VPN для удаленного доступа │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 3: АУТЕНТИФИКАЦИЯ │ ├─────────────────────────────────────────────────────────────┤ │ • Распознавание голоса (speaker verification) │ │ • Multi-factor authentication (голос + PIN/биометрия) │ │ • Session management (таймауты сессий) │ │ • Защита от replay-атак (nonce, timestamps) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 4: АВТОРИЗАЦИЯ │ ├─────────────────────────────────────────────────────────────┤ │ • RBAC (Role-Based Access Control) │ │ • Гранулярные права доступа │ │ • Контекстная авторизация (время, место, устройство) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 5: ЗАЩИТА ДАННЫХ │ ├─────────────────────────────────────────────────────────────┤ │ • Шифрование данных at rest (AES-256) │ │ • Анонимизация и псевдонимизация │ │ • Secure storage (HSM, TPM) │ │ • Data retention policies │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ УРОВЕНЬ 6: МОНИТОРИНГ И АУДИТ │ ├─────────────────────────────────────────────────────────────┤ │ • Логирование всех команд и событий │ │ • SIEM системы (Security Information & Event Management) │ │ • Аномалии детекшн (ML-based) │ │ • Регулярные security audits │ └─────────────────────────────────────────────────────────────┘
2.3. Практическая реализация: Что я бы изменил в своем дипломе
Если бы я начинал проект сегодня, вот конкретные изменения:
Изменение №1: Speaker Verification (распознавание говорящего)
Планировал но не сделал: Система распознавала только команду, но не того, кто ее отдал.
Стало бы: Двухэтапная проверка:
Кто говорит? (Speaker Verification)
Что говорит? (Speech Recognition)
# Пример архитектуры class SecureVoiceControl: def __init__(self): self.speaker_verifier = SpeakerVerificationModel() self.command_recognizer = CommandRecognitionModel() self.authorizer = AuthorizationEngine() def process_command(self, audio): # Этап 1: Верификация диктора speaker_id = self.speaker_verifier.identify(audio) if not speaker_id: raise UnauthorizedError("Unknown speaker") # Этап 2: Распознавание команды command = self.command_recognizer.recognize(audio) # Этап 3: Авторизация if not self.authorizer.can_execute(speaker_id, command): raise ForbiddenError(f"User {speaker_id} cannot execute {command}") # Этап 4: Выполнение с логированием self.audit_log(speaker_id, command) return self.execute(command)
Изменение №2: Защита от Replay-атак
Было: Команды выполнялись без проверки уникальности.
Стало бы: Использование nonce и timestamps:
import time import hashlib import secrets class ReplayProtection: def __init__(self): self.used_nonces = set() self.nonce_ttl = 300 # 5 минут def generate_challenge(self): """Генерация уникального challenge""" nonce = secrets.token_hex(16) timestamp = int(time.time()) return f"{nonce}:{timestamp}" def verify_response(self, challenge, response, audio): """Проверка ответа на challenge""" nonce, timestamp = challenge.split(':') # Проверка свежести if time.time() - int(timestamp) > self.nonce_ttl: raise ReplayAttackError("Challenge expired") # Проверка уникальности nonce if nonce in self.used_nonces: raise ReplayAttackError("Nonce already used") # Проверка подписи expected_signature = hashlib.sha256( f"{nonce}{audio}".encode() ).hexdigest() if response != expected_signature: raise ReplayAttackError("Invalid signature") # Отметка nonce как использованного self.used_nonces.add(nonce) # Очистка старых nonce self.cleanup_old_nonces()
Изменение №3: Шифрование данных
Было: Аудиоданные передавались в открытом виде.
Стало бы: End-to-end шифрование:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import os class EncryptedAudioStream: def __init__(self, key): self.key = key self.backend = default_backend() def encrypt_audio(self, audio_data): """Шифрование аудиопотока""" iv = os.urandom(16) cipher = Cipher( algorithms.AES(self.key), modes.GCM(iv), backend=self.backend ) encryptor = cipher.encryptor() ciphertext = encryptor.update(audio_data) + encryptor.finalize() return { 'ciphertext': ciphertext, 'iv': iv, 'tag': encryptor.tag } def decrypt_audio(self, encrypted_data): """Расшифровка аудиопотока""" cipher = Cipher( algorithms.AES(self.key), modes.GCM( encrypted_data['iv'], encrypted_data['tag'] ), backend=self.backend ) decryptor = cipher.decryptor() return decryptor.update(encrypted_data['ciphertext']) + decryptor.finalize()
Изменение №4: RBAC (Role-Based Access Control)
Было: Все команды выполнялись без проверки прав.
Стало бы: Гранулярная система прав:
from enum import Enum from dataclasses import dataclass from typing import Set, Dict class Permission(Enum): LIGHT_ON = "light:on" LIGHT_OFF = "light:off" DOOR_UNLOCK = "door:unlock" THERMOSTAT_CHANGE = "thermostat:change" CAMERA_VIEW = "camera:view" ADMIN_ACCESS = "admin:all" class Role(Enum): GUEST = "guest" FAMILY = "family" ADMIN = "admin" @dataclass class User: id: str name: str role: Role class AuthorizationEngine: def __init__(self): self.role_permissions: Dict[Role, Set[Permission]] = { Role.GUEST: { Permission.LIGHT_ON, Permission.LIGHT_OFF, }, Role.FAMILY: { Permission.LIGHT_ON, Permission.LIGHT_OFF, Permission.THERMOSTAT_CHANGE, Permission.CAMERA_VIEW, }, Role.ADMIN: { Permission.LIGHT_ON, Permission.LIGHT_OFF, Permission.DOOR_UNLOCK, Permission.THERMOSTAT_CHANGE, Permission.CAMERA_VIEW, Permission.ADMIN_ACCESS, } } def can_execute(self, user: User, command: str) -> bool: """Проверка прав выполнения команды""" command_permission = self.command_to_permission(command) return command_permission in self.role_permissions[user.role] def command_to_permission(self, command: str) -> Permission: """Конвертация команды в permission""" mapping = { "включи свет": Permission.LIGHT_ON, "выключи свет": Permission.LIGHT_OFF, "открой дверь": Permission.DOOR_UNLOCK, "измени температуру": Permission.THERMOSTAT_CHANGE, "покажи камеру": Permission.CAMERA_VIEW, } return mapping.get(command, Permission.LIGHT_ON)
Изменение №5: Аудит и логирование
Было: Никакого логирования.
Стало бы: Детальное логирование всех событий:
import logging import json from datetime import datetime from typing import Dict, Any class SecurityAuditLogger: def __init__(self, log_file: str = "security_audit.log"): self.logger = logging.getLogger("security_audit") self.logger.setLevel(logging.INFO) handler = logging.FileHandler(log_file) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) self.logger.addHandler(handler) def log_command_execution(self, user_id: str, command: str, success: bool, context: Dict[str, Any] = None): """Логирование выполнения команды""" event = { "event_type": "command_execution", "timestamp": datetime.utcnow().isoformat(), "user_id": user_id, "command": command, "success": success, "context": context or {}, "ip_address": context.get("ip_address"), "device_id": context.get("device_id"), } self.logger.info(json.dumps(event)) # Alert на подозрительную активность if not success: self.log_security_alert("failed_command", event) def log_security_alert(self, alert_type: str, event: Dict): """Логирование событий безопасности""" alert = { "alert_type": alert_type, "severity": self.calculate_severity(alert_type), "timestamp": datetime.utcnow().isoformat(), "event": event } self.logger.warning(f"SECURITY_ALERT: {json.dumps(alert)}") def calculate_severity(self, alert_type: str) -> str: """Определение уровня критичности""" severity_map = { "failed_command": "LOW", "replay_attack_detected": "HIGH", "unauthorized_access": "CRITICAL", "brute_force_detected": "HIGH", } return severity_map.get(alert_type, "MEDIUM")
Глава 3. Приватность: GDPR, 152-ФЗ и этические аспекты
3.1. Правовое регулирование
При работе с голосовыми данными вы попадаете под действие:
В России:
152-ФЗ «О персональных данных»
Голосовые данные — это биометрические персональные данные
Требуется письменное согласие на обработку
Обязательная локализация баз данных на территории РФ
Уведомление Роскомнадзора
В Европе:
GDPR (General Data Protection Regulation)
Статья 9: Особые категории данных (биометрия)
Право на забвение (статья 17)
Privacy by Design (статья 25)
Штрафы до 4% от годового оборота или 20 млн евро
3.2. Принципы Privacy by Design
При проектировании системы я рекомендую следовать принципам:
Принцип 1: Минимизация данных
Собирайте только то, что действительно нужно:
# НЕЛЬЗЯ: Сохранять все аудиозаписи def process_voice(audio): save_to_database(audio) # ❌ command = recognize(audio) return command # НУЖНО: Обрабатывать и удалять def process_voice(audio): command = recognize(audio) delete_audio(audio) # ✅ save_command_metadata(command) # Только метаданные return command
Принцип 2: Локальная обработка
По возможности обрабатывайте данные локально:
# Предпочтительно: Edge computing class LocalVoiceProcessor: def __init__(self): self.model = load_on_device_model() def process(self, audio): # Все данные остаются на устройстве return self.model.predict(audio)
Принцип 3: Прозрачность
Информируйте пользователей (Инструкцией к ПО):
class PrivacyNotice: def __init__(self): self.notice = """ Мы собираем следующие данные: - Голосовые команды (для распознавания) - Время выполнения команд (для аудита) Мы НЕ собираем: - Фоновые разговоры - Биометрические шаблоны (после верификации) Ваши права: - Запросить копию данных - Удалить данные - Отозвать согласие """
Принцип 4: Контроль пользователя
Предоставьте инструменты управления:
class UserDataController: def export_user_data(self, user_id: str) -> bytes: """Экспорт всех данных пользователя (GDPR Article 20)""" data = { "voice_commands": self.get_commands(user_id), "voice_profile": self.get_profile(user_id), "audit_logs": self.get_logs(user_id), } return json.dumps(data).encode() def delete_user_data(self, user_id: str): """Полное удаление данных (GDPR Article 17)""" self.delete_commands(user_id) self.delete_profile(user_id) self.delete_logs(user_id) self.revoke_consent(user_id)
3.3. Этические аспекты ИИ в умном доме
Проблема 1: Постоянное прослушивание
Даже если система «слушает» только wake word («Алиса», «Салют»), это создает ощущение постоянного наблюдения.
Решение:
Аппаратное отключение микрофона (физическая кнопка)
Визуальная индикация (LED при активном микрофоне)
Локальная обработка wake word (не отправлять в облако)
Проблема 2: Дети и уязвимые группы
Дети могут не осознавать, что их данные собираются.
Решение:
Детский режим (ограниченные команды, повышенная приватность)
Родительский контроль
Автоматическое удаление детских записей
Проблема 3: Дискриминация алгоритмов
Нейросети могут хуже распознавать:
Акценты
Детские голоса
Голоса пожилых людей
Люди с нарушениями речи
Решение:
Разнообразные тренировочные данные
Тестирование на разных демографических группах
Альтернативные способы ввода (текст, жесты)
Глава 4. Security Testing: Как тестировать безопасность (за этот блок: спасибо коллегам из текущего места работы)
4.1. Методология тестирования
Этап 1: Threat Modeling
Используйте методологию STRIDE:
Spoofing (подделка личности)
Tampering (несанкционированное изменение)
Repudiation (отказ от действий)
Information Disclosure (раскрытие информации)
Denial of Service (отказ в обслуживании)
Elevation of Privilege (повышение привилегий)
Этап 2: Penetration Testing
Примеры тестов:
class VoiceSecurityTester: def test_replay_attack(self): """Тест на replay-атаку""" # Запись команды original_audio = record_command("открой дверь") original_response = send_command(original_audio) # Повторная отправка той же записи replay_response = send_command(original_audio) # Ожидаем блокировку assert replay_response.status == "BLOCKED", "Replay attack succeeded!" def test_spoofing_attack(self): """Тест на подделку голоса""" # Синтез голоса через TTS fake_audio = tts_synthesize("открой дверь", target_voice="admin") response = send_command(fake_audio) # Ожидаем отказ assert response.status == "UNAUTHORIZED", "Voice spoofing succeeded!" def test_adversarial_attack(self): """Тест на adversarial примеры""" # Создание adversarial audio clean_audio = record_command("включи свет") adversarial_audio = add_adversarial_noise( clean_audio, target_command="открой дверь" ) response = send_command(adversarial_audio) # Ожидаем распознавание оригинальной команды assert response.command == "включи свет", "Adversarial attack succeeded!" def test_privacy_leak(self): """Тест на утечку данных""" # Отправка команды send_command("какой у меня пароль?") # Перехват сетевого трафика network_traffic = capture_network_traffic() # Проверка на наличие чувствительных данных assert "пароль" not in network_traffic, "Privacy leak detected!"
Этап 3: Code Review
Чеклист для code review:
[ ] Все данные шифруются при передаче (TLS)
[ ] Все данные шифруются при хранении (AES-256)
[ ] Пароли и ключи не захардкожены
[ ] Реализована защита от replay-атак
[ ] Есть логирование security events
[ ] Реализован rate limiting
[ ] Есть input validation
[ ] Нет уязвимостей (SQL injection, XSS, etc.)
Этап 4: Compliance Audit
Проверка соответствия:
152-ФЗ (для РФ)
GDPR (для ЕС)
Отраслевым стандартам (если есть)
4.2. Инструменты для тестирования
Для сетевого анализа:
Wireshark
tcpdump
Burp Suite
Для fuzzing:
AFL (American Fuzzy Lop)
libFuzzer
custom fuzzers для аудио
Для статического анализа:
SonarQube
Bandit (для Python)
Semgrep
Для динамического анализа:
OWASP ZAP
Metasploit
Глава 5. Практические рекомендации: Чеклист безопасности
5.1. Чеклист для разработчиков
Архитектура:
[ ] Используется defense in depth (многоуровневая защита)
[ ] Реализована сегментация сети
[ ] Есть изоляция критических компонентов
[ ] Используется principle of least privilege
Аутентификация:
[ ] Реализована multi-factor authentication
[ ] Есть защита от brute force (rate limiting, account lockout)
[ ] Используются secure сессии (timeout, rotation)
[ ] Реализована защита от replay-атак
Шифрование:
[ ] TLS 1.3 для передачи данных
[ ] AES-256 для хранения данных
[ ] Secure key management (HSM, KMS)
[ ] Regular key rotation
Приватность:
[ ] Data minimization (только необходимые данные)
[ ] Purpose limitation (только заявленные цели)
[ ] Storage limitation (автоматическое удаление)
[ ] Privacy by design и by default
Мониторинг:
[ ] Логирование всех security events
[ ] SIEM система
[ ] Alerting на аномалии
[ ] Regular security audits
Разработка:
[ ] Secure coding practices
[ ] Code review с фокусом на безопасность
[ ] SAST/DAST инструменты
[ ] Dependency scanning (уязвимости в библиотеках)
5.2. Чеклист для пользователей
Если вы используете умный дом:
Настройки:
[ ] Измените дефолтные пароли
[ ] Включите двухфакторную аутентификацию
[ ] Отключите ненужные функции
[ ] Проверьте разрешения приложений
Сеть:
[ ] Используйте отдельную VLAN для IoT
[ ] Включите WPA3 на Wi-Fi
[ ] Отключите WPS
[ ] Обновите прошивку роутера
Приватность:
[ ] Проверьте, какие данные собираются
[ ] Отключите сбор данных, если возможно
[ ] Регулярно очищайте историю команд
[ ] Используйте локальную обработку
Физическая безопасность:
[ ] Используйте физические переключатели для микрофонов
[ ] Размещайте устройства вне прямой видимости с улицы
[ ] Защищайте устройства от физического доступа
Глава 6. Будущее безопасности голосовых систем
6.1. Emerging Threats
Deepfake Voice Attacks
С развитием генеративного ИИ (GPT, Tacotron, WaveNet) создание реалистичных подделок голоса становится тривиальным.
Защита:
Liveness detection (проверка «живости» голоса)
Multi-modal authentication (голос + лицо + поведение)
Continuous authentication (постоянная проверка в течение сессии)
Adversarial Machine Learning
Специально созданные аудиокоманды, неслышимые для человека, но распознаваемые ИИ.
Защита:
Adversarial training (обучение на adversarial примерах)
Input sanitization (очистка входных данных)
Ensemble models (ансамбли моделей для детекции аномалий)
Supply Chain Attacks
Компрометация библиотек, фреймворков, обновлений прошивки.
Защита:
Code signing (подпись кода)
Secure boot (безопасная загрузка)
SBOM (Software Bill of Materials)
Dependency verification
6.2. Privacy-Enhancing Technologies (PETs)
Federated Learning
Обучение моделей без передачи сырых данных:
Данные остаются на устройстве
Передаются только градиенты
Differential privacy для защиты градиентов
Homomorphic Encryption
Вычисления на зашифрованных данных:
Данные никогда не расшифровываются
Медленно, но безопасно
Подходит для критических операций
Secure Multi-Party Computation (MPC)
Совместные вычисления без раскрытия данных:
Несколько сторон участвуют в вычислениях
Никто не видит данные других
Cryptographic guarantees
6.3. Regulatory Trends
Ожидаемые изменения:
EU AI Act
Классификация ИИ по уровню риска
Голосовые системы — высокий риск
Обязательная сертификация
US Executive Order on AI
Standards for AI safety
Red-teaming requirements
Transparency obligations
Russia
Развитие 152-ФЗ для ИИ
Требования к локализации
Обязательная сертификация
Заключение: Безопасность — это процесс, а не продукт
Когда я начинал свой дипломный проект в 2020 году, я думал о безопасности как о «фиче», которую можно добавить потом.
Сегодня я понимаю: это была ошибка.
Безопасность — это:
Архитектурное решение, а не патч
Культура разработки, а не чеклист
Непрерывный процесс, а не разовое мероприятие
Баланс между удобством и защитой, а не компромисс
Что я бы сказал себе в 2020:
Начни с threat modeling — пойми угрозы до написания кода
Используй security by design — закладывай защиту в архитектуру
Тестируй на безопасность — не только на функциональность
Учись continuously — угрозы эволюционируют
Не доверяй, проверяй — zero trust architecture
Для тех, кто начинает сегодня:
Не повторяйте моих ошибок. Безопасность — это не «потом». Это «сейчас».
Ваш умный дом должен быть не только умным, но и безопасным. Мой дом - моя крепость
Призыв к действию
Если вы создаёте нейросети и ИИ-системы:
Сразу, с первого дня, думайте о безопасности — хотя можно сделать её потом (но до внедрения)
Регулярно проверяйте систему: нет ли дыр и уязвимостей (не просто так есть тестировщики)
Учитесь сами: как защищать свой ИИ от взлома и атак
Если вы руководите продуктом (продукт-менеджер):
Сделайте правило: задача считается готовой, только если она безопасная
Закладывайте деньги в бюджет на тестирование безопасности
Ставьте в приоритет функции, которые защищают личные данные пользователей
Если вы обычный пользователь:
Требуйте от компаний, чтобы они честно рассказывали, как работают их ИИ
Настраивайте приватность в приложениях и соцсетях (не оставляйте как попало)
Регулярно обновляйте свои устройства — телефон, ноутбук, планшет (даже не ИИ)
Читать Часть 1: «От диплома до продакшена: Как я создавал архитектуру ИИ-проекта для… Часть 1: Что я хотел видеть дома в 2021»
Читать Часть 5: «Интеграция с устройствами «Умного дома» — от модели к реальному устройству»
Автор: Алексей Бобрешов, руководитель отдела искусственного интеллекта
Лицензия: CC BY-NC 4.0
