Added a background thread for updating the light zones on the QSE and added MQTT support for Home Assistant.

This commit is contained in:
James Coleman 2021-09-09 17:00:22 -05:00
parent f666e423c2
commit 979c865262
22 changed files with 543 additions and 49 deletions

View file

@ -0,0 +1,32 @@
version: '3'
services:
homeassistant:
container_name: home-assistant
image: homeassistant/home-assistant:stable
volumes:
- ./hass:/config
environment:
- TZ=America/Chicago
restart: always
devices:
- /dev/ttyUSB1:/dev/ttyUSB1
- /dev/serial/by-id/usb-Silicon_Labs_HubZ_Smart_Home_Controller_415007C7-if01-port0
network_mode: host
mqtt:
image: eclipse-mosquitto
volumes:
- ./mosquitto:/mosquitto/config
restart: always
network_mode: host
zwave-js:
container_name: zwavejs2mqtt
image: zwavejs/zwavejs2mqtt:latest
volumes:
- ./zwave-js:/usr/src/app/store
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
- /dev/serial/by-id/usb-Silicon_Labs_HubZ_Smart_Home_Controller_415007C7-if00-port0
environment:
- TZ=America/Chicago
restart: always
network_mode: host

View file

@ -0,0 +1 @@
2021.1.5

View file

@ -0,0 +1,25 @@
{
"version": 1,
"key": "core.entity_registry",
"data": {
"entities": [
{
"entity_id": "binary_sensor.updater",
"config_entry_id": null,
"device_id": null,
"area_id": null,
"unique_id": "updater",
"platform": "updater",
"name": null,
"icon": null,
"disabled_by": null,
"capabilities": null,
"supported_features": 0,
"device_class": null,
"unit_of_measurement": null,
"original_name": "Updater",
"original_icon": null
}
]
}
}

View file

@ -0,0 +1,7 @@
{
"version": 1,
"key": "core.uuid",
"data": {
"uuid": "6ae2a5eefe6741829c95a45064c93a0f"
}
}

View file

@ -0,0 +1,13 @@
{
"version": 1,
"key": "http",
"data": {
"login_attempts_threshold": -1,
"server_port": 8123,
"cors_allowed_origins": [
"https://cast.home-assistant.io"
],
"ip_ban_enabled": true,
"ssl_profile": "modern"
}
}

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,50 @@
blueprint:
name: Motion-activated Light
description: Turn on a light when motion is detected.
domain: automation
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml
input:
motion_entity:
name: Motion Sensor
selector:
entity:
domain: binary_sensor
device_class: motion
light_target:
name: Light
selector:
target:
entity:
domain: light
no_motion_wait:
name: Wait time
description: Time to leave the light on after last motion is detected.
default: 120
selector:
number:
min: 0
max: 3600
unit_of_measurement: seconds
# If motion is detected within the delay,
# we restart the script.
mode: restart
max_exceeded: silent
trigger:
platform: state
entity_id: !input motion_entity
from: "off"
to: "on"
action:
- service: light.turn_on
target: !input light_target
- wait_for_trigger:
platform: state
entity_id: !input motion_entity
from: "on"
to: "off"
- delay: !input no_motion_wait
- service: light.turn_off
target: !input light_target

View file

@ -0,0 +1,43 @@
blueprint:
name: Zone Notification
description: Send a notification to a device when a person leaves a specific zone.
domain: automation
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml
input:
person_entity:
name: Person
selector:
entity:
domain: person
zone_entity:
name: Zone
selector:
entity:
domain: zone
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
integration: mobile_app
trigger:
platform: state
entity_id: !input person_entity
variables:
zone_entity: !input zone_entity
# This is the state of the person when it's in this zone.
zone_state: "{{ states[zone_entity].name }}"
person_entity: !input person_entity
person_name: "{{ states[person_entity].name }}"
condition:
condition: template
value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
action:
domain: mobile_app
type: notify
device_id: !input notify_device
message: "{{ person_name }} has left {{ zone_state }}"

View file

