Xantech / Dayton Audio / Sonance multi-zone amps

Here, for example, is the json for the Xantech in my v1. Works perfectly in my system. But I have been working on a rewrite that is more refined and powerful.

Note, it’s a commented jsonc file.

{
    "matrix":{
        "xantech_mx": {
            "general":{
                "description": "A generic template for a Xantech MX-88 Switcher",
                "supported_models": ["MX-88ai"]
            },
            /*
            Setup the basic configuration of the device
            */
            "config":{
                "interface": "tcp",
                "port": 4999,
                "prefix": "",                
                "suffix": "",
                "delimiterOut": "",                                                             //  Our commands include the + suffix, so no need to have one here
                "delimiterIn": "+\r",                                                           //  Received Responses are delimited by a + followed by \r
                "substitutionCharacter": "$",
                "statusResponseType": "regex",                                                  //  This means our default processing engine for analysing responses is regex
                "messageDelay": 250,                                                            //  The device should have a rate throttle of 4 messages a second

                "contentType": "text",                                                          //  The payload of the messafe is handled as text
                "messageFormat": "delimited",                                                   //  Messages are delimited
                "buffered": true,                                                               //  We want to use buffer management to handle messages that don't come in, in one burst.
                "contentTypeChoices": ["json", "html", "xml", "text", "byte"],
                "messageFormatChoices": ["delimited", "stxetx", "byte", "fixed", "complete"],
                "messageXform" : [{"from":"OK\r","to":""}, {"from":"ERROR\r","to":""}],         //  This device sends non-standard messages that we want to trap and throw away (hence the "")
                "messageTimeout": 1000,                                                         //  If we are awaiting a message, when should we timeout and move on (buffer management)
                "messageLength": 0,                                                             //  not used
                "messageLengthByte": 0,                                                         //  not used
                "messageLengthIncrement": 0                                                     //  not used
            },
            /*  commands:       Define commands that can be sent to the device.

                command:        the command string sent to the  device to do something
                                if its an http command, this is the GET/PUT/POST command
                                allows use of formula engine ${..}
                statusRequest:  is a corresponding Command that asks the device to report the status of the property.
                                allows use of formula engine ${..}
                valueMap:       is where we lookup the inbound human readable instruction (from a GUI or elsewhere) and translate it to the device code.
                                example: {"z1 power", "OFF"} -- we use the valueMap to translate OFF to 0. The ${%1} is the placeholder to insert that value.
                                if there is more than one value, we can use ${%1}, ${%2} etc..
                                if valueMap is missing, the system will look for a valueMap of the same name as the command.
                actionSet:      is for postprocessing.
                                "." is a special actionSet that says, after executing the command, execute a statusRequest. Typically used when the device doesn't automatically
                                update its status.
                template:       directs the system to use a template, for more complex command payloads or http body data
                category:       TODO
                advanced:       instructs the engine to execute advanced processing instructions
            */
            "commands": {
                "z1 power": {"command": "!1PR${%1}+", "statusRequest": "?1PR+", "valueMap":"power", "actionSet": "."},
                "z2 power": {"command": "!2PR${%1}+", "statusRequest": "?2PR+", "valueMap":"power", "actionSet": "."},
                "z3 power": {"command": "!3PR${%1}+", "statusRequest": "?3PR+", "valueMap":"power", "actionSet": "."},
                "z4 power": {"command": "!4PR${%1}+", "statusRequest": "?4PR+", "valueMap":"power", "actionSet": "."},
                "z5 power": {"command": "!5PR${%1}+", "statusRequest": "?5PR+", "valueMap":"power", "actionSet": "."},
                "z6 power": {"command": "!6PR${%1}+", "statusRequest": "?6PR+", "valueMap":"power", "actionSet": "."},
                "z7 power": {"command": "!7PR${%1}+", "statusRequest": "?7PR+", "valueMap":"power", "actionSet": "."},
                "z8 power": {"command": "!8PR${%1}+", "statusRequest": "?8PR+", "valueMap":"power", "actionSet": "."},
                "z1 input": {"command": "!1SS${%1}+", "statusRequest": "?1SS+", "valueMap":"input", "actionSet": "."},
                "z2 input": {"command": "!2SS${%1}+", "statusRequest": "?2SS+", "valueMap":"input", "actionSet": "."},
                "z3 input": {"command": "!3SS${%1}+", "statusRequest": "?3SS+", "valueMap":"input", "actionSet": "."},
                "z4 input": {"command": "!4SS${%1}+", "statusRequest": "?4SS+", "valueMap":"input", "actionSet": "."},
                "z5 input": {"command": "!5SS${%1}+", "statusRequest": "?5SS+", "valueMap":"input", "actionSet": "."},
                "z6 input": {"command": "!6SS${%1}+", "statusRequest": "?6SS+", "valueMap":"input", "actionSet": "."},
                "z7 input": {"command": "!7SS${%1}+", "statusRequest": "?7SS+", "valueMap":"input", "actionSet": "."},
                "z8 input": {"command": "!8SS${%1}+", "statusRequest": "?8SS+", "valueMap":"input", "actionSet": "."},
                "z1 mute": {"command": "!1MU${%1}+", "statusRequest": "?1MU+", "valueMap":"mute", "actionSet": "."},
                "z2 mute": {"command": "!2MU${%1}+", "statusRequest": "?2MU+", "valueMap":"mute", "actionSet": "."},
                "z3 mute": {"command": "!3MU${%1}+", "statusRequest": "?3MU+", "valueMap":"mute", "actionSet": "."},
                "z4 mute": {"command": "!4MU${%1}+", "statusRequest": "?4MU+", "valueMap":"mute", "actionSet": "."},
                "z5 mute": {"command": "!5MU${%1}+", "statusRequest": "?5MU+", "valueMap":"mute", "actionSet": "."},
                "z6 mute": {"command": "!6MU${%1}+", "statusRequest": "?6MU+", "valueMap":"mute", "actionSet": "."},
                "z7 mute": {"command": "!7MU${%1}+", "statusRequest": "?7MU+", "valueMap":"mute", "actionSet": "."},
                "z8 mute": {"command": "!8MU${%1}+", "statusRequest": "?8MU+", "valueMap":"mute", "actionSet": "."},
                "z1 volume": {"command": "!1VO${%1}+", "statusRequest": "?1VO+", "valueMap":"volume", "actionSet": "."},
                "z2 volume": {"command": "!2VO${%1}+", "statusRequest": "?2VO+", "valueMap":"volume", "actionSet": "."},
                "z3 volume": {"command": "!3VO${%1}+", "statusRequest": "?3VO+", "valueMap":"volume", "actionSet": "."},
                "z4 volume": {"command": "!4VO${%1}+", "statusRequest": "?4VO+", "valueMap":"volume", "actionSet": "."},
                "z5 volume": {"command": "!5VO${%1}+", "statusRequest": "?5VO+", "valueMap":"volume", "actionSet": "."},
                "z6 volume": {"command": "!6VO${%1}+", "statusRequest": "?6VO+", "valueMap":"volume", "actionSet": "."},
                "z7 volume": {"command": "!7VO${%1}+", "statusRequest": "?7VO+", "valueMap":"volume", "actionSet": "."},
                "z8 volume": {"command": "!8VO${%1}+", "statusRequest": "?8VO+", "valueMap":"volume", "actionSet": "."},
                "z1 volumedB": {"command": "!1VO${%1}+", "statusRequest": "?1VO+", "valueMap":"volumedB", "actionSet": "."},
                "z2 volumedB": {"command": "!2VO${%1}+", "statusRequest": "?2VO+", "valueMap":"volumedB", "actionSet": "."},
                "z3 volumedB": {"command": "!3VO${%1}+", "statusRequest": "?3VO+", "valueMap":"volumedB", "actionSet": "."},
                "z4 volumedB": {"command": "!4VO${%1}+", "statusRequest": "?4VO+", "valueMap":"volumedB", "actionSet": "."},
                "z5 volumedB": {"command": "!5VO${%1}+", "statusRequest": "?5VO+", "valueMap":"volumedB", "actionSet": "."},
                "z6 volumedB": {"command": "!6VO${%1}+", "statusRequest": "?6VO+", "valueMap":"volumedB", "actionSet": "."},
                "z7 volumedB": {"command": "!7VO${%1}+", "statusRequest": "?7VO+", "valueMap":"volumedB", "actionSet": "."},
                "z8 volumedB": {"command": "!8VO${%1}+", "statusRequest": "?8VO+", "valueMap":"volumedB", "actionSet": "."},
                "power": {"command": "!AO+", "statusRequest": "?1PR+"}
            },
            /* feedbacks:       Define responses received from a device.
                                Order is important as the feedback array is processed from top to bottom.
                                Multiple matches are permitted

                id:             a unique id for each feedback.
                statusReponse:  the Regex or Object to match against the inbound payload. when it's an object, we need
                                a key (the target object). a value (the required matching value). valueKey = where to find the 
                                value that we use in the valueMap lookup.
                                allows use of formula engine ${..}
                                {"key": "URI", "value":"/state/device/power_mode", "valueKey":"ITEMS.0.VALUE"}
                category:       the MQTT category or group that this feedback will show up in. default is "main"
                topic:          the actual descriptor of this feedback (defaults to id)
                sph:            instructs the engine to stop processing any more feedbacks.
                valueMap:       the valueMap to use to look up the human friendly display of the device property/feedback status.
                requestReply:   instructs the engine to require that the last "command" issued to the device, is a match, in
                                order for this feedback to be processed.
                xform:          tells the engine to convert the extracted value to a different dataType (i.e. a charcode becomes integer)
                actionSet:      tells the engine to send commands in a postProcessing actionSet.
                advanced:       instructs the engine to execute advanced processing instructions. overrides valueMap
            */
            "feedbacks": [
                {"id": "anyPower","statusResponse": "(?:^\\?)(\\d)PR(.{1})$", "category":"main", "advanced": true},
                {"id": "power","statusResponse": "(?:^\\?\\dPR)(.{1})$", "category":"main", "valueMap": "systemPower"},
                {"id": "z1 power","statusResponse": "(?:^\\?1PR)(.{1})$", "category":"zone 1", "sph":"end", "valueMap":"power"},
                {"id": "z2 power","statusResponse": "(?:^\\?2PR)(.{1})$", "category":"zone 2", "sph":"end", "valueMap":"power"},
                {"id": "z3 power","statusResponse": "(?:^\\?3PR)(.{1})$", "category":"zone 3", "sph":"end", "valueMap":"power"},
                {"id": "z4 power","statusResponse": "(?:^\\?4PR)(.{1})$", "category":"zone 4", "sph":"end", "valueMap":"power"},
                {"id": "z5 power","statusResponse": "(?:^\\?5PR)(.{1})$", "category":"zone 5", "sph":"end", "valueMap":"power"},
                {"id": "z6 power","statusResponse": "(?:^\\?6PR)(.{1})$", "category":"zone 6", "sph":"end", "valueMap":"power"},
                {"id": "z7 power","statusResponse": "(?:^\\?7PR)(.{1})$", "category":"zone 7", "sph":"end", "valueMap":"power"},
                {"id": "z8 power","statusResponse": "(?:^\\?8PR)(.{1})$", "category":"zone 8", "sph":"end", "valueMap":"power"},
                {"id": "z1 input","statusResponse": "(?:^\\?1SS)(.{1})$", "category":"zone 1", "sph":"end", "valueMap":"input"},
                {"id": "z2 input","statusResponse": "(?:^\\?2SS)(.{1})$", "category":"zone 2", "sph":"end", "valueMap":"input"},
                {"id": "z3 input","statusResponse": "(?:^\\?3SS)(.{1})$", "category":"zone 3", "sph":"end", "valueMap":"input"},
                {"id": "z4 input","statusResponse": "(?:^\\?4SS)(.{1})$", "category":"zone 4", "sph":"end", "valueMap":"input"},
                {"id": "z5 input","statusResponse": "(?:^\\?5SS)(.{1})$", "category":"zone 5", "sph":"end", "valueMap":"input"},
                {"id": "z6 input","statusResponse": "(?:^\\?6SS)(.{1})$", "category":"zone 6", "sph":"end", "valueMap":"input"},
                {"id": "z7 input","statusResponse": "(?:^\\?7SS)(.{1})$", "category":"zone 7", "sph":"end", "valueMap":"input"},
                {"id": "z8 input","statusResponse": "(?:^\\?8SS)(.{1})$", "category":"zone 8", "sph":"end", "valueMap":"input"},
                {"id": "z1 mute","statusResponse": "(?:^\\?1MU)(.{1})$", "category":"zone 1", "sph":"end", "valueMap":"mute"},
                {"id": "z2 mute","statusResponse": "(?:^\\?2MU)(.{1})$", "category":"zone 2", "sph":"end", "valueMap":"mute"},
                {"id": "z3 mute","statusResponse": "(?:^\\?3MU)(.{1})$", "category":"zone 3", "sph":"end", "valueMap":"mute"},
                {"id": "z4 mute","statusResponse": "(?:^\\?4MU)(.{1})$", "category":"zone 4", "sph":"end", "valueMap":"mute"},
                {"id": "z5 mute","statusResponse": "(?:^\\?5MU)(.{1})$", "category":"zone 5", "sph":"end", "valueMap":"mute"},
                {"id": "z6 mute","statusResponse": "(?:^\\?6MU)(.{1})$", "category":"zone 6", "sph":"end", "valueMap":"mute"},
                {"id": "z7 mute","statusResponse": "(?:^\\?7MU)(.{1})$", "category":"zone 7", "sph":"end", "valueMap":"mute"},
                {"id": "z8 mute","statusResponse": "(?:^\\?8MU)(.{1})$", "category":"zone 8", "sph":"end", "valueMap":"mute"},
                {"id": "z1 volume","statusResponse": "(?:^\\?1VO)(.*)$", "category":"zone 1", "valueMap":"volume", "sph":"end"},
                {"id": "z2 volume","statusResponse": "(?:^\\?2VO)(.*)$", "category":"zone 2", "valueMap":"volume", "sph":"end"},
                {"id": "z3 volume","statusResponse": "(?:^\\?3VO)(.*)$", "category":"zone 3", "valueMap":"volume", "sph":"end"},
                {"id": "z4 volume","statusResponse": "(?:^\\?4VO)(.*)$", "category":"zone 4", "valueMap":"volume", "sph":"end"},
                {"id": "z5 volume","statusResponse": "(?:^\\?5VO)(.*)$", "category":"zone 5", "valueMap":"volume", "sph":"end"},
                {"id": "z6 volume","statusResponse": "(?:^\\?6VO)(.*)$", "category":"zone 6", "valueMap":"volume", "sph":"end"},
                {"id": "z7 volume","statusResponse": "(?:^\\?7VO)(.*)$", "category":"zone 7", "valueMap":"volume", "sph":"end"},
                {"id": "z8 volume","statusResponse": "(?:^\\?8VO)(.*)$", "category":"zone 8", "valueMap":"volume", "sph":"end"},
                {"id": "z1 volumedB","statusResponse": "(?:^\\?1VO)(.*)$", "category":"zone 1", "valueMap":"volumedB", "sph":"end"},
                {"id": "z2 volumedB","statusResponse": "(?:^\\?2VO)(.*)$", "category":"zone 2", "valueMap":"volumedB", "sph":"end"},
                {"id": "z3 volumedB","statusResponse": "(?:^\\?3VO)(.*)$", "category":"zone 3", "valueMap":"volumedB", "sph":"end"},
                {"id": "z4 volumedB","statusResponse": "(?:^\\?4VO)(.*)$", "category":"zone 4", "valueMap":"volumedB", "sph":"end"},
                {"id": "z5 volumedB","statusResponse": "(?:^\\?5VO)(.*)$", "category":"zone 5", "valueMap":"volumedB", "sph":"end"},
                {"id": "z6 volumedB","statusResponse": "(?:^\\?6VO)(.*)$", "category":"zone 6", "valueMap":"volumedB", "sph":"end"},
                {"id": "z7 volumedB","statusResponse": "(?:^\\?7VO)(.*)$", "category":"zone 7", "valueMap":"volumedB", "sph":"end"},
                {"id": "z8 volumedB","statusResponse": "(?:^\\?8VO)(.*)$", "category":"zone 8", "valueMap":"volumedB", "sph":"end"}
                
            ],
            /*  valueMaps:          provide cross translation of device codes to human codes and vice versa.

                deviceValue:        the value required by (when inserted into a command), or reported by the device (when extracted from a feedback)
                                    deviceValues can also be formulaic using the ${} syntax. (see volume valueMap below)
                standardValue:      one or more standardized values that will trigger a command attached to this map. i.e. "power", "OFF" 
                displayValue:       the value to display to downstream GUI's or clients
                                    displayValues can also be formulaic using the ${} syntax (see volume valueMap below)
                feedbackActionSet:  an actionSet to be be run, if this valueMap is a result of a feedback. This allows actionSets to be linked to specific values.
                commandActionSet:   an actionSet to be be run, if this valueMap is a result of a command. This allows actionSets to be linked to specific values.
                clear:              reset an entire mqtt node and its children. possibly useful on Power Off for some/many devices.

            */
            "valuemaps":{
                "power": [
                    {"deviceValue": "0","standardValue": [0,"OFF"], "displayValue": "OFF"},
                    {"deviceValue": "1","standardValue": [1,"ON"], "displayValue": "ON"}
                ],
                "systemPower": [
                    {"deviceValue": "","standardValue":"", "displayValue": "${eval(['%z1p','%z2p','%z3p','%z4p','%z5p','%z6p','%z7p','%z8p'].indexOf('ON')>-1 ? 'ON':'OFF')}"}
                ],
                "mute": [
                    {"deviceValue": "0","standardValue": [0,"OFF"], "displayValue": "OFF"},
                    {"deviceValue": "1","standardValue": [1,"ON"], "displayValue": "ON"}
                ],
                "input": [
                    {"deviceValue": "1", "standardValue": [1, "INPUT1"], "displayValue": "INPUT 1"},
                    {"deviceValue": "2", "standardValue": [2, "INPUT2"], "displayValue": "INPUT 2"},
                    {"deviceValue": "3", "standardValue": [3, "INPUT3"], "displayValue": "INPUT 3"},
                    {"deviceValue": "4", "standardValue": [4, "INPUT4"], "displayValue": "INPUT 4"},
                    {"deviceValue": "5", "standardValue": [5, "INPUT5"], "displayValue": "INPUT 5"},
                    {"deviceValue": "6", "standardValue": [5, "INPUT6"], "displayValue": "INPUT 6"},
                    {"deviceValue": "7", "standardValue": [5, "INPUT7"], "displayValue": "INPUT 7"},
                    {"deviceValue": "8", "standardValue": [5, "INPUT8"], "displayValue": "INPUT 8"}
                ],
                "volume": [
                    {"deviceValue": "${range(0,38,1,0,100)}", "standardValue": "", 
                            "displayValue": "${range(0,100,1,0,38)}"}
                ],
                "volumedB": [
                    {"deviceValue": "${eval((parseInt(%1)+38))}", "standardValue": "", 
                            "displayValue": "${eval((parseInt((%1>18 ? (%1-38)*1.25 : %1>7 ? ((%1-25)*2.5)-7.5 : ((%1-52.5)*3.75)+118.125)*2)/2) + 'dB')}"}
                ]
            },
            
            "advanced":{
                "updatePowerVariable":{
                    "feedback": "anyPower",
                    "type": "variable",
                    "inArray": false,
                    "variableMap":{             
                        // all variables extracted in the variableMap are temporary.
                        "value" : "${%2}",      // the feedback extracts two matches. one for matrix channel {%1} and one for power on or off {%2)
                        "name" : "z${%1}p",     
                        "z${%1}p" : "${%2}"     // we can do clver things like make the variable name a variable
                    },
                    "valueMap": "power",
                    "valueMapVariable": "z${%1}p",  // when we call the valuemap, we need to specify which of the message value variables we
                    "outArray": false,              //          want to use. mostly it will be {%1}...but in this case its {%2}
                    "outArrayVariable": "",
                    "insertTemplate": {             // the insertTemplate can use any variable from the variable map OR the deviceMap.variables
                        "parent" : "variables",     // but it can't use the %1 etc.. as they don't exist in this scope
                        "node": "${%name}",
                        "template" : "${var(%name)}"    // var is a special command. instead of "${var(%name)}", we could also have written ${%value}
                    }
                }
            },

            "templates":{
                // not used
            },

            "actionSets":{
                "initialize":[
                    {"command": "z1 power", "value": "STATUS", "delay": "100"},
                    {"command": "z2 power", "value": "STATUS", "delay": "100"},
                    {"command": "z3 power", "value": "STATUS", "delay": "100"},
                    {"command": "z4 power", "value": "STATUS", "delay": "100"},
                    {"command": "z5 power", "value": "STATUS", "delay": "100"},
                    {"command": "z6 power", "value": "STATUS", "delay": "100"},
                    {"command": "z7 power", "value": "STATUS", "delay": "100"},
                    {"command": "z8 power", "value": "STATUS", "delay": "100"},
                    {"command": "z1 input", "value": "STATUS", "delay": "100"},
                    {"command": "z2 input", "value": "STATUS", "delay": "100"},
                    {"command": "z3 input", "value": "STATUS", "delay": "100"},
                    {"command": "z4 input", "value": "STATUS", "delay": "100"},
                    {"command": "z5 input", "value": "STATUS", "delay": "100"},
                    {"command": "z6 input", "value": "STATUS", "delay": "100"},
                    {"command": "z7 input", "value": "STATUS", "delay": "100"},
                    {"command": "z8 input", "value": "STATUS", "delay": "100"},
                    {"command": "z1 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z2 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z3 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z4 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z5 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z6 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z7 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z8 volume", "value": "STATUS", "delay": "100"},
                    {"command": "z1 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z2 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z3 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z4 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z5 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z6 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z7 mute", "value": "STATUS", "delay": "100"},
                    {"command": "z8 mute", "value": "STATUS", "delay": "100"}
                ]
            },

            "variables":{
                // not prefilled.   
            }
        }
    }
}

