Monoprice 6-zone Audio amp items, sitemap & rules

This is really good work!

I have just installed two of these amps as master/slave and will be trying your code out.

It would be nice to also see some examples how you have set up the audio sources together with the amps in the UI. What kind of players do you use and how to make a good user interface?

@mrfrenzy
The sitemap code above is the majority of what I have done for an interface. I am using Habpanel for an interface in one of my zones (a bathroom) and I intend on expanding the usage of Habpanel in the remaining zones, as I get time/need.

I currently have all 6 output zones from the amp set up. Currently, I only use 3 inputs/sources. I have a DirectTV STB, and Onkyo and Yamaha receivers. The receivers are completely controllable with currently available OH bindings.

I can control all I have hooked up with the sitemap, although it is very basic.

On my todo list, is setting up the PA functionality of the amp to work with the voice functionality in OH.

@mrfrenzy

Here’s what my sitemap looks like (I haven’t incorporated @swamiller 's code improvements into my setup yet, but you’ll get the idea:

OFF:

ON:

I’ve connected 4 inputs on my amp, but mostly use the Chromecast Audio I have connected to it. I have 4 zones outputting around the house and in my shed.

@swamiller, I’m trying to merge your code changes back into mine (have to use only a single status item for serial connections and remove the hex characters), but I think I’m having a problem getting the mapdb persistence to work (which is making all my commands send to a “null” zone and error out). I haven’t used mapdb persistence until this (still using rrd4j). Can you post your persistence strategy config file? It would be useful for everyone implementing this, I’m sure.

Thanks!

EDIT: Ignore this post, see my response below…

So, after looking over @rlkoshak’s suggestions again, I saw the “triggeringItem” variable he mentioned - what a wonderful, long-overdue addition to the rules engine!!! :slight_smile: I removed the mapdb persistence from your rules, @swamiller, and edited my top post to add the serial-based implementation of your changes.

Thanks again for your hard work improving my original code!

You already have examples code here to consolidate a lot of this code. Note, you can consolidate even more if you were to upgrade to the 2.3 snapshot and use Features for Rules that work with Groups. I’m not recommending that, but be aware there are changes coming in 2.3 that will make rules like this even easier.

rule "Process Update"
when
    Item MonoPrice_Status_G3 received update or 
    Item MonoPrice_Status_G4 received update or 
    Item MonoPrice_Status_G6 received update or
    Item MonoPrice_Status_G7 received update or
    Item MonoPrice_Status_G8 received update or 
    Item MonoPrice_Status_G9 received update  or
    Item MonoPrice_Status_G10 received update
then
    val num = MonoPrice_Status_G1.state as Number

    val type = triggeringItem.name.split("_").get(2)

    switch(type){
        case "G3": postUpdate("MonoPrice_Z"+num+"_Power", if(triggeringItem.state == "01") "ON" else "OFF")
        case "G4": postUpdate("MonoPrice_Z"+num+"_Mute", if(triggeringItem.state == "01") "ON" else "OFF")
        case "G6": {
            val vol = (triggeringItem.state as Number) / 38 * 100
            postUpdate("MonoPrice_Z"+num+"_Volume", vol.toString)
        }
        case "G7": {
            val tre = (triggeringItem.state as Number) / 14 * 100
            postUpdate("MonoPrice_Z"+num+"_Treble", tre.toString)
        }
        case "G8": {
            val bas = (triggeringItem.state as Number) / 14 * 100
            postUpdate("MonoPrice_Z"+num+"_Bass", bas.toString)
        }
        case "G9": {
            val bal = (triggeringItem.state as Numnber) / 20 * 100
            postUpdate("MonoPrice_Z"+num+"_Balance", bal.toString)
        }
        case "G10": {
             postUpdate("MonoPrice_Z"+num+"_Source", triggeringItems.toString)
        }
    }
end

You can collapse your switch statements in your zone rules to

    val znum = triggeringItem.name.split("_").get(1) 
    val zone = znum.substring(string.length() - 1)

For the treble rule you just need to add the line:

val tone_z = zone+"TR"

Even if there were not these String parsing shortcuts, you could use a lambda to consolidate the switch statements

These suggestions should drop your LOC by a third or so. No arrays necessary.

1 Like

@rlkoshak, VERY COOL!! Thanks for the heads up, that Member Of group handler will do wonders for my rules, especially the metric crap-ton of JSON rules in my MQTT LED strip implementation ( NodeMCU MQTT LED Strip Controller Build & Config How-To Videos ). I could already improve that code tenfold with triggeringItem, but I’ll wait until 2.3 is officially out and then do it all in one shot.

BTW, the post you replied to is ~2.5 years old, back when I was just starting my adventure with OH :slight_smile:

1 Like

Somehow I got mixed up.

At least pointing out the group triggers was useful.

This amp is currently on sale. I’m wondering if this is truly a sale or if it’s just been marked down as it’s a few years old; has anyone been keeping track?

For those who own it: are you overall happy? I’m thinking about picking one up for my new house, though I haven’t purchased the home yet. I’m wary to pick up yet another piece of equipment to move, but the sale price is compelling.

That is a great price for it (I paid $550 for mine in 2014). They may be lowering the price since it’s an older product now, so I don’t know if this is the lowest it’s been or not.

Overall, I’m very happy with it - I use it with a Chromecast Audio as pretty much the only input. Combined it with their in wall speakers and I get quality audio al over the house (using 4 of the 6 zones).

Btw, hopefully their QA got better, but first two amps they sent me didn’t even turn on - third one is solid. Monoprice customer support was top notch though, expedited shipping on the two replacement amps. They let me keep one of the bad ones, so I have spare parts I guess :slight_smile:)

