Прямое управление DMX-512 через встроенный RS-485 на Wiren Board
Описание
Решение создано членом сообщества и не тестировалось компанией Wiren Board. Используя его, вы действуете на свой страх и риск.
Физический уровень протоколов DMX512 и RS-485 идентичен, однако стандартные драйверы Wiren Board (например, wb-mqtt-serial) не могут генерировать специфический сигнал BREAK (логический ноль > 88 мкс), необходимый для DMX.
Данное решение позволяет программно эмулировать сигнал BREAK на встроенном порту RS-485, а также автоматически создает виртуальное устройство в веб-интерфейсе контроллера. Вы можете на лету задавать количество используемых DMX-каналов, и скрипт сам создаст или удалит соответствующие ползунки (range) для управления яркостью.
Подготовка контроллера
- Освободите порт: В веб-интерфейсе Wiren Board перейдите в Настройки -> Настройки драйвера Serial-устройств и отключите порт, к которому подключена шина DMX (например,
/dev/ttyRS485-2). Сохраните изменения. - Установите зависимости: Подключитесь к контроллеру по SSH и установите библиотеки для работы с последовательным портом и MQTT-брокером:
apt-get update
apt-get install python3-serial python3-paho-mqtt
Python-скрипт (Мост MQTT ↔ DMX)
Создайте файл /root/dmx_mqtt.py и вставьте в него следующий код. Обязательно проверьте переменную PORT.
import serial
import time
import paho.mqtt.client as mqtt
# --- Настройки RS-485 и DMX ---
PORT = '/dev/ttyRS485-2' # Освобожденный порт
DMX_BAUD = 250000
BREAK_BAUD = 38400
MAX_CHANNELS = 512
# --- Настройки MQTT ---
MQTT_BROKER = "127.0.0.1"
MQTT_PORT = 1883
DEVICE_ID = "dmx_python"
DEVICE_NAME = "Dynamic DMX"
dmx_data = bytearray([0] * MAX_CHANNELS)
current_count = 0
# Открываем порт
try:
ser = serial.Serial(PORT, DMX_BAUD, stopbits=serial.STOPBITS_TWO)
except Exception as e:
print(f"Ошибка открытия порта {PORT}: {e}")
exit(1)
def send_dmx_frame(data):
"""Генерация BREAK и отправка кадра DMX"""
ser.baudrate = BREAK_BAUD
ser.write(b'\x00')
ser.flush()
ser.baudrate = DMX_BAUD
packet = bytearray([0x00]) + data
ser.write(packet)
ser.flush()
def update_controls(client, new_count):
"""Динамическое создание/удаление MQTT-контролов"""
global current_count
safe_count = max(0, min(new_count, MAX_CHANNELS))
# Добавление новых каналов
if safe_count > current_count:
for i in range(current_count + 1, safe_count + 1):
prefix = f"/devices/{DEVICE_ID}/controls/Channel {i}"
client.publish(f"{prefix}/meta/type", "range", retain=True)
client.publish(f"{prefix}/meta/max", "255", retain=True)
client.publish(f"{prefix}/meta/order", str(i), retain=True)
client.publish(prefix, "0", retain=True)
# Удаление лишних каналов (очистка retained-сообщений)
elif safe_count < current_count:
for i in range(safe_count + 1, current_count + 1):
prefix = f"/devices/{DEVICE_ID}/controls/Channel {i}"
client.publish(f"{prefix}/meta/type", "", retain=True)
client.publish(f"{prefix}/meta/max", "", retain=True)
client.publish(f"{prefix}/meta/order", "", retain=True)
client.publish(prefix, "", retain=True)
dmx_data[i - 1] = 0 # Гасим канал аппаратно
current_count = safe_count
# --- MQTT Коллбэки ---
def on_connect(client, userdata, flags, rc):
print("MQTT подключен. Инициализация устройства...")
client.publish(f"/devices/{DEVICE_ID}/meta/name", DEVICE_NAME, retain=True)
# Базовый контрол для задания количества каналов
client.publish(f"/devices/{DEVICE_ID}/controls/Topic Count/meta/type", "value", retain=True)
client.publish(f"/devices/{DEVICE_ID}/controls/Topic Count/meta/readonly", "0", retain=True)
# Подписка на команды от Web UI
client.subscribe(f"/devices/{DEVICE_ID}/controls/+/on")
def on_message(client, userdata, msg):
"""Обработка ввода пользователя"""
topic = msg.topic
payload = msg.payload.decode('utf-8')
if topic == f"/devices/{DEVICE_ID}/controls/Topic Count/on":
try:
count = int(float(payload))
update_controls(client, count)
# Эхо-ответ для снятия красного статуса ошибки в UI
client.publish(f"/devices/{DEVICE_ID}/controls/Topic Count", str(count), retain=True)
except ValueError:
pass
elif topic.startswith(f"/devices/{DEVICE_ID}/controls/Channel ") and topic.endswith("/on"):
try:
control_name = topic.split('/')[-2]
ch_num = int(control_name.split(' ')[1])
val = int(float(payload))
if 1 <= ch_num <= MAX_CHANNELS:
dmx_data[ch_num - 1] = max(0, min(val, 255))
client.publish(f"/devices/{DEVICE_ID}/controls/{control_name}", str(val), retain=True)
except (ValueError, IndexError):
pass
# --- Основной цикл ---
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_start()
try:
while True:
send_dmx_frame(dmx_data)
time.sleep(0.025) # Поддержание частоты ~40 Гц
except KeyboardInterrupt:
print("\nОстановка...")
send_dmx_frame(bytearray([0] * MAX_CHANNELS))
mqtt_client.loop_stop()
mqtt_client.disconnect()
ser.close()
Представление в веб-интерфейсе контроллера
Устройство будет представлено в стандартном веб-интерфейсе контроллера Wiren Board на вкладке Устройства.
Как это работает (Архитектура)
- Удержание шины: DMX-устройства требуют непрерывного потока данных. Бесконечный цикл
while Trueс задержкой0.025sобеспечивает частоту обновления около 40 кадров в секунду. - Генерация BREAK: Выполняется трюком со снижением
baudrateпорта до 38400 бит/с и отправкой нулевого байта. - Конвенция MQTT Wiren Board:
- Скрипт публикует
retainсообщения в топики/metaдля отрисовки интерфейса (создание полей ввода и ползунков). - Когда пользователь двигает ползунок в веб-интерфейсе, брокер Mosquitto присылает новое значение в подтопик
/on. Скрипт перехватывает его, обновляет массивdmx_dataи отправляет подтверждение обратно в основной топик контрола, чтобы интерфейс понял, что команда принята.
- Скрипт публикует
Ограничения метода: Операционная система Linux на Wiren Board не является системой жесткого реального времени (RTOS). При высокой загрузке CPU (например, при активной записи истории в базу данных) программные тайминги генерации BREAK могут нестабильно колебаться. На некоторых чувствительных DMX-светильниках это может вызывать легкое мерцание. Для профессиональных инсталляций рекомендуется использовать аппаратные шлюзы (Modbus RTU → DMX512).