@skavan Thanks for posting your config, will be useful for me to read through to understand your mental model for interacting with a device. This is similar to what I’ve been working though (modeling the interfaces, what models it applied to, etc). What I’ve run across is that so much is shared, but slightly different across models. I’m looking at Apple PKL (relatively new and may not be a good option since limited support today), as well as Nickel, Nix, RCL, etc.

The biggest need for me is the ability to import portions of the configuration, ability to delete/override some configuration, easy to read, and as cross-language as possible (so other people can create similar tools in other languages using the existing configuration). Also would be cool to be able to generate YAML/JSON from one of the configurations, just for wider compatibility with other tools.

I should have something very interesting for you in a few weeks for IP control if the API over TCP is text based.

Cool. We should coordinate!
Here’s the beginnings of my version 2, supporting variables, regex and math expressions.
It also supports overrides. We should probably put into a repository instead of clogging up this channel.

  1. Predefined Capabilities (very basic so far):
[
    {
        "capability": "switch",
        "name": "power",
        "description": "Power",
        "actions": {
            "onoff": {
                "command": "",
                "arguments": [
                    {
                        "name": "setPower",
                        "type": "enum",
                        "enum": ["on", "off"]
                    }
                ],
                "valueMap": [
                    { "value": "on", "deviceValue": 1 },
                    { "value": "off", "deviceValue": 0 }
                ]
            },
            "status": { "command": "", "arguments": [] },
            "toggle": { "command": "", "arguments": [] }
        },
        "allowedValues": ["on", "off"],
        "displayFormat": ""
    },
    {
        "capability": "audioVolume",
        "name": "volume",
        "description": "Volume Control",
        "actions": {
            "volume": {
                "command": "",
                "arguments": [
                    {
                        "name": "setVolume",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100
                    }
                ]
            },
            "volumeUp": { "command": "", "arguments": [] },
            "volumeDown": { "command": "", "arguments": [] },
            "status": { "command": "", "arguments": [] }
        }
    },
    {
        "capability": "mediaInput",
        "name": "input",
        "description": "Media Source Control",
        "actions": {
            "input": {
                "command": "",
                "arguments": [
                    {
                        "name": "setInput",
                        "type": "enum",
                        "enum": ["Input 1", "Input 2", "Input 3", "Input 4", "Input 5", "Input 6", "Input 7", "Input 8"]
                    }
                ]
            }
        }
    },
    {
        "capability": "audioMute",
        "name": "mute",
        "description": "Audio Mute",
        "actions": {
            "onoff": {
                "command": "",
                "arguments": [
                    {
                        "name": "setMute",
                        "type": "enum",
                        "enum": ["on", "off"]
                    }
                ]
            },
            "status": { "command": "", "arguments": [] },
            "toggle": { "command": "", "arguments": [] }
        },
        "allowedValues": ["on", "off"],
        "displayFormat": ""
    }
]```

2. Beginnings of a revised Matrix controller.
```{
	"id": "i5TTEWeL",
	"name": "MultiZoneAmp",
	"description": "Xantech Matrix Switcher",
	"version": "1.0.0",
    "type": "Amplifier",
	"category": "AV",
	"need a name": "matrix switch",
	"license": "MIT",
	"author": "Suresh Kavan",
	"supportedModels": ["a", "b"],
	"protocol": {
		"interface": "tcp",
		"port": 4999,
		"delimiterOut": "",
		"delimiterIn": "+\r",
		"substitutionCharacter": "$",
		"responseParser": "regex",
		"messageDelay": 250,
		"messageTimeout": 1000
	},
    "preProcessorCommand": "{%pp}",
    "variablePlaceholder": "%",

	// first we have a device. (matrix001)
	// then we have a component (main)
	// then we have a capability (switch, audioVolume, mediaInput, audioMute)
	// capabilities have one or more properties,
	// that are defined by the capability statement
	// i.e. power, volume, volumePct, volumedB, mute, input

	// matrix001/main/switch/power
	// matrix001/main/audioVolume/volume
	// matrix001/main/audioVolume/volumedB
	// matrix001/main/audioVolume/volumePct
	// matrix001/main/audioMute/mute
	// matrix001/main/mediaInput/input

    // commands, components, feedbacks, variables can by of type "array"


	"components": [
		{
			"component": "main",
			"name": "main",
			"description": "Master Controls",
			// pull in the capabilty and override here
			"capabilities": [
				{
					"capability": "switch",
					"name": "power",
					"description": "All Zones Power"
				},
				{
					"capability": "audioVolume",
					"description": "All Zones Volume"
				},
				{
					"capability": "mediaInput",
					"description": "All Zones Source"
				},
				{
					"capability": "audioMute",
					"description": "All Zones Mute"
				}
			],
			"actions": {
				"switch": {
					"onoff": {
						"command": ["!1PR{%1}+", "!2PR{%1}+"]
					},
					"status": {
						"command": ["?1PR+", "?2PR+"]
					},
					"toggle": {
						"command": ["!1PT+", "!2PT+"]
					}
				},
				"audioVolume": {
					"volume": {
						"command": ["!1VO${%1}+", "!2VO${%1}+"],
						"arguments": [
							{
								"name": "setVolume",
								"type": "integer",
								"minimum": 0,
								"maximum": 38
							}
						]
					},
					"volumeUp": {
						"command": ["!1VI$1+", "!2VI$1+"],
						"arguments": []
					},
					"volumeDown": {
						"command": ["!1VD$1-", "!2VD$1-"],
						"arguments": []
					}
				}
			}
		},
		{
			"component": "zone{%pp}",
            // doesn't have to be integers, could be text too.
            "type": {"array": [1, 2, 3]} ,
			"name": "Zone{%pp}",
			"description": "Zone {%pp} Controls",
			"capabilities": [
				{
					"capability": "switch",
					"name": "power",
					"description": "Zone {%pp} Power"
				},
				{
					"capability": "audioVolume",
					"description": "Zone {%pp} Volume"
				},
				{
					"capability": "mediaInput",
					"description": "Zones {%pp} Source"
				},
				{
					"capability": "audioMute",
					"description": "Zone {%1} Mute"
				}
			],
			"actions": {
				"switch": {
					"onoff": {
						"command": "!{%pp}PR{%1}+"
					},
					"status": {
						"command": "?{%pp}PR+"
					},
					"toggle": {
						"command": "!{%pp}PT+"
					}
				},
				"audioVolume": {
					"volume": {
						"command": "!{%pp}VO{%1}+",
						"arguments": [
							{
								"name": "setVolume",
								"type": "integer",
								"minimum": 0,
								"maximum": 38
							}
						]
					},
					"volumeUp": {
						"command": "!{%pp}VI+",
						"arguments": []
					},
					"volumeDown": {
						"command": "!{%pp}VD+",
						"arguments": []
					}
				}
			}
		}
	],
    "feedbacks": [
        {"name": "Power", "match": "\\?(\\d)PO(\\d)\\+", "component": "zone{%1}", "capability": "switch", "value": "{%2}", "variables": ["zonePower"]},
		{"name": "Volume", "match": "\\?(\\d)VO(\\d{1,3})\\+", "component": "zone{%1}", "capability": "audioVolume", "value": "{%2}", "variables": ["zoneVolume"]}
    ],
    "variables":[
        {"name":"masterPower", "formula": "${zone1.switch}} + ${zone2.switch}} + ${zone3.switch} > 0", "type":"boolean"},
    ],

    // these override predefined capabilities
	"capabilities": [
        {
            "capability": "mediaInput",
            "name": "input",
            "description": "Media Source Control",
            "actions": {
                "input": {
                    "command": "",
                    "arguments": [
                        {
                            "name": "setInput",
                            "type": "enum",
                            "enum": ["Sonos", "Input 2", "Input 3", "Input 4", "Input 5", "Input 6", "Input 7", "Input 8"]
                        }
                    ]
                }
            }
        }
    ]
}

