Привет, Хабр! Когда делал ремонт у меня возникла идея сделать систему управления потоками аудио-видео между источниками и устройствами воспроизведения, чтобы, когда жена выгоняет из комнаты, нажать одну кнопку на телефоне и продолжить смотреть суточный марафон 24 часа Ле Мана в другой комнате. И чтобы всё работало с мобильного устройства.

Исходные данные: есть кабельное телевидение, сервер для хранения видео, игровая приставка, ПК и несколько ТВ в разных комнатах, на которых все это хочется запускать. Если с видео сервером проблем нет — на нем развернут DLNA-сервер, то все остальное будет требовать ручного коммутирования между источниками сигналов и ТВ (вариант с пучками HDMI-кабелей между комнатами не рассматривался из-за переизбыточности и сложности монтажа).

Принципиальная схема работы

В центре всей системы управления должен быть HDMI-матричный коммутатор (коммутирует любой вход на любой выход) с возможностью подключения к нему управляющего оборудования, например, через последовательный порт. Помимо него нужно само управляющее оборудование с соответствующим ПО и клиентские устройства для удаленного управления. Итоговая схема выглядит примерно так:

Поискав оборудование в интернетах, нашёл самый дешёвый подходящий вариант 4x4 коммутатора с поддержкой 4k 60fps и управлением по RS-232 на Aliexpress (впрочем, не удивительно, что именно там).

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

Однако, при условии работы в нескольких комнатах, пульт становится бесполезным.

Включив коммутатор в розетку, я подумал, что где-то стартует двигатель самолета —относительно небольшой коммутатор гудит как сервер, который пару лет не пылесосили (в дальнейшем удалось существенно понизить шум заменой вентиляторов). 

Но, стоит отдать должное, подключив источники ввода-вывода, выяснилось, что работает он именно так, как я и ожидал: мгновенно переключение источников; никаких лагов на 4k 60fps не заметил, правда, напрячь все 4 источника и 4 выходных устройства технической возможности не было, но на 2x2 сигналах полет шикарный.

Разбираемся с последовательным портом

Следующий этап проверки — управление коммутацией через RS-232. 

И тут начинается веселье. Инструкция представляет собой пару страничек 5 на 5 см с изображением как правильно вставить HDMI кабель (как будто есть несколько вариантов) и как подключить питание. 

Подождите, но есть же последовательный порт, как мне его использовать? После поиска в гугле и общения с DeepSeek, пришлось начать общение с продавцом на площадке маркетплейса. На мой вопрос об использовании RS-232 последовал ответ (сопровождаемый традиционным «Hello, my dear friend!», которое сложно читать без акцента в голове): вот вам заводское java-приложение (странно, что про него не было написано в инструкции или хотя бы карточке товара), используйте его, по-другому использовать RS-232 не получится. 

В целом, странно, ведь нет никакой разницы, что подает сигнал коммутатору, главное — правильно его сформировать, но по ответу было понятно, что продавец этого не понимает и пытаться получить от него какую-то дополнительную информацию бесполезно.

Что ж, настало время примерять на себя роль реверс-инженера.

Декомпиллировав присланный jar-файл и потратив некоторое время на изучение полученных файлов, были найдены следующие компоненты, вызывающие переключения сигналов:

private JComboBox jComboBox1;
private JComboBox jComboBox2;
private JComboBox jComboBox3;
private JComboBox jComboBox4;
private JComboBox jComboBox5;
private JComboBox jComboBox6;
private JComboBox jComboBox7;
private JComboBox jComboBox8;

Для каждого combobox’а задается свойство Name:

this.jComboBox1.setModel(new DefaultComboBoxModel(new String[] {
  "Input 1", "Input 2", "Input 3", "Input 4", 
  "Input 5", "Input 6", "Input 7", "Input 8"
}));
this.jComboBox1.setName("1");
this.jComboBox1.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent evt) {
    MX0808_MF.this.jComboBox1ActionPerformed(evt);
  }
});

И обработчик выбора пункта выпадающего списка:

private void jComboBox1ActionPerformed(ActionEvent evt) {
  JComboBox jcb = (JComboBox)evt.getSource();
  this.selectPort(jcb);
}

На основании этого свойства и выбранного значения в combobox’е формируется сигнал для переключения:

public void selectPort(JComboBox jcb) {
  if (!this.isBackInfo) {
    int select = jcb.getSelectedIndex();
    String name = jcb.getName();
    String hex = Integer.toHexString(select);
    msgSend("cir " + name + hex);
  } else {
    this.isBackInfo = false;
  }
}

Сама отправка сигналов в выбранный порт со скоростью передачи 9600 бод:

public static void msgSend(String msg) {
  buf.delete(0, buf.length());
  data = (msg + "\r\n").getBytes();
  (new Thread() {
    public void run() {
      try {
        if (MX0808_HDMI_4K.mark) {
          MX0808_HDMI_4K.outputStream.write(MX0808_HDMI_4K.data);
        } else {
        JOptionPane.showMessageDialog((Component)null, "The port is closed", 
          "Connect error", 0);
        }
      } catch (IOException var2) {}
    }
  }).start();
}

Сложно сказать, насколько декомпилированный код совпадает с исходным, так что оставим китайский код без комментариев.