I’ve had mine for about 4 years and it’s been working great. I am controlling 6 zones and have 4 different sources. The only issue I had in the past was integration with my automation system and general control over my network and OH (along with Global Cache iTach Flex) solved that problem for me.

Also note this amp is also sold as a Factor Electronics V-66.

https://www.mcssl.com/store/factorelectronics/001v-66

I have found the Factor Electronics manual was better in some areas than the Monoprice version.

Another helpful app I found:

The app works great but doesnt always play nice with my OH rules.

1 Like

@swamiller @bartus

Thanks for the feedback and tips! I ordered one and am looking forward to putting it to use!

Thanks for sharing this wonderful information.

First, thank you all for sharing!
It sure saved me a day or two when trying to get my two Dayton DAX66 up and running with openHAB.
The communication is done with a cheap RS232<->USB adapter on a Linux system.
At first glance everything seemed to work with a single unit but further testing showed issues especially with all the sleeps in the rules. After a few tries I understood that a quick fix wasn’t going to work. So I ended up rewriting all the rules with the previous posted contribution as a base.

First of all. This version will not be my final, just a good enough version for now. There is for sure some more tweaks I would like to do but time is up for now :).

Here is a list of the main issue fix:

  1. To keep the user UI feedback experience at an acceptable level the zone states will not be updated until no input has been given during 1 second.
  2. Issues with the math and integer rounding at dimmer value calculation caused some item values getting stuck.
  3. The rules would grow way too much if support for slaves would be added.

Except for improving timing/sleep issues I also added some new features:

  1. The rules can now fully handle both the Master and any Slaves that may be connected.
  2. openHAB can now detect when master unit is powered ON and will automatically start to update all states.
  3. Three new status items that shows which units that is alive (answering)
  4. The stereo balance gauge is made of two dimmers that is synchronized. (Note: You can still use only one if you want to. It just felt a bit awkward for me with a single gauge for this setting.)

And a TODO list:

  1. The Dimmer calculation rule is quite intact. That part can surely be improved.
  2. Sorry @rlkoshak, I didn’t add a Gate Keeper as sophisticated as your one in your super pattern article.
  3. In order to make the rules significantly shorter I had to fiddle with the Item names to fit a get-item-by-name solution. Don’t know right now if this is a really terrible solution.

It’s been years since I did anything with openHAB so some of my ideas are probably quite terrible but I haven’t noticed anything and it is working smoothly except for an issue with the Google Home binding that is mentioned here https://community.openhab.org/t/help-with-a-few-item-warnings-oh-2-3/45832

My units are of the type Dayton Audio DAX66 but other identical brands are:

  • Monoprice 10761/MPR-SG6Z
  • Factor Electronics V66
  • McLelland MAP-1200HD
  • Omnitronic MCS-1250

Items (with Master + Slave 1)
Note: All “Audio_Zones_Dax66Amp” strings can be replaced. I think the last underscore in each item name must be kept due to dependency in a rule.

Group       gDax66AliveCheck (All)
Group       gDax66PowerSwitch (All)
Group       gDax66Volume (All)
Group       gDax66Mute (All)
Group       gDax66Tone (All)
Group       gDax66Balance (All)
Group       gDax66Source (All)
Group       gDax66CommandReceiver (All)
Group       gDax66DoNotDisturb (All)
Group       gDax66KeypadPower (All)
Group       gDax66PublicAddress (All)

/*============================================= Master unit ==============================================*/