@ryans … please rememebr I also posted enhanced (hacked as best I could) code for bass/treble/balance. While I know that many set it and forget it, sometimes this can be valuable. My GUI now has this for every zone in my DAX:

Is there any chance you would consider cooperation to also bring the bass, treble and balance settings for the Soundavo ws66i integration? (now part of home assistant)
Browsing through the code, I see lines describing the setting of these, but I don’t see them in the actual entities/devices in home assistant

I am only a “hack” when it comes to python coding. I am hoping that @ryans will have time to review and eventually put this into the code for all the players (and done the way it should be).

As such, I chose not to create things beyond attributes off the media_player entity.

Then I implemented services (xantech.set_bass, xantech.set_treble, xantech.set_balance so that they can be changed in say an automation.

I just tied a input_number to these through an automation. The only thing I would guess is missing would be to have created those input_number entities under each zone, but I though that overkill (3 x 8 zones = 24 input_number entities … versus 3 attributes on each zone).

alias: Change DAX Bass_Treble_Balance
description: ""
trigger:
  - platform: state
    entity_id:
      - input_number.dax_bass_level
      - input_number.dax_treble_level
      - input_number.dax_balance_level
  - platform: state
    entity_id:
      - input_select.dax_zones
condition: []
action:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ trigger.entity_id == 'input_number.dax_treble_level' }}"
        sequence:
          - service: xantech.set_treble
            target:
              entity_id: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %} {{
                entity_id }} {% endif %} {% endfor %}
            data_template:
              treble: "{{ states(trigger.entity_id) | int }}"
      - conditions:
          - condition: template
            value_template: "{{ trigger.entity_id == 'input_number.dax_bass_level' }}"
        sequence:
          - service: xantech.set_bass
            target:
              entity_id: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %} {{
                entity_id }} {% endif %} {% endfor %}
            data_template:
              bass: "{{ states(trigger.entity_id) | int }}"
      - conditions:
          - condition: template
            value_template: "{{ trigger.entity_id == 'input_number.dax_balance_level' }}"
        sequence:
          - service: xantech.set_balance
            target:
              entity_id: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %} {{
                entity_id }} {% endif %} {% endfor %}
            data_template:
              balance: "{{ states(trigger.entity_id) | int }}"
      - conditions:
          - condition: template
            value_template: "{{ trigger.entity_id == 'input_select.dax_zones' }}"
        sequence:
          - service: input_number.set_value
            data_template:
              value: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %}
                {{state_attr(entity_id,'balance') }} {% endif %} {% endfor %}
            target:
              entity_id: input_number.dax_balance_level
          - service: input_number.set_value
            data_template:
              value: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %}
                {{state_attr(entity_id,'bass') }} {% endif %} {% endfor %}
            target:
              entity_id: input_number.dax_bass_level
          - service: input_number.set_value
            data_template:
              value: >
                {% set selzone = states('input_select.dax_zones') %} {% for
                entity_id in state_attr('group.dax_media_players','entity_id')
                %} {% if state_attr(entity_id,'friendly_name') == selzone %}
                {{state_attr(entity_id,'treble') }} {% endif %} {% endfor %}
            target:
              entity_id: input_number.dax_treble_level