Если систематизировать данные по всем combobox’ам, сигнал для переключения формируется из префикса «cir », а далее 2-х шестнадцатиричных цифр, обозначающих совместно входной и выходной порт, и символ конца строки.

По-видимому, плата коммутатора предназначается для коммутаторов до 8 входных и выходных портов, т.к. номер портов зашивается по правилу 8*(outputPort-1)+(inputPort-1).

Найти команду для получения текущего состояния портов оказалось проще — нужная строка «bc \r\n» сразу бросается в глаза, поскольку зашита в код константой. Эта команда вернет 8 строк состояний для каждого из портов.

Пишем свой сервер для управления

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

Т.к. сервер минималистичен до безобразия (всего 4 ручки: signIn, signOut для того, чтобы абы кто не мог управлять моим коммутатором; getInfo для получения текущего состояния устройства; setHdmi для переключения некоторого output на input), реализовал его на Flask, сам сервер приводить не буду, поскольку в нем ничего интересного, только модуль для взаимодействия с коммутатором:

import serial 

def switch_port(port, output, input): 
    serialPort = serial.Serial(port, 9600) 
    command = 'cir '+'{:02x}'.format(8*(output-1)+(input-1))+'\r\n' 
    serialPort.write(bytes(command, 'utf-8')) 
    serialPort.close() 


def get_state(port): 
    serialPort = serial.Serial(port, 9600) 
    command = 'bc \r\n' 
    serialPort.write(bytes(command, 'utf-8')) 
    state = [] 
    state.append(int(serialPort.readline().decode('utf-8')[2]) + 1) 
    state.append(int(serialPort.readline().decode('utf-8')[2]) + 1) 
    state.append(int(serialPort.readline().decode('utf-8')[2]) + 1) 
    state.append(int(serialPort.readline().decode('utf-8')[2]) + 1) 
    return state

Думаю, в этом коде сможет разобраться и школьник, при том, что он гораздо лаконичнее «китайского» декомпиллированного варианта.

Сервер должен работать 24/7, так что для того, чтобы развернуть его, я решил использовать свою RaspberyPi. Тут я столкнулся еще с одним непредвиденным осложнением: так как разворачивал сервер в докере, а с докером на RaspberyPi ранее не работал, выяснил, что собрать образ на ПК и закинуть на малинку так просто не получится из-за другой архитектуры процессора — кто хочет запускать контейнеры на малинке, должны и собрать их на малинке. К счастью, пересобрать образ для нужной платформы получилось менее чем за 10 минут: закинул исходники, «docker build» — и рабочий образ для RaspberyPi образ готов!

Развернув сервер, можно потыкать его через Postman — о чудо, оно работает, и работает как надо!

И последнее — клиент

При наличии какой-либо системы умного дома, например, Home Assistant, можно было бы интегрировать сервер в него и управлять всем оборудованием централизованно, но, в настоящий момент мои руки так и не добрались до глобальной автоматизации жилища, так что решил написать отдельное мобильное приложение для взаимодействия с сервером. 

Так как приложение надо поставить всем членам семьи, нужно реализовать его максимально безболезненно и для Android и для IPhone, мой выбор пал на ReactNative (проверить на IPhone, к сожалению, так  и не представилась возможность).

В приложении реализовано 2 окна. На первом — текущее состояние подключенного коммутатора, и возможность выбора для любого устройства отображения источник сигнала. Второе окно — настройки, где можно указать адрес сервера, учётную запись для подключения, а также псевдонимы для всех источников сигналов и устройств воспроизведения для удобной навигации в основном окне.

Все введенные настройки сохраняются в AsyncStorage, в запущенном виде приложение каждые 5 секунд обновляет состояние коммутатора (все-таки, его все еще можно переключать вручную) и в зависимости от ответа выводит состояние сервера (Online/Offline). Дизайн минималистичен, но большего и не надо:

Заключение

В итоге получилась готовая система для управления аудио-видео в доме. Выглядит это чудо примерно так:

Возможно, кто-то скажет, что задача надумана и едва ли кому-то потребуется часто перекоммутировать источники — да, действительно, пользуюсь я ей не так часто, как мне виделось на этапе создания, но приятно, когда ты смотришь суточный марафон 24 часа Ле Мана, и жена в ночи выгоняет в другую комнату, просто нажать одну кнопку в приложении, чтобы картинка появилась на экране другого телевизора.  И именно это делает тебя счастливым в тот момент — путь, который ты преодолел и мысль, что ты смог его пройти)


Также подписывайтесь на Телеграм-канал Alfa Digital, где рассказывают о работе в IT и Digital: новости, события, вакансии, полезные советы и мемы.

Основы безопасности веб-приложений: краткий «курс» по выявлению уязвимостей
Как выглядит веб-приложение с точки зрения злоумышленника? Чтобы ответить на этот вопрос, сегодня мы...
habr.com
Почему моё Android-приложение крашится?
Привет! Меня зовут Абакар, я работаю главным техническим лидером разработки в Альфа-Банке. Если вы А...
habr.com
Есть ли ответ на вопрос «Почему AI любит добавлять в тексты много длинных тире?»
В AI-текстах так часто используется длинное тире, что на эту тему пишут статьи вида «Длинное тире — ...
habr.com
Как я бросил курить за день, но потратил на это год
Мой стаж как больного человека, зависимого от никотина, 11 лет. С июня сего года я больше не употреб...
habr.com

Комментарии (0)