String      Audio_Zones_Dax66Amp_RS232_Buffer    "Dax66 Send/Receive RS232 data buffer"      { serial="/dev/ttyUSB0@9600" }
String      Audio_Zones_Dax66Amp_Send            "Dax66 Send RS232 data"
String      Audio_Zones_Dax66Amp_Receive         "Dax66 Last Received RS232 data"

Switch      Audio_Zones_Dax66Amp_Alive_Check_10  "Master unit is [%s]"                  (gDax66AliveCheck)
Switch      Audio_Zones_Dax66Amp_Alive_Check_20  "Slave 1 unit is [%s]"                 (gDax66AliveCheck)
Switch      Audio_Zones_Dax66Amp_Alive_Check_30  "Slave 2 unit is [%s]"                 (gDax66AliveCheck)

Switch      Audio_Zones_Dax66Amp_Refresh         "Alive check and complete status refresh"

Switch      Audio_Zones_Dax66Amp_10PA            "All zones PA [%s]"                    <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_11PA            "Zone 1 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_12PA            "Zone 2 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_13PA            "Zone 3 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_14PA            "Zone 4 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_15PA            "Zone 5 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_16PA            "Zone 6 PA [%s]"                       <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_10PR            "All zones power [%s]"                 <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_11PR            "Zone 1 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_12PR            "Zone 2 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_13PR            "Zone 3 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_14PR            "Zone 4 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_15PR            "Zone 5 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_16PR            "Zone 6 power [%s]"                    <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_10MU            "All zones mute [%s]"   	            <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_11MU            "Zone 1 mute [%s]"   	                <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_12MU            "Zone 2 mute [%s]"    	                <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_13MU            "Zone 3 mute [%s]"    	                <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_14MU            "Zone 4 mute [%s]"    	                <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_15MU            "Zone 5 mute [%s]"     	            <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_16MU            "Zone 6 mute [%s]"    	                <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_10DT            "All zones do not disturb [%s]"        <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_11DT            "Zone 1 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_12DT            "Zone 2 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_13DT            "Zone 3 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_14DT            "Zone 4 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_15DT            "Zone 5 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_16DT            "Zone 6 do not disturb [%s]"           <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_10VO            "All zones volume [%d %%]"             <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_11VO            "Zone 1 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_12VO            "Zone 2 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_13VO            "Zone 3 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_14VO            "Zone 4 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_15VO            "Zone 5 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_16VO            "Zone 6 volume [%d %%]"                <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_10TR            "All zones treble [%d %%]"             <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_11TR            "Zone 1 treble [%d %%]"                <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_12TR            "Zone 2 treble [%d %%]"                <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_13TR            "Zone 3 treble [%d %%]"                <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_14TR            "Zone 4 treble [%d %%]"                <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_15TR            "Zone 5 treble [%d %%]"                <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_16TR            "Zone 6 treble [%d %%]"                <treble>	        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_10BS            "All zones bass [%d %%]"  	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_11BS            "Zone 1 bass [%d %%]"  	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_12BS            "Zone 2 bass [%d %%]"  	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_13BS            "Zone 3 bass [%d %%]"   	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_14BS            "Zone 4 bass [%d %%]"   	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_15BS            "Zone 5 bass [%d %%]"    	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_16BS            "Zone 6 bass [%d %%]"    	            <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_10BLL           "All zones balance Left [%d %%]"       <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_11BLL           "Zone 1 balance Left [%d %%]"          <control>           (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_12BLL           "Zone 2 balance Left [%d %%]" 	        <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_13BLL           "Zone 3 balance Left [%d %%]" 	        <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_14BLL           "Zone 4 balance Left [%d %%]" 	        <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_15BLL           "Zone 5 balance Left [%d %%]" 	        <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_16BLL           "Zone 6 balance Left [%d %%]" 	        <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_10BLR           "All zones balance Right [%d %%]"      <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_11BLR           "Zone 1 balance Right [%d %%]"         <control>           (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_12BLR           "Zone 2 balance Right [%d %%]"         <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_13BLR           "Zone 3 balance Right [%d %%]"         <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_14BLR           "Zone 4 balance Right [%d %%]"         <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_15BLR           "Zone 5 balance Right [%d %%]"         <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_16BLR           "Zone 6 balance Right [%d %%]"         <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]

Number      Audio_Zones_Dax66Amp_10CH            "All zones source [%.0f]" 	            <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_11CH            "Zone 1 source [%.0f]" 	            <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_12CH            "Zone 2 source [%.0f]" 	            <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_13CH            "Zone 3 source [%.0f]"	                <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_14CH            "Zone 4 source [%.0f]"	                <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_15CH            "Zone 5 source [%.0f]"	                <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_16CH            "Zone 6 source [%.0f]"	                <control>		    (gDax66Source)

String      Audio_Zones_Dax66Amp_11LS            "Zone 1 keypad power [%s]"             <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_12LS            "Zone 2 keypad power [%s]"             <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_13LS            "Zone 3 keypad power [%s]"             <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_14LS            "Zone 4 keypad power [%s]"             <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_15LS            "Zone 5 keypad power [%s]"             <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_16LS            "Zone 6 keypad power [%s]"             <switch>		    (gDax66KeypadPower)


/*============================================= Slave 1 ==============================================*/


Switch      Audio_Zones_Dax66Amp_20PA            "All slave 1 zones PA [%s]"            <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_21PA            "Slave 1 Zone 1 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_22PA            "Slave 1 Zone 2 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_23PA            "Slave 1 Zone 3 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_24PA            "Slave 1 Zone 4 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_25PA            "Slave 1 Zone 5 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_26PA            "Slave 1 Zone 6 PA [%s]"               <switch>		    (gDax66PublicAddress, gDax66CommandReceiver)  [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_20PR            "All slave 1 zones power [%s]"         <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_21PR            "Slave 1 Zone 1 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_22PR            "Slave 1 Zone 2 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_23PR            "Slave 1 Zone 3 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_24PR            "Slave 1 Zone 4 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_25PR            "Slave 1 Zone 5 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_26PR            "Slave 1 Zone 6 power [%s]"            <switch>		    (gDax66PowerSwitch, gDax66CommandReceiver)     [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_20MU            "All slave 1 zones mute [%s]"   	    <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_21MU            "Slave 1 Zone 1 mute [%s]"   	        <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_22MU            "Slave 1 Zone 2 mute [%s]"    	        <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_23MU            "Slave 1 Zone 3 mute [%s]"    	        <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_24MU            "Slave 1 Zone 4 mute [%s]"    	        <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_25MU            "Slave 1 Zone 5 mute [%s]"     	    <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_26MU            "Slave 1 Zone 6 mute [%s]"    	        <mute> 		        (gDax66Mute, gDax66CommandReceiver)      [ "Switchable" ]

Switch      Audio_Zones_Dax66Amp_20DT            "All slave 1 zones do not disturb [%s]"  <mute> 		    (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_21DT            "Slave 1 Zone 1 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_22DT            "Slave 1 Zone 2 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_23DT            "Slave 1 Zone 3 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_24DT            "Slave 1 Zone 4 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_25DT            "Slave 1 Zone 5 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]
Switch      Audio_Zones_Dax66Amp_26DT            "Slave 1 Zone 6 do not disturb [%s]"   <mute> 		        (gDax66DoNotDisturb, gDax66CommandReceiver)  [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_20VO            "All slave 1 zones volume [%d %%]"     <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_21VO            "Slave 1 Zone 1 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_22VO            "Slave 1 Zone 2 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_23VO            "Slave 1 Zone 3 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_24VO            "Slave 1 Zone 4 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_25VO            "Slave 1 Zone 5 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_26VO            "Slave 1 Zone 6 volume [%d %%]"        <soundvolume>       (gDax66Volume, gDax66CommandReceiver)          [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_20TR            "All slave 1 zones treble [%d %%]"     <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_21TR            "Slave 1 Zone 1 treble [%d %%]"        <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_22TR            "Slave 1 Zone 2 treble [%d %%]"        <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_23TR            "Slave 1 Zone 3 treble [%d %%]"        <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_24TR            "Slave 1 Zone 4 treble [%d %%]"        <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_25TR            "Slave 1 Zone 5 treble [%d %%]"        <treble>            (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_26TR            "Slave 1 Zone 6 treble [%d %%]"        <treble>	        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_20BS            "All slave 1 zones bass [%d %%]"  	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_21BS            "Slave 1 Zone 1 bass [%d %%]"  	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_22BS            "Slave 1 Zone 2 bass [%d %%]"  	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_23BS            "Slave 1 Zone 3 bass [%d %%]"   	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_24BS            "Slave 1 Zone 4 bass [%d %%]"   	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_25BS            "Slave 1 Zone 5 bass [%d %%]"    	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_26BS            "Slave 1 Zone 6 bass [%d %%]"    	    <bass>		        (gDax66Tone, gDax66CommandReceiver)            [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_20BLL           "All slave 1 zones balance Left [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_21BLL           "Slave 1 Zone 1 balance Left [%d %%]"  <control>           (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_22BLL           "Slave 1 Zone 2 balance Left [%d %%]" 	<control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_23BLL           "Slave 1 Zone 3 balance Left [%d %%]" 	<control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_24BLL           "Slave 1 Zone 4 balance Left [%d %%]" 	<control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_25BLL           "Slave 1 Zone 5 balance Left [%d %%]" 	<control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_26BLL           "Slave 1 Zone 6 balance Left [%d %%]" 	<control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]

Dimmer      Audio_Zones_Dax66Amp_20BLR           "All slave 1 zones balance Right [%d %%]" <control>	    (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_21BLR           "Slave 1 Zone 1 balance Right [%d %%]" <control>           (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_22BLR           "Slave 1 Zone 2 balance Right [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_23BLR           "Slave 1 Zone 3 balance Right [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_24BLR           "Slave 1 Zone 4 balance Right [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_25BLR           "Slave 1 Zone 5 balance Right [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]
Dimmer      Audio_Zones_Dax66Amp_26BLR           "Slave 1 Zone 6 balance Right [%d %%]" <control>	        (gDax66Balance, gDax66CommandReceiver)         [ "Switchable" ]

Number      Audio_Zones_Dax66Amp_20CH            "All slave 1 zones source [%.0f]" 	    <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_21CH            "Slave 1 Zone 1 source [%.0f]" 	    <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_22CH            "Slave 1 Zone 2 source [%.0f]" 	    <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_23CH            "Slave 1 Zone 3 source [%.0f]"	        <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_24CH            "Slave 1 Zone 4 source [%.0f]"	        <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_25CH            "Slave 1 Zone 5 source [%.0f]"	        <control>		    (gDax66Source)
Number      Audio_Zones_Dax66Amp_26CH            "Slave 1 Zone 6 source [%.0f]"	        <control>		    (gDax66Source)

String      Audio_Zones_Dax66Amp_21LS            "Slave 1 Zone 1 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_22LS            "Slave 1 Zone 2 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_23LS            "Slave 1 Zone 3 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_24LS            "Slave 1 Zone 4 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_25LS            "Slave 1 Zone 5 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
String      Audio_Zones_Dax66Amp_26LS            "Slave 1 Zone 6 keypad power [%s]"     <switch>		    (gDax66KeypadPower)
2 Likes

…and here are the rules that didn’t fit in the previous message :wink:

import java.util.regex.Matcher
import java.util.regex.Pattern
import org.apache.commons.lang.StringUtils

var Timer aliveCheckTimeoutTimer = null
val java.util.Map<String, Timer> inputZoneTimers = newHashMap

val updateAmpValue = [ String zoneId, String actionCode, String value, Functions.Function4<String, String, String, Functions.Function1<String, Integer>, Integer> getAmpValueAsPercentage, Functions.Function1<String, Integer> getMaxAmpValue | 
    logDebug("dax66.rules", "func updateAmpValue: On zoneId " + zoneId + ", set ampValue(" + actionCode + ") = " + value)
    switch actionCode {
        case "PA": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, if(value == "01") "ON" else "OFF") }//Public Address
        case "PR": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, if(value == "01") "ON" else "OFF") }//Power
        case "MU": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, if(value == "01") "ON" else "OFF") }//Mute
        case "DT": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, if(value == "01") "ON" else "OFF") }//Do Not Disturb 
        case "VO": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, getAmpValueAsPercentage.apply(zoneId, actionCode, value, getMaxAmpValue).toString) }//Volume
        case "TR": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, getAmpValueAsPercentage.apply(zoneId, actionCode, value, getMaxAmpValue).toString) }//Treble
        case "BS": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, getAmpValueAsPercentage.apply(zoneId, actionCode, value, getMaxAmpValue).toString) }//Bass
        case "BL": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode + "R", getAmpValueAsPercentage.apply(zoneId, actionCode, value,getMaxAmpValue).toString) }//Balance
        case "CH": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, value.right(1)) }//Source
        case "LS": { postUpdate("Audio_Zones_Dax66Amp_" + zoneId + actionCode, if(value == "01") "ON" else "OFF") }//Keypad connected
    }
]

