- PVSM.RU - https://www.pvsm.ru -

Как я делаю DIY-контроллер для ПК: громкость, приложения, MIDI, OBS

Всё начиналось с идеи

Началось всё в 2024 году в где‑то середине осени. Тогда я ещё не имел тесных дел с arduino и python, но у меня появилась идея сделать простенькую мини клавиатуру для переключения треков, чтобы во время игры во что либо не отвлекался на это. Что‑то около недели и у меня уже получилось собрать на макетке схему со свитчами от клавиатуры, которая управляла громкостью системы и листала треки. Я был в восторге: что‑то, собранное на коленке, тупо с ИИ, работает! Да, тогда я полностью полагался на дипсик или чатГпт.

Затем появился азарт

Далее я захотел сделать хоть какую-нибудь рабочую версию и решил учиться работать в Компас-3D. Сделал я свою первую модель, напечатал, не с первой попытки, как ещё-то, хах. И закончил я как раз к новому 2025 году.

V-Alpha 0.5.0

V-Alpha 0.5.0

Захотелось чего-то нового

Пользовался я своим творением недолго, где-то месяц, то есть до февраля 2025г. Я решил устроить редизайн, довольно глобальный, сделав ползунковые резисторы, как на верхнем фото, что казалось мне очень удобным и красивым. Я был прав, получилось очень круто! Вот этой версией я пользовался уже где-то до полугода.

V-Alpha 1.0.0

V-Alpha 1.0.0

Но сытость была не на долго

Я где‑то к лету пару раз успел переписать код на Python, добавляя какие‑то фичи или просто оптимизируя. К осени 2025, я решил снова переделать контроллер, добавив плату, вместо навесного монтажа: не очень всё‑таки удобно каждый раз паять по 20 тонкий проводков, которые обрываются и тому подобное. К сожалению получилось абсолютно не то, чего я ожидал, в том числе потому, что делал я в спешке и даже забыл добавить некоторый функционал, ради которого и делал обновление. Это была какая‑то громадная штука, в пару раз больше чем предыдущая, ну и ваще мне не понравилось :‑(

Усердные попытки дали свои плоды

Но я не сдавался! Сделав снова огромный перерыв, я сделал новую версию, но она не так интересна, как нынешняя версия, в которую я добавил всё, что хотел: и плату, и светодиоды, и наконец... Сам написал код на питоне, который я продолжаю улучшать и по сей день. Сделал я его кстати тоже к новому году, но уже 2026)

V-Beta 1.0.0

V-Beta 1.0.0

Какой путь выберет самурай дальше?

Даже поняв, что мой проект не столь и уникальный, я продолжаю его улучшать и даже уже был первый тестер! На самом деле я хочу сейчас довести проект до условной V‑Beta1.5.0 и чуть выдохнуть, посмотреть на реакцию людей, послушать их мнения. Я начал потихоньку делать посты на пикабу, вомбате, тг. Хочу видос на ютуб ещё снять, но пока не выходит.

Какие изменения за последнее время?

И так. Сейчас я занимаюсь как раз таки доводкой проекта до контрольной точки, а именно:

  • Делаю плату под новую конфигурацию деталей (добавляю энкодер с кнопками [1])

  • Допиливаю код на питоне и для прошивки Arduino Pro Micro

  • После этого всего моделирую корпус и собираю всё в одно целое

Подробнее про код и прошивку. Для ардуинки добавляю профиль для работы контроллера как MIDI‑устройства. На питоне делаю сейчас GUI для нормальной связи с пользователем, а именно настройки биндов, уведомлений и так далее.

Новая трассировка платы

Новая трассировка платы
Окна настроек

Окна настроек

Подробнее про архитектуру проекта

Раньше я писал скрипт вообще в 1 файле, где имелся набор функций и бесконечный цикл. Система была не очень красивая, но работала. Однако, изучив такую вещь как класс в питоне, я смог продвинуться в разработке, и теперь архитектура выглядит вот так.

Как я делаю DIY-контроллер для ПК: громкость, приложения, MIDI, OBS - 6

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

🧱 core

  • основные классы, использующиеся либо почти везде:

    • message — отправление сообщений

    • config — универсальная настройка логгера

  • либо единожды, для работы некоторых функций программы:

    • connector — подключение к ардуино

    • reader — чтение данных с com‑порта

    • tray_icon — иконка в трее

📁 data

Все доп файлы для работы программы по типу настроек, иконочек.

# binds
{
    "left_1": "Предыдущий трек",
    "left_2": "Play / Pause",
    "left_3": "Следующий трек",
    "left_4": "Мут звука",
    "left_A": "Громкость системы",
    "left_B": "Громкость приложения",
    "left_C": "Громкость приложения",
    "left_D": "Громкость приложения",
    "right_Энкодер": null,
    "right_↑": null,
    "right_↓": null,
    "right_←": null,
    "right_→": null,
    "right_●": null
}
# notifications
{
    "connection_not": false,
    "disconnection_not": false,
    "change_audio_not": false,
    "bind_not": true,
    "sound_not": true
}
# obs connection settings
{
    "port": "4455",
    "password": "password",
    "A": "123",
    "B": "Звук раб. стола",
    "C": "Микр/доп",
    "D": ""
}

🖥️ GUI

Ну, думаю понятно, что gui с настройками уведомлений, биндов (пока не доделал), подключения к obs.

Пока делал пост чуть обновил настройки и теперь вот так это выглядит:

