Ссылка на репозиторий
В хаб можно добавлять программные модули для считывания данных с устройств или сервисов и отправки команд в другие сервисы. Есть 2 вида модулей:
Взаимодействие между ними (передача данных) с помощью каналов redis репозиторий в соответствии с конфигурацией, которую задает пользователь в последнем изменении для этого использовалась таблица конфигурации, инструкция к ней представлена на листе "инструкция". С помощью скрипта (который так же поставляется вместе с хабом скрипт
)
Диаграмма, визуализирующая работу хаба ссылка
Класс должен поставляться в виде модуля с такой структурой
— <listener_name>/
- __main__.py
— <listener_name>.py
— settings.py
Содержит код класса
from listener.base_listener import BaseListener
from utils.logger import logger
logger = logger(__name__)
class TestListener(BaseListener):
redis_channels = [] # список каналов, на сообщения в которых следует реагировать (включить подсветку и тд) инициализировать до вызова конструктора родительского класса
def __init__(self, google_sheet):
super().__init__(google_sheet)
def prepare(self):
self.device = connect_device(port) # ПРИМЕР
. . .
def job(self):
data = <your_protocol>.read() # Для разных протоколов - разные функции, это просто пример
if data in self.conf:
return self.conf[button]
def redis_handler(self, topic, message):
pass
# Обрабатывать сообщения, выполнять какие-то действия
def echo(self):
while True:
raw = <your_protocol>.read() # Для разных протоколов - разные функции, это просто пример
if raw:
print(raw)
Если ваш пульт возвращает числинные значения (например координаты джойстика или значение на котором сейчас фейдер), то в функцию job должно быть включено типовое сообщение, в которое кладется нужное значение и возвращается (в этом случае лучше всего задать типовые сообщения прямо в файле настроек)
Пример:
from listener.base_listener import BaseListener
from utils.logger import logger
from listener.joystick.settings import config, stop_msg, z_msg, x_msg, y_msg
logger = logger(__name__)
class TestListener(BaseListener):
def __init__(self, google_sheet):
super().__init__(google_sheet)
def prepare(self):
# Standard messages
self.stop_msg = stop_msg
self.z_msg = z_msg
self.x_msg = x_msg
self.y_msg = y_msg
self.joystick = None
self.connect_joystick()
def job(self):
data = <your_protocol>.read() # Для разных протоколов - разные функции, это просто пример
if data.startswith('Joystick'): # Нужно отловить, что данные пришли с джойстика
msg = copy.deepcopy(self.x_msg) # Если движение по оси x, то нам нужно типовое сообщение для x
msg = copy.deepcopy(self.y_msg) # Если движение по оси y, то нам нужно типовое сообщение для y
msg = copy.deepcopy(self.z_msg) # Если движение по оси z(прим. zoom), то нам нужно типовое сообщение для z
msg['value'] = data['x/y/z']
return [msg]
if data in config: # Если же пришла информация о нажатой кнопке, то возвращаем значение из конфига
return config[button]
def echo(self):
while True:
data = <your_protocol>.read() # Для разных протоколов - разные функции, это просто пример
if data.startswith('Joystick'): # Нужно отловить, что данные пришли с джойстика
msg = copy.deepcopy(self.x_msg) # Если движение по оси x, то нам нужно типовое сообщение для x
msg = copy.deepcopy(self.y_msg) # Если движение по оси y, то нам нужно типовое сообщение для y
msg = copy.deepcopy(self.z_msg) # Если движение по оси z(прим. zoom), то нам нужно типовое сообщение для z
msg['value'] = data['x/y/z']
print(msg)
if data in config: # Если же пришла информация о нажатой кнопке, то возвращаем значение из конфига
print(config[button])
if __name__ == '__main__':
предусмотрена для запуска listener'аfrom listener.<listener_name>.<listener_name>.py import TestListener
import argparse
def main():
parser = argparse.ArgumentParser(description='Run onvif publisher on your module')
parser.add_argument('google_sheet', type=str, help='Type of API')
args = parser.parse_args()
TestListener(args.google_sheet)
if __name__ == '__main__':
main()
Файл с настройками listener’а, это могут быть настройки redis сервера либо порты подключения пульта, всё это должно быть собрано в этом файле. Также в этом файле должны открываться все конфиги:
listener_port = os.environ.get('<LISTENER_NAME>_PORT', '/dev/cu.usbserial-14230')
PS Потом это импортится в <listener_name>.py:
from listener.<listener_name>.settings import listener_conf, listener_port
От программиста требуется список разных button_identifier, которые используются в config.yaml и их соответствие физическим кнопкам на пульте
Файл config.yaml содержит конфиг для кнопок такой структуры (генерируется автоматически утилитой google_sheet)
<button-identifier>:
- publisher: <publisher1_name>
data:
. . .
- publisher: <publisher2_name>
data:
. . .
Здесь publisher1_name, publisher2_name - названия pulisher'ов которым будет передаваться data (data для разных publisher'ов может быть разная)
PS Структура любого сообщения -- {"publisher":<publisher_name>, "data":<your_data>}
PS2 Конфиги могут меняться в зависимости от программ, с которыми планируется использовать ваш listener, поэтому ваша задача - сделать его максимально универсальным. Нужно предоставить тестовый конфиг.
Класс должен поставляться в виде модуля с такой структурой:
— <publisher_name>/
— __init__.py
— <publisher_name>.py
— settings.py
Содержит код класса:
from publisher.base_publisher import BasePublisher
class TestPublisher(BasePublisher):
def __init__(self):
super().__init__()
. . .
def job(self, data):
if data[‘change’] == ‘preset’:
self.ptz_change_preset(data[‘preset_token’])
Файл с настройками publisher'а, это могут быть порт и хост сервера, куда отправляются request'ы, всё это должно быть собрано в этом файле:
server_host = os.environ.get('<PUBLISHER_NAME>SERVER_HOST', '172.18.130.50')
server_port = int(os.environ.get('<PUBLISHER_NAME>SERVER_PORT', 5119))
PS Потом это импортится в <publisher_name>.py:
from publisher.<pulisher_name>.publisher_settings import server_host, server_port
env.sh
Также желательно создать файл env.sh
в котором вы будете обозначать ваши переменные окружения, такие как порт или хост сервера:
<PUBLISHER/LISTENER_NAME>_SERVER_PORT=5119
<PUBLISHER/LISTENER_NAME>_SERVER_HOST=172.18.130.50
Вместо <PUBLISHER_NAME> или <publisher_name> вставь название своего publisher'а, например:
В проект включены некоторые утилиты, они будут описаны в этом разделе
python3 utils/google_sheet_conf <sheet_name>
Файл будет сохранен по пути universal-hub/data/<sheet_name>.yaml
from utils.logger import logger
logger = logger(__name__)
Перед запуском нужно убедиться, что на рабочем ПК с ОС Windows установлен плагин для OBS obs-websocket-5 актуальной версии для установленного OBS и установить его в случае отсутствия.
Нужен компьютер с установленной на него ОС Debian Bullseye или Ubuntu 20.04 LTS.
sudo apt install libhidapi-dev libhidapi-hidraw0 libhidapi-libusb0 libportmidi-dev libsdl2-dev python3 python3-dev python3-pip python3-venv -y
git clone https://git.miem.hse.ru/19102/universal-hub.git
cd universal-hub
git checkout develop
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
sudo sh udev_rules/udev.sh
sudo docker run -d -p 6379:6379 --name redis --restart always redis/redis-stack-server:latest
Или с помощью docker compose
sudo docker compose up -d
src/env.sh
python3 listener/<listener_name>
python3 publisher/<publisher_name>
service_files
в /etc/systemd/systemcp service_files/* /etc/systemd/system
sudo systemctl daemon-reload
sudo systemctl enable service-name.service
sudo systemctl start service-name.service
sudo journalctl -eu service-name.service
sudo env PYTHONPATH=*your/path* python3 ...
)