val getAmpValueAsPercentage = [ String zoneId, String actionCode, String value, Functions.Function1<String, Integer> getMaxAmpValue | 
    val int maxAmpValue = getMaxAmpValue.apply(actionCode)
    val result = Math::round((Double::parseDouble(value) / maxAmpValue) * 100).intValue
    result
]

val getMaxAmpValue = [ String actionCode | 
    var int maxAmpValue
    switch actionCode {
        case "VO": maxAmpValue = 38
        case "TR": maxAmpValue = 14
        case "BS": maxAmpValue = 14
        case "BL": maxAmpValue = 20
    }
    maxAmpValue
]
rule "Dax66 Sending RS232"
when
    Item Audio_Zones_Dax66Amp_Send received update
then
    var String sendData = Audio_Zones_Dax66Amp_Send.state.toString
    Audio_Zones_Dax66Amp_RS232_Buffer.sendCommand(sendData)

    //This code will take care of race-conditions between frequent user input and zone update requests.
    //The idea is to wait until no user input has been done during last second in a zone before requesting an update from the amp.
    if(sendData.startsWith("<")) {
        val String zoneId = sendData.substring(1, 3)
        if(inputZoneTimers.containsKey(zoneId) && inputZoneTimers.get(zoneId) !== null) {
            inputZoneTimers.get(zoneId).reschedule(now.plusMillis(1000))
        } else {
            inputZoneTimers.put(zoneId, createTimer(now.plusMillis(1000)) [|
                Audio_Zones_Dax66Amp_Send.sendCommand("?" + zoneId + "\r")//Request an update for current zone or amp unit, just to be sure everything is up to date.
                inputZoneTimers.get(zoneId).cancel
                inputZoneTimers.remove(zoneId)
            ])
        }
    }
