Panel Groups Helper Class for MUSE in Python
Coding Example
Table of Contents
Brand:
- AMX
- BSS
Models/Languages:
- MUSE
- Programming
- Python
- Programming
Overview:
This helper file allows you to create panel groups in your MUSE Python code. This allows you to use a single line of code instead of utilizing nested FOR loops to create watches and send commands to your panels. This will reduce the time needed to program and clutter in your code.
Usage:
- Import the PanelGroup class from panel_group
- from panel_group import PanelGroup
- Instantiate the PanelGroup class by giving it a list of your touch panels, and optionally, a reverse lookup dictionary. If you do not use the reverse lookup, it will make some pieces not work, i.e. the panel exception.
-
tp_group = PanelGroup(
[dvTP, dvTP2, dvTP3, dvTP4, dvTP5, dvTP6],
device_lookup)
-
tp_group = PanelGroup(
- Watches, commands, events, anything that needs to access the group can be utilized in the same way a single device is referenced
- tp_group.port[1].button[10].watch(levelMatch)
-
Special use case method except_panel can be used to send a message to every device EXCEPT the device used in the method
- tp_group.except_panel(event.device).port[1].send_command(f"^ENA-{event.id},0")
Downloads:
Panel Groups Example with Panel Files.zip
Code:
index.py
This is the main code running on the MUSE controller that imports the PanelGroup class.
from mojo import context
#Import PanelGroup from panel_group.py to create a panel group
from panel_group import PanelGroup
context.log.info('Begin program')
#Define all of your devices loaded to the controller
dvTP = context.devices.get("AMX-10001")
dvTP2 = context.devices.get("AMX-10002")
dvTP3 = context.devices.get("AMX-10003")
dvTP4 = context.devices.get("AMX-10004")
dvTP5 = context.devices.get("AMX-10005")
dvTP6 = context.devices.get("AMX-10006")
#This creates a reverse lookup so you can get the object name from the device's string name
device_lookup = {
"AMX-10001": dvTP,
"AMX-10002": dvTP2,
"AMX-10003": dvTP3,
"AMX-10004": dvTP4,
"AMX-10005": dvTP5,
"AMX-10006": dvTP6
}
#Create a PanelGroup named tp_group, and pass it an array of touch panel objects as well as the device_lookup
#The device lookup isn't actually necessary, and if you don't send that, it just creates a blank dictionary
tp_group = PanelGroup(
[dvTP, dvTP2, dvTP3, dvTP4, dvTP5, dvTP6],
device_lookup
)
#This allows you to create watches for groups of buttons that all use the same callback function
#PanelGroups support lists/tupples/sets
ports = [1, 2]
transportButtons = [1, 2, 3]
menuButtons = [4, 5, 6]
#Function parses out information from a button event and allows you to access each piece later on,
#most importnt is path split, don't necessarily need all of this
def processButtonEvent(event):
path_parts = event.path.split('/')
port_index = path_parts.index("port") + 1
port_number = int(path_parts[port_index])
button_id = int(event.id)
return {
"port_number": port_number,
"id": button_id,
"value": event.value,
"device": event.device
}
#Callback functions that process transport and menu buttons
#For this example all they do is turn the button feedback on for each panel
#Rather than using loops and other functions, it uses ONLY the tp_group object exactly as you would use the device object
def processTransportButtons(event):
event_info = processButtonEvent(event)
tp_group.port[event_info["port_number"]].channel[event_info["id"]].value = event.value
def processMenuButtons(event):
event_info = processButtonEvent(event)
tp_group.port[event_info["port_number"]].channel[event_info["id"]].value = event.value
#Watch events created for every button, on every port, of every touch panel with one line rather than 4 nested FOR loops
#Supports lists/tupples/sets when creating multiple watches
tp_group.port[ports].button[transportButtons].watch(processTransportButtons)
tp_group.port[ports].button[menuButtons].watch(processMenuButtons)
#Example of creating an Online event for all panels
#If a panel comes online it sets all panels to the default state by sending the word Online to button 1
def TPOnlineEvent(event):
tp_group.port[1].send_command("^TXT-1,0,Online")
tp_group.online(TPOnlineEvent)
#This creates a button event that, when pressed, sends the word Offline to every panel
#When the button is released it sends Online to every panel
def tpSetOffline(event):
if(event.value):
tp_group.port[1].send_command("^TXT-1,0,Offline")
else:
tp_group.port[1].send_command("^TXT-1,0,Online")
tp_group.port[1].button[7].watch(tpSetOffline)
#It is possible to get into an odd state if the same bargraph on two different panels are pressed at the same time
#This uses a special method form PanelGroup to send a message to every panel EXCEPT the one pressing the button
#The except_panel method can use the device's string name OR object name,
#if the string name is used it uses the reverse lookup to get the object name
#In this case when a bargraph is pressed, it disables the same bargraph on all the other panels
#When the bargraph is released, it enables all bargraphs and sets them to the value of the panel that was pressed
def levelMatch(event):
if(event.value):
tp_group.except_panel(event.device).port[1].send_command(f"^ENA-{event.id},0")
if(not event.value):
tp_group.except_panel(event.device).port[1].send_command(f"^ENA-{event.id},1")
tp_group.port[1].level[1].value = device_lookup[event.device].port[1].level[1].value
tp_group.port[1].button[10].watch(levelMatch)
#Creates a level event for every panel
#As an example, we create a log of the value, if this were a real scenario you would be sending values to a device
def levelEvent(event):
context.log.info(f"Send {event.value} to device")
tp_group.port[1].level[1].watch(levelEvent)
# leave this as the last line in the Python script
context.run(globals())
panel_group.py
This is the imported file to create your groups.
class BroadcastProxy:
"""
A generic proxy that forwards attribute access, item access,
assignment, and method calls to multiple underlying objects.
This allows syntax like:
tp_group.port[1].channel[5].value = True
to mean:
for panel in panels:
panel.port[1].channel[5].value = True
"""
def __init__(self, items):
object.__setattr__(self, "_items", list(items))
def __getattr__(self, name):
"""
Called when accessing an attribute, such as:
tp_group.port
tp_group.port[1].channel
tp_group.port[1].button[5].watch
It gathers that attribute from every object currently represented
by this proxy and returns another proxy.
"""
return BroadcastProxy(
getattr(item, name)
for item in self._items
)
def __getitem__(self, key):
"""
Called when using square brackets, such as:
tp_group.port[1]
tp_group.port[1].channel[5]
Also supports lists/tuples/sets, so you can do:
tp_group.port[[1, 2]].button[[1, 3, 4]].watch(callback)
"""
results = []
if isinstance(key, (list, tuple, set)):
for item in self._items:
for single_key in key:
results.append(item[single_key])
else:
for item in self._items:
results.append(item[key])
return BroadcastProxy(results)
def __setattr__(self, name, value):
"""
Called on assignment, such as:
tp_group.port[1].channel[5].value = True
This is where the broadcast assignment happens.
"""
if name.startswith("_"):
object.__setattr__(self, name, value)
return
for item in self._items:
setattr(item, name, value)
def __call__(self, *args, **kwargs):
"""
Called when the proxied object is callable, such as:
tp_group.port[1].button[5].watch(callback)
In that example, .watch becomes a proxy around each panel's
watch method, and then this calls all of them.
"""
results = []
for item in self._items:
results.append(item(*args, **kwargs))
return results
def items(self):
"""
Return the underlying objects represented by this proxy.
Useful for debugging.
"""
return list(self._items)
def first(self):
"""
Return the first underlying object.
Useful when you intentionally only want one result.
"""
if not self._items:
return None
return self._items[0]
class PanelGroup:
"""
Represents a group of touch panels.
Attribute access on this object is forwarded to all panels.
"""
def __init__(self, panels, device_lookup = None):
self.panels = list(panels)
self.device_lookup = device_lookup or {}
def __getattr__(self, name):
"""
Allows:
tp_group.port
to behave like:
[panel.port for panel in self.panels]
"""
return BroadcastProxy(
getattr(panel, name)
for panel in self.panels
)
def except_panel(self, excluded_panel):
"""
Exclude a panel from the group.
excluded_panel can be either:
- the real panel object
- the physical device name string from event.device
"""
if isinstance(excluded_panel, str):
excluded_panel = self.device_lookup.get(excluded_panel, None)
return PanelGroup(
[
panel
for panel in self.panels
if panel != excluded_panel
],
self.device_lookup
)
def __iter__(self):
"""
Allows manual iteration if you ever still need it.
"""
return iter(self.panels)
def items(self):
"""
Return the real panel objects.
"""
return list(self.panels)
Table of Contents