Fix QLC+ Integration

This commit is contained in:
Mattallmighty 2021-11-15 07:56:41 +11:00
parent adfea1d9fa
commit ec5f19ac8d
4 changed files with 78 additions and 79 deletions

8
.vscode/tasks.json vendored
View File

@ -24,9 +24,11 @@
{ {
"label": "[3]-[Frontend]:3000-start", "label": "[3]-[Frontend]:3000-start",
"type": "shell", "type": "shell",
// "command": "runuser -l vscode -c 'cd /workspaces/${workspaceFolderBasename}/frontend; yarn; yarn start'",
"command": "cd /workspaces/${workspaceFolderBasename}/frontend; yarn; yarn start", "command": "cd /workspaces/${workspaceFolderBasename}/frontend; yarn; yarn start",
"group": "build", "group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [] "problemMatcher": []
}, },
{ {
@ -60,7 +62,7 @@
"type": "shell", "type": "shell",
"command": "rm -rf /workspaces/${workspaceFolderBasename}/ledfx_frontend/*; runuser -l vscode -c 'cd /workspaces/${workspaceFolderBasename}/frontend; yarn build'", "command": "rm -rf /workspaces/${workspaceFolderBasename}/ledfx_frontend/*; runuser -l vscode -c 'cd /workspaces/${workspaceFolderBasename}/frontend; yarn build'",
"problemMatcher": [] "problemMatcher": []
}, }
], ],
"inputs": [ "inputs": [
{ {

View File

@ -50,7 +50,7 @@ function ConfirmationDialogRaw(props) {
// console.log("qlcInfo - Response: ", qlcInfo); // console.log("qlcInfo - Response: ", qlcInfo);
const effectNames = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.effect_set.event_filters.effect_name const effectNames = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.effect_set.event_filters.effect_name
//const effectCleared = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.effect_cleared.event_name //const effectCleared = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.effect_cleared.event_name
const SceneSet = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.scene_set.event_filters.scene_name const SceneSet = qlcInfo && qlcInfo.event_types && qlcInfo.event_types.scene_activated.event_filters.scene_name
const QLCWidgets = qlcInfo && qlcInfo.qlc_widgets && qlcInfo.qlc_widgets.sort((a,b) => parseInt(a[0]) - parseInt(b[0]) ) const QLCWidgets = qlcInfo && qlcInfo.qlc_widgets && qlcInfo.qlc_widgets.sort((a,b) => parseInt(a[0]) - parseInt(b[0]) )
const EVENT_TYPES= qlcInfo && qlcInfo.event_types && qlcInfo.event_types const EVENT_TYPES= qlcInfo && qlcInfo.event_types && qlcInfo.event_types
// console.log("test3",EVENT_TYPES); // console.log("test3",EVENT_TYPES);

View File

@ -1,5 +1,4 @@
import logging import logging
from json import JSONDecodeError
from aiohttp import web from aiohttp import web
@ -15,60 +14,70 @@ class QLCEndpoint(RestEndpoint):
ENDPOINT_PATH = "/api/integrations/qlc/{integration_id}" ENDPOINT_PATH = "/api/integrations/qlc/{integration_id}"
async def get(self, integration_id) -> web.Response: async def get(self, integration_id, request) -> web.Response:
"""Get info from QLC+ integration""" """Get info from QLC+ integration"""
integration = self._ledfx.integrations.get(integration_id) integration = self._ledfx.integrations.get(integration_id)
if (integration is None) or (integration.type != "qlc"): if (integration is None) or (integration.type != "qlc"):
response = {"not found": 404} response = {"not found": 404}
return web.json_response(data=response, status=404) return web.json_response(data=response, status=404)
response = {} data = await request.json()
info = data.get("info")
if info is None:
response = {
"status": "failed",
"reason": 'Required attribute "info" was not provided',
}
return web.json_response(data=response, status=500)
# generate dict of {effect_id: effect_name} if info == "event_types":
effect_names = [] # generate dict of {effect_id: effect_name}
for effect_type, effect in self._ledfx.effects.classes().items(): effect_names = []
effect_names.append(effect.NAME) for effect_type, effect in self._ledfx.effects.classes().items():
effect_names.append(effect.NAME)
scene_names = [] scene_names = []
for scene in self._ledfx.config["scenes"]: for scene in self._ledfx.config["scenes"]:
scene_names.append(self._ledfx.config["scenes"][scene]["name"]) scene_names.append(self._ledfx.config["scenes"][scene]["name"])
response["event_types"] = { response = {
Event.EFFECT_SET: { Event.EFFECT_SET: {
"event_name": "Effect Set", "event_name": "Effect Set",
"event_filters": {"effect_name": effect_names}, "event_filters": {"effect_name": effect_names},
}, },
Event.EFFECT_CLEARED: { Event.EFFECT_CLEARED: {
"event_name": "Effect Cleared", "event_name": "Effect Cleared",
"event_filters": {}, "event_filters": {},
}, },
Event.SCENE_SET: { Event.SCENE_SET: {
"event_name": "Scene Set", "event_name": "Scene Set",
"event_filters": {"scene_name": scene_names}, "event_filters": {"scene_name": scene_names},
}, },
} }
response["qlc_widgets"] = await integration.get_widgets() elif info == "qlc_widgets":
response = await integration.get_widgets()
response["qlc_listeners"] = integration.data elif info == "qlc_listeners":
response = integration.data
else:
response = {
"status": "failed",
"reason": f'Unknown info parameter "{info}"',
}
return web.json_response(data=response, status=500)
return web.json_response(data=response, status=200) return web.json_response(data=response, status=200)
async def put(self, integration_id, request) -> web.Response: async def put(self, integration_id, request) -> web.Response:
"""Toggle a QLC event listener""" """ Toggle a QLC event listener """
integration = self._ledfx.integrations.get(integration_id) integration = self._ledfx.integrations.get(integration_id)
if (integration is None) or (integration.type != "qlc"): if (integration is None) or (integration.type != "qlc"):
response = {"not found": 404} response = {"not found": 404}
return web.json_response(data=response, status=404) return web.json_response(data=response, status=404)
try: data = await request.json()
data = await request.json()
except JSONDecodeError:
response = {
"status": "failed",
"reason": "JSON Decoding failed",
}
return web.json_response(data=response, status=400)
event_type = data.get("event_type") event_type = data.get("event_type")
event_filter = data.get("event_filter") event_filter = data.get("event_filter")
@ -77,21 +86,21 @@ class QLCEndpoint(RestEndpoint):
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_type" was not provided', "reason": 'Required attribute "event_type" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if event_filter is None: if event_filter is None:
response = { response = {
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_filter" was not provided', "reason": 'Required attribute "event_filter" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if type(event_filter) is not dict: if type(event_filter) is not dict:
response = { response = {
"status": "failed", "status": "failed",
"reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ', "reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
# toggle the event listener # toggle the event listener
if not integration.toggle_event(event_type, event_filter): if not integration.toggle_event(event_type, event_filter):
@ -99,7 +108,7 @@ class QLCEndpoint(RestEndpoint):
"status": "failed", "status": "failed",
"reason": f"Could not find event with type {event_type} and filter {event_filter}", "reason": f"Could not find event with type {event_type} and filter {event_filter}",
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
# Save the configuration (integration will handle modifying "data") # Save the configuration (integration will handle modifying "data")
for _integration in self._ledfx.config["integrations"]: for _integration in self._ledfx.config["integrations"]:
@ -115,20 +124,13 @@ class QLCEndpoint(RestEndpoint):
return web.json_response(data=response, status=200) return web.json_response(data=response, status=200)
async def post(self, integration_id, request) -> web.Response: async def post(self, integration_id, request) -> web.Response:
"""Add a new QLC event listener or update an existing one""" """ Add a new QLC event listener or update an existing one """
integration = self._ledfx.integrations.get(integration_id) integration = self._ledfx.integrations.get(integration_id)
if (integration is None) or (integration.type != "qlc"): if (integration is None) or (integration.type != "qlc"):
response = {"not found": 404} response = {"not found": 404}
return web.json_response(data=response, status=404) return web.json_response(data=response, status=404)
try: data = await request.json()
data = await request.json()
except JSONDecodeError:
response = {
"status": "failed",
"reason": "JSON Decoding failed",
}
return web.json_response(data=response, status=400)
event_type = data.get("event_type") event_type = data.get("event_type")
event_filter = data.get("event_filter") event_filter = data.get("event_filter")
qlc_payload = data.get("qlc_payload") qlc_payload = data.get("qlc_payload")
@ -138,28 +140,28 @@ class QLCEndpoint(RestEndpoint):
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_type" was not provided', "reason": 'Required attribute "event_type" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if event_filter is None: if event_filter is None:
response = { response = {
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_filter" was not provided', "reason": 'Required attribute "event_filter" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if type(event_filter) is not dict: if type(event_filter) is not dict:
response = { response = {
"status": "failed", "status": "failed",
"reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ', "reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if qlc_payload is None: if qlc_payload is None:
response = { response = {
"status": "failed", "status": "failed",
"reason": 'Required attribute "qlc_payload" was not provided', "reason": 'Required attribute "qlc_payload" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
# Create a link between ledfx event and sending the payload # Create a link between ledfx event and sending the payload
integration.create_event(event_type, event_filter, True, qlc_payload) integration.create_event(event_type, event_filter, True, qlc_payload)
@ -178,20 +180,13 @@ class QLCEndpoint(RestEndpoint):
return web.json_response(data=response, status=200) return web.json_response(data=response, status=200)
async def delete(self, integration_id, request) -> web.Response: async def delete(self, integration_id, request) -> web.Response:
"""Delete a QLC event listener""" """ Delete a QLC event listener """
integration = self._ledfx.integrations.get(integration_id) integration = self._ledfx.integrations.get(integration_id)
if (integration is None) or (integration.type != "qlc"): if (integration is None) or (integration.type != "qlc"):
response = {"not found": 404} response = {"not found": 404}
return web.json_response(data=response, status=404) return web.json_response(data=response, status=404)
try: data = await request.json()
data = await request.json()
except JSONDecodeError:
response = {
"status": "failed",
"reason": "JSON Decoding failed",
}
return web.json_response(data=response, status=400)
event_type = data.get("event_type") event_type = data.get("event_type")
event_filter = data.get("event_filter") event_filter = data.get("event_filter")
@ -200,21 +195,21 @@ class QLCEndpoint(RestEndpoint):
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_type" was not provided', "reason": 'Required attribute "event_type" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if event_filter is None: if event_filter is None:
response = { response = {
"status": "failed", "status": "failed",
"reason": 'Required attribute "event_filter" was not provided', "reason": 'Required attribute "event_filter" was not provided',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
if type(event_filter) is not dict: if type(event_filter) is not dict:
response = { response = {
"status": "failed", "status": "failed",
"reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ', "reason": f'Invalid filter "{event_filter}", should be dictionary eg. {{ "scene_name" : "my scene" }} ',
} }
return web.json_response(data=response, status=400) return web.json_response(data=response, status=500)
# Delete the listener and event from data # Delete the listener and event from data
integration.delete_event(event_type, event_filter) integration.delete_event(event_type, event_filter)

View File

@ -62,7 +62,7 @@ class QLC(Integration):
self.restore_from_data(data) self.restore_from_data(data)
def restore_from_data(self, data): def restore_from_data(self, data):
"""Creates the event listeners from saved data""" """ Creates the event listeners from saved data """
if data is not None: if data is not None:
try: try:
for entry in data: for entry in data:
@ -84,7 +84,7 @@ class QLC(Integration):
return self._data return self._data
def create_event(self, event_type, event_filter, active, qlc_payload): def create_event(self, event_type, event_filter, active, qlc_payload):
"""Create or update event listener that sends a qlc payload on a specific event""" """ Create or update event listener that sends a qlc payload on a specific event """
# If it exists, remove the existing listener and update data # If it exists, remove the existing listener and update data
for idx, entry in enumerate(self._data): for idx, entry in enumerate(self._data):
_event_type, _event_filter, _active, _qlc_payload = entry _event_type, _event_filter, _active, _qlc_payload = entry
@ -110,7 +110,7 @@ class QLC(Integration):
) )
def delete_event(self, event_type, event_filter): def delete_event(self, event_type, event_filter):
"""Completely delete event listener and saved payload from data""" """ Completely delete event listener and saved payload from data """
# remove listener if it exists # remove listener if it exists
self._remove_listener(event_type, event_filter) self._remove_listener(event_type, event_filter)
# remove event and payload from data # remove event and payload from data
@ -123,7 +123,7 @@ class QLC(Integration):
) )
def toggle_event(self, event_type, event_filter): def toggle_event(self, event_type, event_filter):
"""Toggle a payload linked to event on or off""" """ Toggle a payload linked to event on or off """
# Update "active" flag in data # Update "active" flag in data
for idx, entry in enumerate(self._data): for idx, entry in enumerate(self._data):
_event_type, _event_filter, _active, _qlc_payload = entry _event_type, _event_filter, _active, _qlc_payload = entry
@ -149,7 +149,7 @@ class QLC(Integration):
return False # failed to find event_type with this event_filter return False # failed to find event_type with this event_filter
def _remove_listener(self, event_type, event_filter): def _remove_listener(self, event_type, event_filter):
"""Internal function to remove ledfx events listener if it exists""" """ Internal function to remove ledfx events listener if it exists """
for idx, entry in enumerate(self._listeners): for idx, entry in enumerate(self._listeners):
_event_type, _event_filter, listener = entry _event_type, _event_filter, listener = entry
if (_event_type == event_type) and (_event_filter == event_filter): if (_event_type == event_type) and (_event_filter == event_filter):
@ -159,7 +159,7 @@ class QLC(Integration):
break break
def _add_listener(self, event_type, event_filter, qlc_payload): def _add_listener(self, event_type, event_filter, qlc_payload):
"""Internal function that links payload to send on the specified event""" """ Internal function that links payload to send on the specified event """
def make_callback(qlc_payload): def make_callback(qlc_payload):
def callback(_): def callback(_):
@ -180,7 +180,7 @@ class QLC(Integration):
self._listeners.append((event_type, event_filter, listener)) self._listeners.append((event_type, event_filter, listener))
async def get_widgets(self): async def get_widgets(self):
"""Returns a list of widgets as tuples: [(ID, Type, Name),...]""" """ Returns a list of widgets as tuples: [(ID, Type, Name),...] """
# First get list of widgets (ID, Name) # First get list of widgets (ID, Name)
widgets = [] widgets = []
message = "QLC+API|getWidgetsList" message = "QLC+API|getWidgetsList"
@ -198,7 +198,7 @@ class QLC(Integration):
return widgets return widgets
async def _send_payload(self, qlc_payload): async def _send_payload(self, qlc_payload):
"""Sends payload of {id:value, ...} pairs to QLC""" """ Sends payload of {id:value, ...} pairs to QLC"""
for widget_id, value in qlc_payload.items(): for widget_id, value in qlc_payload.items():
await self._client.send(f"{int(widget_id)}|{value}") await self._client.send(f"{int(widget_id)}|{value}")
@ -238,12 +238,14 @@ class QLCWebsocketClient:
self.websocket = None self.websocket = None
self.url = url self.url = url
self.domain = domain self.domain = domain
self.session = aiohttp.ClientSession()
async def connect(self): async def connect(self):
"""Connect to the WebSocket.""" """Connect to the WebSocket."""
while True: while True:
try: try:
self.websocket = await aiohttp.ClientSession.ws_connect(self.domain) self.websocket = await self.session.ws_connect(self.url)
#self.websocket = await self.ws_connect(self.url)
return True return True
except aiohttp.client_exceptions.ClientConnectorError: except aiohttp.client_exceptions.ClientConnectorError:
_LOGGER.info( _LOGGER.info(
@ -276,7 +278,7 @@ class QLCWebsocketClient:
await self.websocket.send_str(message) await self.websocket.send_str(message)
# Every call to the logger is a performance hit # Every call to the logger is a performance hit
# _LOGGER.debug(f"Sent message {message} to {self.domain}") _LOGGER.debug(f"Sent message {message} to {self.domain}")
async def receive(self): async def receive(self):
"""Receive one message from the WebSocket.""" """Receive one message from the WebSocket."""
@ -299,4 +301,4 @@ class QLCWebsocketClient:
elif message.type == aiohttp.WSMsgType.CLOSED: elif message.type == aiohttp.WSMsgType.CLOSED:
break break
elif message.type == aiohttp.WSMsgType.ERROR: elif message.type == aiohttp.WSMsgType.ERROR:
break break