end

rule "Dax66 Check for received data from RS232 send/receive"
when
    Item Audio_Zones_Dax66Amp_RS232_Buffer received update
then
    //All data (send AND receive) is concatenated.
    //Each data block is terminated with at least one '\r' then a '\n' and then finally a '#' char.
    //Note: Double CR chars, '\r\r\n#' probably means the end of a RECEIVED block.
    var String rawBuffer = Audio_Zones_Dax66Amp_RS232_Buffer.state.toString
    logDebug("dax66.rules", "------>   RS232 Send/Receive: Audio_Zones_Dax66Amp_RS232_Buffer = [" + rawBuffer.replace('\r','\\r').replace('\n','\\n') + "]")
    if(rawBuffer.equals("\r\n#")) {//Master unit is powered on...
        Audio_Zones_Dax66Amp_Refresh.sendCommand(ON)//...start alive check
        return
    }
    var String[] receivedDataBlocks = StringUtils.substringsBetween(rawBuffer, ">", "#")
    if(receivedDataBlocks !== null) {
        for(String receivedData: receivedDataBlocks) {
            Audio_Zones_Dax66Amp_Receive.postUpdate(receivedData.remove("\r").remove("\n"))
            //Need this sleep here or else doubles of same data is posted to Audio_Zones_Dax66Amp_Receive
            Thread::sleep(50)
        }
    }