mode: single

I suppose it could have been one service instead of three and set them all, but the service takes in the target entity and the value to set it to with value checking. This is for bass. Now that I write this it could have been just set level and likely took in bass, treble and balance.

Certainly this has the service references:

And the attributes as extras:

    @property
    def extra_state_attributes(self):
        """Return the extra state attributes for bass, treble, balance."""
        bass = self._status.get('bass')
        treble = self._status.get('treble')
        balance = self._status.get('balance')
        return {
            "bass": bass,
            "treble": treble,
            "balance": balance
        }

Carried in constants:

Of course, I made mods in the series and protocols of pyxantech:

The key to another integration would be to modify this I believe for your particular receiver. :

    set_volume:    '<{zone}VO{volume:02}'   # volume: 0-38
    set_treble:    '<{zone}TR{treble}:02}'  # treble: 0-14
    set_bass:      '<{zone}BS{bass:02}'     # bass: 0-14
    set_balance:   '<{zone}BL{balance:02}'  # balance: 0-20
    set_source:    '<{zone}CH{source:02}'   # source: 0-6

And also the overall get current state might need to be modified to be sure it returns bass, treble and balance and restore sets them.

  responses:
    zone_status: '>(?P<zone>\d\d)(?P<unknown>\d\d)(?P<power>[01]{2})(?P<mute>[01]{2})(?P<do_not_disturb>[01]{2})(?P<volume>\d\d)(?P<treble>\d\d)(?P<bass>\d\d)(?P<balance>\d\d)(?P<source>\d\d)(?P<keypad>\d\d)'

  extra:
    restore_zone:    [ 'set_power', 'set_source', 'set_volume', 'set_mute', 'set_bass', 'set_balance', 'set_treble' ]
    restore_success: "OK\r"