Настройки

Настройки

Да, не оч, но я пока не способен сделать что-то сильно лучше. И то прибегал к помощи ии, особенно с вот этой картинкой, там столько объектов спавнить, задолбаешься.

🎮 Handlers

Классы‑обработчики:

  • default_handler — все методы для переключения музыки, звука и тому подобное

  • OBS_handler — методы для работы с api obs: подключение, громкость каналов

  • handler — основной обработчик, где словари с ключами‑командами собираются в 1, и обрабатываются в зависимости от входящих в порт ключей и значений

# словарь методов default_handler
self.keys = {
            'A': self.master_volume,
            'B': self.app_volume,
            'C': self.app_volume,
            'D': self.app_volume,
            'M': self.microphone_state,
            'V': self.volume_state,
            'K': self.bind,
            'P': self.music,
            'S': self.music,
            "N": self.music
        }

# словарь методов OBS_handler
self.keys = {
            'A': self.set_channel_volume,
            'B': self.set_channel_volume,
            'C': self.set_channel_volume,
            'D': self.set_channel_volume,
            'M': None,
            'V': None,
            'K': None,
            'P': self.mute_channel_volume,
            'S': self.mute_channel_volume,
            'N': self.mute_channel_volume
        }

А сам класс handler вот:

import logging
import time


from threading import Thread

from Python_code.managers.app_manager import AppManager
from Python_code.managers.device_manager import DeviceManager
from Python_code.core.reader import Reader
from Python_code.core.connector import Connector
from Python_code.core.tray_icon import TrayIcon
from Python_code.core.message import Message
from Python_code.core.config import setup_logger

from Python_code.Handlers.default_handler import Default
from Python_code.Handlers.OBS_handler import OBS
from Python_code.Handlers.containers import Services, Events


setup_logger()


class Handler:
    def __init__(self):
        self.logger = logging.getLogger(self.__class__.__name__)
        self.tray = TrayIcon()
        self.msg = Message()
        self.connector = Connector(self.msg, self.tray.shutdown_event)
        self.disconnect_event = self.connector.disconnect_event
        self.app_manager = AppManager(self.disconnect_event, self.msg)
        self.device_manager = DeviceManager(self.disconnect_event)
        self.audio_disconnect_event = self.device_manager.audio_disconnect_event
        self.mic_disconnect_event = self.device_manager.mic_disconnect_event
        self.reader = Reader(self.connector, self.disconnect_event)

        self.services = Services(
            connector=self.connector,
            reader=self.reader,
            app_manager=self.app_manager,
            device_manager=self.device_manager,
            msg=self.msg
        )

        self.events = Events(
            audio_disconnect_event=self.device_manager.audio_disconnect_event,
            mic_disconnect_event=self.device_manager.mic_disconnect_event,
            disconnect_event=self.disconnect_event
        )

        self.default = Default(self.services, self.events)
        self.obs = OBS(self.services, self.events)

        self.keys = {
            'A': None,
            'B': None,
            'C': None,
            'D': None,
            'M': None,
            'V': None,
            'K': None,
            'P': None,
            'S': None,
            'N': None
        }
        self.path = 'binds.json'
        self.load_binds()

        thread = Thread(target=self.process, daemon=True)
        thread.start() # поток обработки входящих данных

    def process(self):
        while True:
            if not self.disconnect_event.is_set():
                key, val = self.reader.wait_for_key(3)
                if key or val:
                    try:
                        self.keys[key](key, val)
                        self.reader.line = None
                        self.reader.key = None
                        self.reader.val = None
                    except:
                        self.logger.debug(f'На кнопку {key} не назначена команда')
                    time.sleep(0.001)
            else:
                time.sleep(3)

    # пока не сделал(
    def load_binds(self):
        pass

🕵️ managers

Менеджеры, постоянно за чем-то следящие:

  • app_manager — обновляет словарь с аудио приложениями, если появляются новые процессы, плюс переписывает бинды этих приложений в data/mus.json

    {
        "B": "яндекс музыка.exe",
        "C": "discord.exe",
        "D": "hl.exe"
    }
  • device_manager — следит за аудио устройствами, их подключением, отключением и т. п.

В целом по архитектуре всё. Хочу чуть-чуть, совсем капелльку, объяснить систему того, как это всё работает.

⚙️ Логика

  1. Connector устанавливает соединение с Arduino.

  2. Менеджеры начинают обновлять свои словари и списки из data/, сканируя систему (аудиоустройства и приложения).

  3. Появляется иконка в трее, приходят уведомления о запуске. Работа началась.

  4. С Arduino приходят сообщения вида:
    A0.23NB0.69 и т.п.

  5. В handler значения обрабатываются в зависимости от ключа и значения, вызывая нужные методы.

Что дальше?

Проект не закончен, но теперь у него есть твёрдая основа. Дальше — корпус, видео и V‑Beta 1.5.0 как закрепление прогресса.

Спасибо, что дочитали. Я буду рад вашему мнению и идеям — пишите!

Подписывайтесь на мой telegram-канал [2], там я рассказываю об изменениях подробнее.

Автор: FRIMID

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/diy/452288

Ссылки в тексте:

[1] энкодер с кнопками: https://static.chipdip.ru/lib/233/DOC033233348.pdf

[2] telegram-канал: https://t.me/chumarno

[3] Источник: https://habr.com/ru/articles/1039616/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1039616