end

rule "Dax66 Process received data"
when
    Item Audio_Zones_Dax66Amp_Receive received update
then
    val String receivedData = Audio_Zones_Dax66Amp_Receive.state.toString
    if(receivedData !== null) {
        logInfo("dax66.rules", "Process received data: Data = [" + receivedData + "]")

        //The two rows below updates unit alive awareness for the unit that sent the received data.
        val targetSyncItem = gDax66AliveCheck.members.findFirst[ name.equals("Audio_Zones_Dax66Amp_Alive_Check_" + receivedData.left(1) + "0")]
        if(targetSyncItem.state == OFF) { targetSyncItem.sendCommand(ON) }

        var Pattern pattern = null 
        var Matcher matcher = null
        val bigResponseValueCount = 11
        if(receivedData.length == (bigResponseValueCount * 2)) {
            pattern = Pattern::compile("(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)")
            matcher = pattern.matcher(receivedData)
            if(matcher.find()) {
                for(var int i = 2; i <= bigResponseValueCount; i=i+1)
                {
                    var String actionCode = "N/A"
                    switch i {
                        case 2: { actionCode = "PA" }//Public Address
                        case 3: { actionCode = "PR" }//Power
                        case 4: { actionCode = "MU" }//Mute
                        case 5: { actionCode = "DT" }//Do Not Disturb 
                        case 6: { actionCode = "VO" }//Volume
                        case 7: { actionCode = "TR" }//Treble
                        case 8: { actionCode = "BS" }//Bass
                        case 9: { actionCode = "BL" }//Balance
                        case 10: { actionCode = "CH" }//Source
                        case 11: { actionCode = "LS" }//Keypad connected
                    }
                    logDebug("dax66.rules", "Process received data: zoneId " + matcher.group(1) + ", ampValue(" + actionCode + ") = " + matcher.group(i))
                    updateAmpValue.apply(matcher.group(1), actionCode, matcher.group(i), getAmpValueAsPercentage, getMaxAmpValue)
                }
            } else {
                logError("dax66.rules", "Process received data: matcher.find() = FAIL")
            }
        } else if(receivedData.length == 6) {
            pattern = Pattern::compile("(\\d\\d)(\\s\\s)(\\d\\d)")
            matcher = pattern.matcher(receivedData)
            if(matcher.find()) {
                logDebug("dax66.rules", "Process received data: zoneId " + matcher.group(1) + ", ampValue(" + matcher.group(2) + ") = " + matcher.group(3))
                updateAmpValue.apply(matcher.group(1), matcher.group(2), matcher.group(3), getAmpValueAsPercentage, getMaxAmpValue)
            }
        } else {
            logError("dax66.rules", "Process received data: FAILED! receivedData.length = " + receivedData.length)
        }
    }