@ -0,0 +1,49 @@
# Configure a default setup of Home Assistant (frontend, api, etc)
default_config:
homeassistant:
name: Church
latitude: 34.719930
longitude: -86.704050
elevation: 470
unit_system: imperial
time_zone: "America/Chicago"
legacy_templates: false
logger:
default: info
# Text to speech
tts:
- platform: google_translate
group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml
zha:
database_path: /config/zigbee.db
enable_quirks: true
mqtt:
discovery: true
broker: 127.0.0.1
port: 1883
username: !secret mqtt_username
password: !secret mqtt_password
birth_message:
topic: 'homeassistant/status'
payload: 'online'
will_message:
topic: 'homeassistant/status'
payload: 'offline'
light:
- platform: mqtt
schema: json
name: lutron_qse_nwk
state_topic: "lutron/qse-nwk"
command_topic: "lutron/qse-nwk/set"
brightness: true
color_mode: true
supported_color_modes: ["brightness"]

BIN
Home Assistant/hass/core Normal file

Binary file not shown.

View file

View file

@ -0,0 +1,2 @@
2021-09-02 23:56:55 WARNING (MainThread) [homeassistant.components.http.ban] Login attempt or request with invalid authentication from 10.11.0.2 (10.11.0.2) (Mozilla/5.0 (X11; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0)
2021-09-02 23:56:55 WARNING (MainThread) [homeassistant.bootstrap] Support for the running Python version 3.7.3 is deprecated and will be removed in the first release after December 7, 2020. Please upgrade Python to 3.8.0 or higher.

Binary file not shown.

View file

View file

View file

@ -0,0 +1,5 @@
# Use this file to store secrets like usernames and passwords.
# Learn more at https://home-assistant.io/docs/configuration/secrets/
mqtt_username: mqtt
mqtt_password: mqtt_password_placeholder

View file

@ -0,0 +1,2 @@
user mqtt
topic readwrite #

View file

@ -0,0 +1,6 @@
per_listener_settings true
allow_zero_length_clientid true
listener 1883 0.0.0.0
allow_anonymous false
password_file /mosquitto/config/pwfile
acl_file /mosquitto/config/aclfile

View file

@ -0,0 +1 @@
mqtt:mqtt_password_placeholder

View file

@ -1,9 +1,23 @@
This project is designed to control the GRAFIK Eye QS Control panel via the QSE-CI-NWK-E with the serial interface. The project is designed to use the OLA (https://www.openlighting.org/) project to use either a DMX device to a network DMX protocol to control the 6 available zones.
This project is designed to control the GRAFIK Eye QS Control panel via the QSE-CI-NWK-E with the serial interface. The project is designed to use the OLA (https://www.openlighting.org/) project to use a DMX device to a network DMX protocol to control the 6 available zones. This project also supports MQTT messaging for Home Assistant support.
I designed this software for use on a Raspberry Pi using the 2019-07-10-raspbian-buster-lite release and OLA at https://github.com/OpenLightingProject/ola/tree/dc40569a7ef2512c7c9459a94c9dc4292d809262 compiled and installed using instructions at https://www.openlighting.org/ola/linuxinstall/
# Installation
1. Decide if you want Home Assistant support, if you do not, you can skip to step 5.
2. If you do not already have an home assistant setup, you can view https://www.home-assistant.io/installation/ or use the base configurations in the `Home Assistant` directory to use my home assistant docker setup. The base configuration is designed for the HUSBZB-1 USB adapter, which you can use `ls -lah /dev/serial/by-id/` to see which ttyUSB interfaces are which, and get the correct serial number for your device.
3. If you don't have MQTT setup, you can follow guide at https://cyan-automation.medium.com/setting-up-mqtt-and-mosquitto-in-home-assistant-20eb810a91e6 or use my base configurations in the `Home Assistant` directory to configure mosquitto. Once you setup a username/password in the pwfile, use `mosquitto_passwd -U pwfile` to encrypt the password.
4. Edit the `lutron-dmx-control.py` file to make sure MQTT is enabled, pointed to the proper server, and has the correct password configured.
5. If you are not planning on using MQTT/Home Assistant, you can edit `lutron-dmx-control.py` to change `MQTT_ENABLED` from True to False.
6. Update the serial port for the QSE NWK in `lutron-dmx-control.py`, you can use `ls -lah /dev/serial/by-id/` to determine the device id.
7. Run the bash script install.sh to install services for olad and `lutron-dmx-control.py`.
```bash
sudo bash ./install.sh
```
# Configuration
Once OLA is installed, run it using `olad -l 3` and then edit the configuration files in `.ola/` to disable the modules which are not used as some of them will take the serial device. Once configured, run `olad -l 3` again and visit the raspberry pi's IP address at port 9090 in your browser to configure the DMX universe you are going to use. Once configured, you can then test this software by changing the configuration portion of the code.
Once services are installed we need to stop the olad service to edit configuration files with `sudo systemctl start olad@pi`. We can then configure ola by editing the configuration files in `.ola/` to disable the modules which are not used as some of them will take the serial device. Once configured, run `sudo systemctl start olad@pi` and visit the raspberry pi's IP address at port 9090 in your browser to configure the DMX universe you are going to use. Once configured, you can then test this software by changing the configuration portion of the code.
Trick to disable all modules except the one you are using.
@ -12,23 +26,20 @@ sed -i '/enabled\s=/c\enabled = false' ~/.ola/*.conf
sed -i '/enabled\s=/c\enabled = true' ~/.ola/ola-e131.conf
```
# Installation
# Home Assistant MQTT config
Install Python/needed modules.
If you have your own Home Assistant install, the configuration for this project is below.
```bash
apt install python3-pip python3-serial
pip3 install ola
```
Copy lutron-dmx-control@.service and olad@.service to /etc/systemd/system/ and run the following to enable/start.
```bash
systemctl daemon-reload
systemctl enable olad@pi
systemctl start olad@pi
systemctl enable lutron-dmx-control@pi
systemctl start lutron-dmx-control@pi
```yaml
light:
- platform: mqtt
schema: json
name: lutron_qse_nwk
state_topic: "lutron/qse-nwk"
command_topic: "lutron/qse-nwk/set"
brightness: true
color_mode: true
supported_color_modes: ["brightness"]
```
# Recommend

29
install.sh Normal file
View file

@ -0,0 +1,29 @@
#!/bin/bash
USER=$(whoami)
if [ "$USER" != "root" ]; then
echo "Please use sudo with this install script to ensure right permissions for installation."
exit 1
fi
# Get the script directory.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
cd $SCRIPT_DIR
# Install Python/needed modules.
apt install -y python3-pip python3-serial python3
pip3 install ola
pip3 install paho-mqtt
cp lutron-dmx-control.py /home/pi/lutron-dmx-control.py
chown pi: /home/pi/lutron-dmx-control.py
# Copy lutron-dmx-control@.service and olad@.service to /etc/systemd/system/ and run the following to enable/start.
cp olad@.service /etc/systemd/system/
cp lutron-dmx-control@.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable olad@pi
systemctl start olad@pi
systemctl enable lutron-dmx-control@pi
systemctl start lutron-dmx-control@pi

View file

@ -28,7 +28,10 @@ import io
import _thread
import threading
import time
import random
import json
from paho.mqtt import client as mqtt_client
# Documentation
# This program is designed to use the Open Lighting Arcretechture (OLA) to receive a DMX signal
# and translate to commands to control the 6 dimiable zones on the Lutron GRAFIK Eye QS Control panel
@ -36,9 +39,11 @@ import time
# Configuration
# Serial port device to use to communicate with Lutron's QSE NWK.
QSE_NWK_DEVICE = "/dev/ttyUSB0"
QSE_NWK_DEVICE = "/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0"
# Set baud rate on Lutron's QSE NWK.
QSE_NWK_BAUD = 115200
# Number of zones on GRAFIK Eye QS Control panel
QSE_ZONES = 6
# DMX Universe in OLA that is used.
DMX_UNIVERSE = 3
# The starting address.
@ -49,25 +54,45 @@ VERBOSE=1
# Variables used at run time, do not adjust.
serialSession = None
currentValues = [0,0,0,0,0,0]
zoneValues = []
sentValues = []
sendAllDataThisTime = True
controlDisabled = False
lastDMXUniverseUpdate = 0
# To prevent data from overlapping which is known to crash the QSE NWK, we implement a thread lock which must be released before being obtained.
dataLock = threading.Lock()
# To prevent data from overlapping which is known to crash the QSE NWK, we implement a thread lock which must be released before being obtained.
sendAllDataThisTImeLock = threading.Lock()
# MQTT Configurations
MQTT_ENABLED = True
MQTT_BROKER = '127.0.0.1'
MQTT_PORT = 1883
MQTT_TOPIC = "lutron/qse-nwk"
MQTT_TOPIC_SET = MQTT_TOPIC + "/set"
MQTT_CLIENT_ID = f'lutron-qse-nwk-{random.randint(0, 1000)}'
MQTT_USERNAME = 'mqtt'
MQTT_PASSWORD = 'mqtt_password_placeholder'
# MQTT light state
mqttLightState = "OFF"
mqttLightBrightness = 0
mqttSentLightState = ""
mqttSentLightBrightness = 0
# MQTT state values
MQTT_LIGHT_ON = "ON"
MQTT_LIGHT_OFF = "OFF"
# MQTT Connection
mqtt_conn = None
# This fucnction translates the 0-255 signal from DMX to 0.00 to 100.00 signal used by Lutron,
# and it sends the appropiate command to the QSE NWK to change the brightness level of a zone.
def SetZone(zone, value):
global serialSession, currentValues, sendAllDataThisTime, controlDisabled
# We only want to translate a level of it has not already been sent to the zone,
# or if we want to send all data this time. However we do not want to send the level
# if the controls has been disabled by the designated button on the control panel.
if (currentValues[zone-1]==value and not sendAllDataThisTime) or controlDisabled:
return
# Update the array of current values.
currentValues[zone-1] = value
def qse_send_zone_value(zone, value):
global serialSession, VERBOSE
# Translate to the command.
command = "#DEVICE,1,%d,14,%.2f,00:00" % (zone,round((value/255.00)*100,2))
@ -77,30 +102,73 @@ def SetZone(zone, value):
# Send to the QSE NWK.
serialSession.write(bytes(command+"\n\r", 'utf-8'))
def NewData(data):
global sendAllDataThisTime, dataLock
# This function receives data when a DMX update occurs.
def dmx_universe_update(data):
global dataLock, zoneValues, lastDMXUniverseUpdate, QSE_ZONES, VERBOSE
# Acquire the lock for the thread to prevent data from overlapping.
dataLock.acquire()
if VERBOSE>=2:
print(data)
# Send the new levels to each zone via the QSE NWK.
SetZone(1,data[DMX_START_ADDRESS+0])
SetZone(2,data[DMX_START_ADDRESS+1])
SetZone(3,data[DMX_START_ADDRESS+2])
SetZone(4,data[DMX_START_ADDRESS+3])
SetZone(5,data[DMX_START_ADDRESS+4])
SetZone(6,data[DMX_START_ADDRESS+5])
# Write the new levels to each zone.
for zone in range(QSE_ZONES):
zoneValues[zone] = data[DMX_START_ADDRESS+zone]
# Reset the flag of send all data to false as we would have sent all data this time.
sendAllDataThisTime = False
# Keep up to date with the last update to determine rather or not to unlock mqtt support.
lastDMXUniverseUpdate = time.time()
# Allow the next command call to follow through by releasing the lock.
dataLock.release()
# This function is a thread that writes any changes to the QSE controller.
def qse_write_zone_values():
global sendAllDataThisTImeLock, dataLock, sendAllDataThisTime, zoneValues, sentValues, QSE_ZONES
while True:
# If control is disabled, we won't check this time.
if controlDisabled:
# Prevent CPU overload and wait half a second before continuing.
time.sleep(0.5)
continue
# Acquire lock to prevent conflict between threads.
dataLock.acquire()
# Copy zone values locally to allow changes by other threads while we send.
thisZoneValues = zoneValues.copy()
# Allow the next command call to follow through by releasing the lock.
dataLock.release()
# Acquire send all data this time lock.
sendAllDataThisTImeLock.acquire()
# Check for changes in zones values and send it.
for zone in range(QSE_ZONES):
# If zone value is the same and we're not sending all zone data this time, skip sending.
if thisZoneValues[zone]==sentValues[zone] and not sendAllDataThisTime:
continue
# Update the array of sent values.
sentValues[zone] = thisZoneValues[zone]
# Send value via QSE NWK
qse_send_zone_value(zone+1, thisZoneValues[zone])
# Reset the flag of send all data to false as we would have sent all data this time.
sendAllDataThisTime = False
# Release the lock.
sendAllDataThisTImeLock.release()
# Lower CPU usage.
time.sleep(0.1)
# This function reads the serial data from the QSE NWK line by line and performs a few functions based on response.
def QSE_Read():
global serialSession, controlDisabled
def qse_read():
global serialSession, controlDisabled, sendAllDataThisTime, mqttLightBrightness, mqttLightState, VERBOSE
# Creates a bufferred reader for the serial input.
sio = io.TextIOWrapper(io.BufferedReader(serialSession))
@ -122,30 +190,172 @@ def QSE_Read():
serialSession.write(bytes("#RESET,0\n\r", 'utf-8'))
# If the all zone up button is pressed, we disable control from the program to allow someone to manually control zones.
if line=="~DEVICE,1,74,3":
elif line=="~DEVICE,1,74,3":
if VERBOSE>=2:
print("Received disable signal.")
controlDisabled = True
# If the all zone down button is pressed, we re-enable the programs control of the zones.
if line=="~DEVICE,1,75,3":
elif line=="~DEVICE,1,75,3":
if VERBOSE>=2:
print("Received enable signal.")
controlDisabled = False
sendAllDataThisTime = True
# If none of the above, and is a device notification, we parse.
elif line.startswith("~DEVICE,1"):
data = line.split(",")
# If brightness notice and zone 1, let's update MQTT.
if data[3]=="14" and data[2]=="1":
# Acquire lock to prevent conflict between threads.
dataLock.acquire()
# Convert brightness to MQTT light state.
mqttLightBrightness = round((float(data[4])/100.00)*255,0)
if mqttLightBrightness==0:
# If control was disabled, and brightness is now 0, disable the control disablement.
if controlDisabled:
controlDisabled = False
mqttLightState = MQTT_LIGHT_OFF
else:
mqttLightState = MQTT_LIGHT_ON
# Publish current state to MQTT.
mqtt_publish_state()
# Allow the next command call to follow through by releasing the lock.
dataLock.release()
if VERBOSE>=1:
print(line)
# Reset the send all data flag every 10 seconds to ensure all zones have the correct value set.
def sendAllDataReset():
global sendAllDataThisTime
def qse_reset_sendAllDataThisTime():
global sendAllDataThisTImeLock, sendAllDataThisTime, VERBOSE
while True:
# Wait 10 seconds before running.
time.sleep(10)
# Acquire send all data this time lock.
sendAllDataThisTImeLock.acquire()
# Reset
if VERBOSE>=3:
print("Resetting flag to send all data")
sendAllDataThisTime = True
# Release the lock.
sendAllDataThisTImeLock.release()
# Sends the current MQTT light state to MQTT.
def mqtt_publish_state():
global mqtt_conn, mqttLightState, mqttLightBrightness, mqttSentLightState, mqttSentLightBrightness, VERBOSE
# If we already sent this message, no duplicates.
if mqttLightState==mqttSentLightState and mqttLightBrightness==mqttSentLightBrightness:
return
mqttSentLightState = mqttLightState
mqttSentLightBrightness = mqttLightBrightness
# Generate json format of current state.
msg = json.dumps({"brightness": mqttLightBrightness,"state": mqttLightState})
# Send message.
result = mqtt_conn.publish(MQTT_TOPIC, msg)
# result: [0, 1]
status = result[0]
if status == 0 and VERBOSE>=2:
print(f"Send `{msg}` to topic `{MQTT_TOPIC}`")
if status != 0:
print(f"Failed to send message to topic {MQTT_TOPIC}")
# Receives MQTT messages from subscribed topics.
def mqtt_on_message(client, userdata, msg):
global mqttLightState, mqttLightBrightness, mqttSentLightState, mqttSentLightBrightness, lastDMXUniverseUpdate, VERBOSE
# If message received is to the JSON set topic, update light state.
if msg.topic==MQTT_TOPIC_SET:
# Decode JSON from the message.
decoded_message=str(msg.payload.decode("utf-8"))
data = json.loads(decoded_message)
if VERBOSE>=2:
print(f"Received `{data}` from `{msg.topic}` topic")
# Acquire lock to prevent conflict between threads.
dataLock.acquire()
# Check message for brightness and state values/update accordingly.
if "brightness" in data:
mqttLightBrightness = data["brightness"]
if "state" in data:
if mqttLightState!=data["state"]:
mqttLightState = data["state"]
# If light state is on, but brightness value is off, set brightness to 50%.
if mqttLightState==MQTT_LIGHT_ON and mqttLightBrightness==0:
mqttLightBrightness = 127
# Check to see if it has been more than 5 seconds since the last DMX universe update, if it has been, we're allowed to control the lights.
durationSinceLastDMXUniverseUpdate = time.time() - lastDMXUniverseUpdate
if durationSinceLastDMXUniverseUpdate>5:
# If state is on, set brightness levels to all zones.
if mqttLightState==MQTT_LIGHT_ON:
for zone in range(QSE_ZONES):
zoneValues[zone] = mqttLightBrightness
else: # If state is off, set brightness level of 0.
for zone in range(QSE_ZONES):
zoneValues[zone] = 0
else:
# If locked due to DMX control, force values to first zone value.
mqttLightBrightness = zoneValues[0]
if mqttLightBrightness==0:
mqttLightState = MQTT_LIGHT_OFF
else:
mqttLightState = MQTT_LIGHT_ON
mqttSentLightState = ""
mqttSentLightBrightness = 0
# Publish current state to MQTT.
mqtt_publish_state()
# Allow the next command call to follow through by releasing the lock.
dataLock.release()
elif msg.topic!=MQTT_TOPIC:
print(f"Received unknown message `{msg.payload.decode()}` from `{msg.topic}` topic")
# Subscribes to the MQTT topic for this light.
def mqtt_subscribe():
global mqtt_conn
mqtt_conn.subscribe(MQTT_TOPIC+"/#")
# When the MQTT broker is connected, this function is called.
def mqtt_on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
# New connection means we must publish state and subscribe to our topic.
mqtt_subscribe()
mqtt_publish_state()
else:
print("Failed to connect, return code %d\n", rc)
# The MQTT thread for connection to the MQTT broker.
def mqtt_connect():
global mqtt_conn
try:
mqtt_conn = mqtt_client.Client(MQTT_CLIENT_ID)
if MQTT_USERNAME!="":
mqtt_conn.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
mqtt_conn.on_connect = mqtt_on_connect
mqtt_conn.on_message = mqtt_on_message
mqtt_conn.connect(MQTT_BROKER, MQTT_PORT)
mqtt_conn.loop_forever()
except:
print("MQTT Connection Failed, trying again in 10 seconds.\n")
time.sleep(10.0)
mqtt_connect()
# Build array with 0% in each zone.
for zone in range(QSE_ZONES):
zoneValues.append(0)
sentValues.append(0)
# Connect to the QSE NWK by using the serial port.
print("Connecting to QSE NWK at: "+QSE_NWK_DEVICE)
with serial.Serial(QSE_NWK_DEVICE, QSE_NWK_BAUD, timeout=2) as ser:
@ -160,13 +370,20 @@ if serialSession == None:
serialSession.open()
# Now that we are ready to roll, we start the read thread.
_thread.start_new_thread(QSE_Read, ())
_thread.start_new_thread(qse_read, ())
# Start the write thread.
_thread.start_new_thread(qse_write_zone_values, ())
# Start the reset send all data thread.
_thread.start_new_thread(sendAllDataReset, ())
_thread.start_new_thread(qse_reset_sendAllDataThisTime, ())
# Start the MQTT light thread.
if MQTT_ENABLED:
_thread.start_new_thread(mqtt_connect, ())
# Connect to the DMX universe with the OLA wrapper.
wrapper = ClientWrapper()
client = wrapper.Client()
client.RegisterUniverse(DMX_UNIVERSE, client.REGISTER, NewData)
client.RegisterUniverse(DMX_UNIVERSE, client.REGISTER, dmx_universe_update)
wrapper.Run()