Anytime Help Center

Contact Us

If you still have questions or prefer to get help directly from an agent, please submit a request.
We’ll get back to you as soon as possible.

Please fill out the contact form below and we will reply as soon as possible.

  • Support
  • Guest
  • Log In
  • AKG
    Microphones Wireless Integrated Systems Automatic Mixers Headphones Discontinued Products (AKG) General AKG Inquiries Certifications (AKG) Video Manual Series (AKG)
  • AMX
    Networked A/V Distribution (AVoIP) Traditional A/V Distribution Video Signal Processing Architectural Connectivity User Interfaces Control Processing Power (AMX) Programming (AMX) Software (AMX) Discontinued Products (AMX) Video Manual Series (AMX) General AMX Inquiries Certifications (AMX)
  • BSS
    Soundweb™ Omni Soundweb™ London Soundweb™ Contrio™ Software (BSS) Discontinued Products (BSS) Video Manual Series (BSS) General BSS Inquiries Certifications (BSS)
  • Crown
    CDi DriveCore Series CDi Series Commercial Series ComTech Series DCi DriveCore Series I-Tech HD Series XLC series XLi Series XLS DriveCore 2 Series XTi 2 Series Discontinued Products (Crown) Software (Crown) General Crown Inquiries Certifications (Crown) Video Manual Series (Crown)
  • dbx
    CX Series 500 Series DriveRack Personal Monitor Control ZonePRO Zone Controllers FeedBack Suppression Microphone Preamps Dynamics Processors Crossovers Equalizers Software (dbx) Discontinued Products (dbx) General dbx Inquiries Certifications (dbx)
  • Flux::
    Immersive Processing Analysis Subscriptions General FLUX: Inquiries
  • JBL
    Cinema Sound Installed Live Portable Tour Sound Recording & Broadcast Software (JBL) Discontinued Products (JBL) Video Manual Series (JBL) General JBL Inquiries Certifications (JBL)
  • Lexicon
    Plugins Effects Processors Cinema Discontinued Products (Lexicon) Video Manual Series (Lexicon) General Lexicon Inquiries Certifications (Lexicon)
  • Martin
    Atomic ELP ERA Exterior MAC P3 VC VDO Tools Discontinued Products (Martin) General Martin Inquiries Certifications (Martin)
  • Soundcraft
    Digital Analog Connected Analog Only Discontinued Products (Soundcraft) Video Manual Series (Soundcraft) General Soundcraft Inquiries Certifications (Soundcraft)
  • General HARMAN Inquiries
    Dante
+ More
  • Home
  • AMX
  • Programming (AMX)
  • Muse

Panel Groups Helper Class for MUSE in Python

Coding Example

Written by Wesley Moore

Updated at July 16th, 2026

Table of Contents

Brand: Models/Languages: Overview: Usage:  Downloads:  Code: 

Brand:

  • AMX
  • BSS

Models/Languages:

  • MUSE
    • Programming
      • Python

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)
  • 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_group.py

Panel Groups Example.zip

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)

 

 

Related Videos

blueprint framework

Was this article helpful?

Yes
No
Give feedback about this article

Table of Contents

Brand: Models/Languages: Overview: Usage:  Downloads:  Code: 

Related Articles

  • Muse Mutually Exclusive helper
  • File Read Write with MUSE
  • MUSE Controller-to-Controller Programming with Python
  • Controlling CE-REL8 Relay Ports with Python Code
  • Controlling MUSE Relay Ports with Python Code

Related Articles

  • Muse Mutually Exclusive helper
  • File Read Write with MUSE
  • MUSE Controller-to-Controller Programming with Python
  • Controlling CE-REL8 Relay Ports with Python Code
  • Controlling MUSE Relay Ports with Python Code
Copyright © HARMAN Professional. All rights reserved. Privacy Policy | Terms of Use
Expand