end

rule "Refresh values from Dax66 at alive confirm"
when
    Member of gDax66AliveCheck received command
then
    if(receivedCommand == ON) {
        val aliveUnitId = triggeringItem.name.right(2)
        Audio_Zones_Dax66Amp_Send.sendCommand("?" + aliveUnitId + "\r")
    }
end

//Can't trust openHAB persistence because Dax66 values may have changed during openHAB offline time.
rule "Check if Dax66 is alive at openHAB startup"
when
    Item Audio_Zones_Dax66Amp_Refresh received command or
    System started
then 
    //Reset alive check status
    Audio_Zones_Dax66Amp_Alive_Check_10.postUpdate(OFF)
    Audio_Zones_Dax66Amp_Alive_Check_20.postUpdate(OFF)
    Audio_Zones_Dax66Amp_Alive_Check_30.postUpdate(OFF)

    //Send refresh requests and wait 5 seconds for everything to process
    aliveCheckTimeoutTimer = createTimer(now.plusSeconds(5)) [|
        aliveCheckTimeoutTimer.cancel
        aliveCheckTimeoutTimer = null
        logInfo("dax66.rules", "Alive check: Master = " + Audio_Zones_Dax66Amp_Alive_Check_10.state + "   Slave 1 = " + Audio_Zones_Dax66Amp_Alive_Check_20.state + "   Slave 2 = " + Audio_Zones_Dax66Amp_Alive_Check_30.state)
    ]

    for(var int i = 1; i <= 3; i++) {
        Thread::sleep(1000)
        Audio_Zones_Dax66Amp_Send.sendCommand("?" + i + "1PR\r")
    }
end

rule "Dax66 command handler"
when
    Member of gDax66CommandReceiver received command
then
    if(triggeringItem.state == NULL) { return }
    val String target = triggeringItem.name.substringAfterLast('_')
    val String zoneId = target.substring(0, 2)
    val String actionCode = target.substring(2, 4)
    logDebug("Dax66", "Command handler: triggeringItem.getType = " + triggeringItem.getType + "   zoneId = " + zoneId + "   actionCode = " + actionCode)

    switch triggeringItem.getType {
        case "Switch":  Audio_Zones_Dax66Amp_Send.sendCommand("<" + zoneId + actionCode + if(receivedCommand == ON) "01\r" else "00\r")
        case "Dimmer":  {
            var int maxAmpValue = getMaxAmpValue.apply(actionCode)
            var Number currentValue
            var Number newValue
            logDebug("Dax66", "Dimmer item: triggeringItem.state.getClass = " + triggeringItem.state.getClass.toString)
            if(receivedCommand instanceof PercentType) {           
                currentValue = receivedCommand
                newValue = Math::round(currentValue.doubleValue * (maxAmpValue.doubleValue / 100))             
                logDebug("Dax66", "Dimmer item (PercentType): newValue = " + currentValue + " -> " + newValue)
            } else if(receivedCommand instanceof IncreaseDecreaseType) {
                currentValue = Math::round(Double::parseDouble(triggeringItem.state.toString) * (maxAmpValue.doubleValue / 100))
                if(receivedCommand==INCREASE && currentValue < maxAmpValue) {
                    newValue = currentValue + 1
                    logDebug("Dax66", "Dimmer item (IncreaseDecreaseType/INCREASE): newValue = " + currentValue + " -> " + newValue)
                } else if(receivedCommand==DECREASE && currentValue > 0) {
                    newValue = currentValue - 1
                    logDebug("Dax66", "Dimmer item (IncreaseDecreaseType/DECREASE): newValue = " + currentValue + " -> " + newValue)
                } else { return }
            } else { 
                logWarn("Dax66", "Dimmer item: Unsupported receivedCommand type = " + receivedCommand.getClass.toString)
                triggeringItem.postUpdate(Math::round(((maxAmpValue.doubleValue / 2) / maxAmpValue) * 100) as Number) 
            }

            logDebug("Dax66", "Dimmer item: " + Math::round((currentValue.doubleValue / maxAmpValue) * 100) + "% -> " + Math::round((newValue.doubleValue / maxAmpValue) * 100) + "%")
            triggeringItem.postUpdate(Math::round((newValue.doubleValue / maxAmpValue) * 100) as Number)

            if(actionCode.equals("BL") && target.substring(4, 5).equals("L")) {
                newValue = 20 - newValue// Do some reverse calculation if LEFT balance is the triggering item. 
            }
            Audio_Zones_Dax66Amp_Send.sendCommand("<" + zoneId + actionCode + (if (newValue < 10) "0" else "") + newValue.toString + "\r")
        }
    }