Tying it together, it rocks:

2 Likes

In regards to balance/bass/treble control questions/suggestions from@AlexMPH and @kbrown01, I created a new thread on adding this support to Home Assistant to allow ALL media player entities to provide support for these interfaces.

This is the best layer to add this so that it becomes codified as a standard that any media player integration can support.

Here is the thread, please feel free to start contributing there. I don’t have the time to setup an environment and test the changes to Home Assistant core right now or I would.

Hopefully some enthuasiastic developer takes this up as a fun project to add to core…it even would make a great first project.

See thread here:

YOU CAN ALSO VOTE FOR THIS FEATURE on the above link.

1 Like

Voted and thanks!

I added a comment that specifically this should be targeted at stereo controls and not surround sound systems.

1 Like

I’m new to HA and was hoping for some help getting this set up to control my Sonance C4630 SE in HA.
If I used this in the config

media_player:
  - platform: xantech
    type: sonance

I get an error saying
Invalid config for ‘xantech’ from integration ‘media_player’ at configuration.yaml, line 14: value must be one of [‘dax88’, ‘monoprice6’, ‘sonance6’, ‘xantech8’, ‘zpr68-10’] for dictionary value ‘type’, got ‘sonance’

If I change it to sonance6 I get this error.
Traceback (most recent call last):
File “/usr/src/homeassistant/homeassistant/helpers/entity_platform.py”, line 350, in _async_setup_platform
await asyncio.shield(awaitable)
File “/config/custom_components/xantech/media_player.py”, line 137, in async_setup_platform
amp = await async_get_amp_controller(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/pyxantech/init.py”, line 568, in async_get_amp_controller
protocol_config = PROTOCOL_CONFIG[protocol]
~~~~~~~~~~~~~~~^^^^^^^^^^
KeyError: ‘sonance6’

Any help would be appreciated… Joe

Did you make any headway with this ? I seem to be in the same boat.

I can now control the Xantech MRC88 with an Itach IP2SL from my Mac and via USB to Serial cable from a windows VM but I cannot seem to control via home assistant either via IP or USB. The media player entities show up but I’m unable turn them on and just get the error -

‘Failed to call service media_player/turn_on. unknown error’

Anyone have any ideas ? I’m running HA as a VM on an Unraid server if that makes any difference.

Edit: I can control the zones (on and off) via telnet switch through HA.

Also to add, this is the error showing in HA logs for all zones -

WARNING (MainThread) [custom_components.xantech.media_player] Failed updating Xantech House Audio zone 13 (Living Room):

Ok, I’ve managed to get it working and my issue appears to have been using 11,12,13 etc for my zones rather than 1, 2, 3…

Not sure why as I originally copied the example config from github and changed it but it seems to be working as expected now. Hopefully this helps anyone else who might come across the same issue in future.

Also, this is working over an iTach IP2SL.

hi all

so for the DAX88 - if you have say 6 pairs of speakers hard wired to it - you can cast youtube music (like you cast from the app on your phone) to any / all zones?

This seems like exactly what i’m looking for.

thank you

anybody else have this problem after upgrading to HA 2024.6 ?

Logger: homeassistant.config
Source: config.py:1440
First occurred: 11 June 2024 at 22:40:04 (1 occurrences)
Last logged: 11 June 2024 at 22:40:04

Platform error: media_player - cannot import name 'SerialException' from 'serial' (/usr/local/lib/python3.12/site-packages/serial/__init__.py)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config.py", line 1440, in _async_load_and_validate_platform_integration
    platform = await p_integration.integration.async_get_platform(domain)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1085, in async_get_platform
    platforms = await self.async_get_platforms((platform_name,))
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1162, in async_get_platforms
    import_future.result()
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1150, in async_get_platforms
    platforms.update(self._load_platforms(platform_names))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1075, in _load_platforms
    platform_name: self._load_platform(platform_name)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1249, in _load_platform
    cache[full_name] = self._import_platform(platform_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1281, in _import_platform
    return importlib.import_module(f"{self.pkg_path}.{platform_name}")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/util/loop.py", line 131, in protected_loop_func
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/config/custom_components/xantech/media_player.py", line 8, in <module>
    from serial import SerialException
ImportError: cannot import name 'SerialException' from 'serial' (/usr/local/lib/python3.12/site-packages/serial/__init__.py)

what I see that the serial package is really missing:

PyYAML-6.0.1.dist-info             packaging                          pyparsing
PyYAML.libs                        packaging-24.0.dist-info           pyparsing-3.1.2.dist-info
README.txt                         pathspec                           setuptools
_distutils_hack                    pathspec-0.12.1.dist-info          setuptools-69.5.1-py3.12.egg-info
_yaml                              pip                                yaml
awake                              pip-24.0-py3.12.egg-info           yamllint
awake-1.0-py3.12.egg-info          pkg_resources                      yamllint-1.35.1.dist-info
distutils-precedence.pth           pulsemixer-1.5.1.dist-info
➜  site-packages pip uninstall serial
WARNING: Skipping serial as it is not installed.
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
➜  site-packages pip uninstall pyserial
WARNING: Skipping pyserial as it is not installed.
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
➜  site-packages pip install pySerial                 
Looking in indexes: https://pypi.org/simple, https://wheels.home-assistant.io/musllinux-index/
Collecting pySerial
  Downloading https://wheels.home-assistant.io/musllinux-index/pyserial-3.5-py2.py3-none-any.whl.metadata (1.6 kB)
Downloading https://wheels.home-assistant.io/musllinux-index/pyserial-3.5-py2.py3-none-any.whl (90 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 90.6/90.6 kB 3.9 MB/s eta 0:00:00
Installing collected packages: pySerial
Successfully installed pySerial-3.5
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
➜  site-packages ls
PyYAML-6.0.1.dist-info             packaging-24.0.dist-info           pyserial-3.5.dist-info
PyYAML.libs                        pathspec                           serial
README.txt                         pathspec-0.12.1.dist-info          setuptools
_distutils_hack                    pip                                setuptools-69.5.1-py3.12.egg-info
_yaml                              pip-24.0-py3.12.egg-info           yaml
awake                              pkg_resources                      yamllint
awake-1.0-py3.12.egg-info          pulsemixer-1.5.1.dist-info         yamllint-1.35.1.dist-info
distutils-precedence.pth           pyparsing
packaging                          pyparsing-3.1.2.dist-info
➜  site-packages cd serial 
➜  serial ls
__init__.py     __pycache__     rs485.py        serialjava.py   serialutil.py   threaded        urlhandler
__main__.py     rfc2217.py      serialcli.py    serialposix.py  serialwin32.py  tools           win32.py

I tried reinstalling serial - it shows up but disappears after reboot…

After it is reinstalled, when I go to Developer Tools it still complains about missing “serial” and init.py

Configuration warnings
Platform error 'media_player' from integration 'xantech' - cannot import name 'SerialException' from 'serial' (/usr/local/lib/python3.12/site-packages/serial/__init__.py)

Anyone has an idea what happened?
For a while yesterday I had 2 USB SERIAL connected - one for DAYTON and one for Cambridge Audio CXA61. I have also installed CXA61 integration and forwarded another USB_to_serial (CXA61) to my VM running HA (on QNAP NAS).
After starting it the CXA61 integration did work but generated lags and only switching on was forwarded to CXA and it didn;t really react much.
After a restart or two both integrations broke and I started seeing the missing serial error.
I removed second serial (CXA61), made sure first serial is discovered as ttyUSB0 configured in xantech integration, restarted HA several times.
I even disconnected both serial and removed both integrations from config and made several restarts with no serials attached - no go.
I also rebooted QNAP host and also (software removed second serial (CXA61) passthrough and rebooted again with no serials attached,
Then rebooted with only xantech aserial attached.
no go.
no more ideas where to try…
Can anyobe help me solving this issue please?

Maybe this also has something to do with my problem?
I started seeing this in logs recently:

Logger: homeassistant.util.loop
Source: util/loop.py:84
First occurred: 04:57:11 (2 occurrences)
Last logged: 05:00:46

Detected blocking call to import_module inside the event loop by integration 'config' at homeassistant/components/config/core.py, line 38: res = await check_config.async_check_ha_config_file(request.app[KEY_HASS]) (offender: /usr/src/homeassistant/homeassistant/loader.py, line 1281: return importlib.import_module(f"{self.pkg_path}.{platform_name}")), please create a bug report at https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+config%22 Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "/usr/src/homeassistant/homeassistant/__main__.py", line 223, in <module> sys.exit(main()) File "/usr/src/homeassistant/homeassistant/__main__.py", line 209, in main exit_code = runner.run(runtime_conf) File "/usr/src/homeassistant/homeassistant/runner.py", line 190, in run return loop.run_until_complete(setup_and_run_hass(runtime_config)) File "/usr/local/lib/python3.12/asyncio/base_events.py", line 672, in run_until_complete self.run_forever() File "/usr/local/lib/python3.12/asyncio/base_events.py", line 639, in run_forever self._run_once() File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1988, in _run_once handle._run() File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run self._context.run(self._callback, *self._args) File "/usr/local/lib/python3.12/site-packages/aiohttp/web_protocol.py", line 452, in _handle_request resp = await request_handler(request) File "/usr/local/lib/python3.12/site-packages/aiohttp/web_app.py", line 543, in _handle resp = await handler(request) File "/usr/local/lib/python3.12/site-packages/aiohttp/web_middlewares.py", line 114, in impl return await handler(request) File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 92, in security_filter_middleware return await handler(request) File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 83, in forwarded_middleware return await handler(request) File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 26, in request_context_middleware return await handler(request) File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 242, in auth_middleware return await handler(request) File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 32, in headers_middleware response = await handler(request) File "/usr/src/homeassistant/homeassistant/helpers/http.py", line 73, in handle result = await handler(request, **request.match_info) File "/usr/src/homeassistant/homeassistant/components/http/decorators.py", line 81, in with_admin return await func(self, request, *args, **kwargs) File "/usr/src/homeassistant/homeassistant/components/config/core.py", line 38, in post res = await check_config.async_check_ha_config_file(request.app[KEY_HASS])

Of course I introduced more problems by upgrading HA to 2024.6.2 when playing with integrations - now I have no clue what might have caused this issue…

EDIT:
I can confirm that reverting back from CORE 2024.6.2 to CORE 2024.6.1 brings back the integration and it is working fine.
So I can confirm, that at least in my situation it is latest CORE update 2024.6.2 that was to blame. Heads up.

EDIT2:
So it was not the HA CORE 2024.6.2 fault.
Looks like adding second usb-serial device to my HA has broke it.
I have just tried to readd CXA61 via second serial to the same HA host, and it started again.
Actually upgrading to 2024.6.2 resolved my issue this time (after disconnecting second serial).
Is there a way to have 2 integrations for 2 audio devices connected through serial-usb adapter?

Just a question … do you have the standard integration or mine (which includes bass/treble/balance controls). I have not upgraded yet precisely because I have a total custom where I have those controls. Just want to be sure it isn;t something I did.

Doesn’t look like it from the errors, just usb to serial error but you never know.

I have installed integrarion from HACS - the original version.
I will be upgrading to yours soon.

Mine is a total hack for the Dayton Audio device. It has been discussed and the author (rightly so) thinks bass/treble/balance should be in the core media player so that everyone can enjoy such things.

I just use personal version until that happens.

1 Like

Hi - I am trying to figure out how to configure/program the DAX88 with HA over our ethernet network, using an IP address as the “port”, but I’m not sure how to do so. I have HACS installed, and I was able to download the “Xantech/Dayton Audio/Sonance Multi-Zone Amp Control for Home Assistant” package.

I went into the “Terminal” and changed directory to the “config” folder, and then edited the configuration.yaml file to add the following code:

media_player:

platform: xantech
type: dax88
port: socket://192.168.1.242:443
zones:
11:
name: “Living Front”
12:
name: “Living Back”
13:
name: “Living Center”
14:
name: “Kitchen”
15:
name: “Master Bedroom”
16:
name: “Basement Porch”
17:
name: “Living Subwoofer”
18:
name: “ZONE8”
sources:
1:
name: “input1”
2:
name: “input2”
3:
name: “Input3”
4:
name: “Input4”
5:
name: “HDTV”
6:
name: “Sonos”
7:
name: “Input7”
8:
name: "Wi-Fi"

I can go to “Quick reload” under the “Settings” menu, and the yaml seems to load just fine (I don’t get any warnings). Then, I added the “Media Control” card to one of my Dashboards, but it only shows our Sonos (it was one of the options I had to pick under the “Entity” drop down menu when adding the card).

At this point, I’m not sure what to do to get a list of speaker sets, their volumes, and the power button to the right (I’m assuming this is the ability to wake/sleep each speaker set) like others are showing in this thread. I get the feeling that I need to change something and/or install a different media player, but I’m not sure which one, how to get it, or how to install.

Any help would be greatly appreciated.

I think I made some progress here with the programming of the configuration.yaml.

When using port “888” as suggested here: GitHub - rsnodgrass/hass-xantech: Xantech Multi-Zone Matrix Audio for Home Assistant

I get an error that the “zones” cannot load.

When checking for the communicating ports using nmap, I get the following list of “open” ports: 53, 80, 443, 8899, 49152.

When I change the “port” in the configuration.yaml file to one of those above, the system now seems to recognize each “zone”. Although, there is a “Failed updating” error during startup (and thereafter).

And if I try to click the speaker in the Entities settings to turn on one of the zones, I get another error that says “failed to call service” “unknown error”.

The error logs show the following:

At this point I’m completely stuck. Perhaps this is not compatible over our ethernet connection? Any help would be appreciated.

I have Dax88 Dayton that has 6 + 2 Zones. We are using the Ethernet port to communicate with it via its own app for now.
Does HA work with Ethernet port or we must use the Serial converter cable?
Thank you