end

rule "Dax66 sync left and right balance dimmers"
when
    Member of gDax66Balance received update
then
    val String targetSyncSide = if(triggeringItem.name.endsWith("L")) "R" else "L"
    val targetSyncItem = gDax66Balance.members.findFirst[ name.equals(triggeringItem.name.left(triggeringItem.name.length - 1) + targetSyncSide)]
    if(targetSyncItem.state == NULL || Integer::parseInt(triggeringItem.state.toString) + Integer::parseInt(targetSyncItem.state.toString) != 100)
        targetSyncItem.postUpdate(100 - Integer::parseInt(triggeringItem.state.toString))
    logDebug("Dax66", "Balance dimmers: " + triggeringItem.name + " = " + triggeringItem.state + "  ==>  " + targetSyncItem.name + " = " + (100 - Integer::parseInt(triggeringItem.state.toString)))
end

rule "Dax66 Zone Source"
when
        Member of gDax66Source received command
then
        val zoneIdAndActionCode = triggeringItem.name.right(4)
        Audio_Zones_Dax66Amp_Send.sendCommand("<" + zoneIdAndActionCode + "0" + receivedCommand.toString + "\r")
end

3 Likes

Hey. I am hoping you guys might be able to give me some guidance. I am new to openHAB, coming from Home Assistant.

I am having issues with my Monoprice Amp. I am still learning how to configure everything but I got my Ecobee items, sitemap, and rules working correctly.

I am using a usb to serial device that previously working with Home Assistant. I know the OS see’s the device but not sure how I know if openHAB is seeing it correctly.
“Bus 001 Device 004: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port”

I copied the items, rules, and sitemap from the 1st post here. The rules isn’t working for me but the other load just fine. Maybe it is seeing it and my only problem is I need to fix my rules?

“2019-01-09 18:52:27.435 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model ‘monoprice.items’”
“2019-01-09 18:52:46.845 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model ‘monoprice.sitemap’”

then I get errors with he rules.
“Error during the execution of startup rule ‘MonoPrice Status on command change’: No match found”
“Rule ‘MonoPrice Status on command change’: No match found” and so on.

Thanks.

What you’re seeing is just poor error-handling on my part :slight_smile: - not an actual error with your rules. Basically, when you created all the items, the String item that contains the message going to/coming from the amp is empty, and the rule that parses it errors out on the Regex pattern match. I haven’t had a chance to try @tony_alpskog 's rewrite above yet, but his rules do a better job of error-checking.

Do any of the switches on your sitemap work to control the amp zones? If not, you’ll need to make sure that the String AudioMTX is actually being populated with your commands, and it’s getting responses from the amp (basically, troubleshoot the serial connection to the amp).

Thanks for the reply!

No, so far I haven’t gotten the switches to do anything with the sitemap. I wasn’t sure if it was because the error in the rules (Like I said, I’m new and still learning everything.)

I will take a deeper look. thanks!

I’ve adapted your streamlined version of the rule to use my Globalcache iTach Flex (serial over network) adapter.

I must say it was a bit of a learning curve for me since OH and coding is a hobby for me. I learned a lot about how your (and any) lambda works, although it’s still a little mysterious to me.

Have made any updates to what you posted?

No updates. The reason for the improvement was that I had bought one unit on discount and I wanted to know the limits of the interface before buying a second one. So I just made the whole thing on a “good enough level” to fulfill my requirements. Got the units but they are not installed yet. I will probably continue the work when I’ve got the whole thing installed. No date so far for that :). The test sitemap is a quite crappy hack but I could add it if someone wants it.