Broadlink binding for RMx, A1, SPx and MP. Any interest?

Hi everyone,

I was wondering if there is a way not to use the map-file with this binding. I am creating the IR codes for my Daikin air conditioner at runtime using a rule, so I do not want to create a key for each configuration.

rule "AC control"
when Member of AC received command
then 
    val low_str  = '0e0e'
    val high_str = '0e2c'

    val prefix = "260050020e0e0e0e0e0e0e0e0e0e0d000341713a0e2c0e0e0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e2c0e0e0e2c0e2c0e0e0e2c0e2c0e2c0e2c0e2c0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e2c0e0e0e2c0e0e0e0e0e0e0e2c0e2c0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e2c0e2c0e2c0e0e0e2c0e0e0e2c0e2c0d000474713a0e2c0e0e0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e2c0e0e0e2c0e2c0e0e0e2c0e2c0e2c0e2c0e2c0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e2c0e0e0e2c0e0e0e2c0e0e0d000475713a"    
    val header = "0e2c0e0e0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e2c0e0e0e2c0e2c0e0e0e2c0e2c0e2c0e2c0e2c0e0e0e0e0e2c0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e"
    val suffix = "0d000d050000000000000000"

    var power = AC_power.state()
    val mode = AC_mode.state()
    val temp_set = AC_set_temp.state()
    val wind_level = AC_windlevel.state()   
    val wind_swing = AC_windswing.state()
    val powermode = AC_powermode.state()
    val economy = AC_economy.state()

    logInfo("broadlink.rules", "State: power "+power+", mode "+mode+", temp "+temp_set+", wind_level "+wind_level + ", wind_swing " + wind_swing + ", powermode " + powermode + ", economy " + economy)
    
    //Integer::parseInt(str,16).intValue()

    // Convert to int for checksum: 11 + +da + 27 + 00 + 00
    // header
    val header_int = Integer.parseInt("11", 16) + Integer.parseInt("da", 16) + Integer.parseInt("27", 16)
    
    
    var mode_byte_hex = mode.toString
    if (mode == NULL){
        mode_byte_hex = "0"
        postUpdate(AC_mode,0)
    } 
    if (power == ON){
        mode_byte_hex += "9"
    } else {
        mode_byte_hex += "8"
        postUpdate(AC_power,OFF)
        power = OFF
    }
    if (power == OFF){
        postUpdate(AC_mode_google,"off")
    } else if (mode_byte_hex.substring(0,1) == "0"){
        postUpdate(AC_mode_google,"heatcool")
    } else if (mode_byte_hex.substring(0,1) == "3"){
        postUpdate(AC_mode_google,"cool")
    } else if (mode_byte_hex.substring(0,1) == "4"){
        postUpdate(AC_mode_google,"heat")
    } else {
        postUpdate(AC_mode_google,"on")
    }

    val mode_byte_int = Integer.parseInt(mode_byte_hex, 16)
    var mode_byte_bin = Integer::toBinaryString(mode_byte_int)
    var mode_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < mode_byte_bin.length){
            mode_byte_cor += mode_byte_bin.charAt(mode_byte_bin.length-i-1)
        } else {
            mode_byte_cor += "0"
        } 
    }
    mode_byte_bin = String::format("%1$8s", mode_byte_bin)
    logInfo("broadlink.rules", "mode_byte_hex "+mode_byte_hex)
    logInfo("broadlink.rules", "mode_byte_cor "+mode_byte_cor)

       
    val temp_byte_int = 40
    if ((temp_set != null) && (temp_set != NULL)){
        temp_byte_int = 2*Integer.parseInt(temp_set.toString)
    } else {
        postUpdate(AC_set_temp,20)
    }
    val temp_byte_hex = Integer::toHexString(temp_byte_int)
    val temp_byte_bin = Integer::toBinaryString(temp_byte_int)
    var temp_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < temp_byte_bin.length){
            temp_byte_cor += temp_byte_bin.charAt(temp_byte_bin.length-i-1)
        } else {
            temp_byte_cor += "0"
        } 
    }
    logInfo("broadlink.rules", "temp_byte_hex "+temp_byte_hex)
    logInfo("broadlink.rules", "temp_byte_cor "+temp_byte_cor)


    val fixed_byte_hex = "00"
    val fixed_byte_int = Integer.parseInt(fixed_byte_hex, 16)
    val fixed_byte_bin = Integer::toBinaryString(fixed_byte_int)
    var fixed_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < fixed_byte_bin.length){
            fixed_byte_cor += fixed_byte_bin.charAt(fixed_byte_bin.length-i-1)
        } else {
            fixed_byte_cor += "0"
        } 
    }

    var wind_byte_hex = "a"
    if (wind_level == 0){
        wind_byte_hex = "a"
    } else if (wind_level == 1){
        wind_byte_hex = "b"
    } else if ((wind_level != null) && (wind_level != NULL)){
        wind_byte_hex = wind_level.toString
    } else {
        postUpdate(AC_windlevel,0)
    }
    if (wind_swing == ON){
        wind_byte_hex += "f"
    } else {
        wind_byte_hex += "0"
    }
    val wind_byte_int = Integer.parseInt(wind_byte_hex, 16)
    val wind_byte_bin = Integer::toBinaryString(wind_byte_int)
    var wind_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < wind_byte_bin.length){
            wind_byte_cor += wind_byte_bin.charAt(wind_byte_bin.length-i-1)
        } else {
            wind_byte_cor += "0"
        } 
    }
    logInfo("broadlink.rules", "wind_byte_hex "+wind_byte_hex)
    logInfo("broadlink.rules", "wind_byte_cor "+wind_byte_cor)
    
    val timer_a_byte_hex = "00"
    val timer_b_byte_hex = "06"
    val timer_c_byte_hex = "60"
    val timer_a_byte_int = Integer.parseInt(timer_a_byte_hex, 16)
    val timer_b_byte_int = Integer.parseInt(timer_b_byte_hex, 16)
    val timer_c_byte_int = Integer.parseInt(timer_c_byte_hex, 16)
    val timer_a_byte_bin = Integer::toBinaryString(timer_a_byte_int)
    val timer_b_byte_bin = Integer::toBinaryString(timer_b_byte_int)
    val timer_c_byte_bin = Integer::toBinaryString(timer_c_byte_int)
    var timer_a_byte_cor = ""
    var timer_b_byte_cor = ""
    var timer_c_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < timer_a_byte_bin.length){
            timer_a_byte_cor += timer_a_byte_bin.charAt(timer_a_byte_bin.length-i-1)
        } else {
            timer_a_byte_cor += "0"
        }
        if (i < timer_b_byte_bin.length){
            timer_b_byte_cor += timer_b_byte_bin.charAt(timer_b_byte_bin.length-i-1)
        } else {
            timer_b_byte_cor += "0"
        } 
        if (i < timer_c_byte_bin.length){
            timer_c_byte_cor += timer_c_byte_bin.charAt(timer_c_byte_bin.length-i-1)
        } else {
            timer_c_byte_cor += "0"
        }
    }
    logInfo("broadlink.rules", "timer_byte_hex "+timer_a_byte_hex + " " +timer_b_byte_hex + " " + timer_c_byte_hex)
    logInfo("broadlink.rules", "timer_byte_cor "+timer_a_byte_cor + " " +timer_b_byte_cor + " " + timer_c_byte_cor)

    
    var powermode_byte_hex = "00"
    if (powermode == ON){
        powermode_byte_hex = "01"
    }
    val powermode_byte_int = Integer.parseInt(powermode_byte_hex, 16)
    val powermode_byte_bin = Integer::toBinaryString(powermode_byte_int)
    var powermode_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < powermode_byte_bin.length){
            powermode_byte_cor += powermode_byte_bin.charAt(powermode_byte_bin.length-i-1)
        } else {
            powermode_byte_cor += "0"
        } 
    }
    logInfo("broadlink.rules", "powermode_byte_hex "+powermode_byte_hex)
    logInfo("broadlink.rules", "powermode_byte_cor "+powermode_byte_cor)


    val special_byte_hex = "c1"
    val special_byte_int = Integer.parseInt(special_byte_hex, 16)
    val special_byte_bin = Integer::toBinaryString(special_byte_int)
    var special_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < special_byte_bin.length){
            special_byte_cor += special_byte_bin.charAt(special_byte_bin.length-i-1)
        } else {
            special_byte_cor += "0"
        } 
    }

    var economy_byte_hex = "c1"//80
    if (economy == ON){
        economy_byte_hex = "c1"//84
    }
    val economy_byte_int = Integer.parseInt(economy_byte_hex, 16)
    val economy_byte_bin = Integer::toBinaryString(economy_byte_int)
    var economy_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < economy_byte_bin.length){
            economy_byte_cor += economy_byte_bin.charAt(economy_byte_bin.length-i-1)
        } else {
            economy_byte_cor += "0"
        } 
    }
    logInfo("broadlink.rules", "economy_byte_hex "+economy_byte_hex)
    logInfo("broadlink.rules", "economy_byte_cor "+economy_byte_cor)

    val check_byte_int_full = header_int + mode_byte_int+temp_byte_int+wind_byte_int+timer_a_byte_int+timer_b_byte_int+timer_c_byte_int+powermode_byte_int+economy_byte_int
    val check_byte_hex_full = Integer::toHexString(check_byte_int_full)
    val check_byte_hex = check_byte_hex_full.substring(check_byte_hex_full.length-2,check_byte_hex_full.length)
    val check_byte_int = Integer.parseInt(check_byte_hex, 16)
    val check_byte_bin = Integer::toBinaryString(check_byte_int)
    var check_byte_cor = ""
    for (var i = 0; i<8; i++){
        if (i < check_byte_bin.length){
            check_byte_cor += check_byte_bin.charAt(check_byte_bin.length-i-1)
        } else {
            check_byte_cor += "0"
        } 
    }
    logInfo("broadlink.rules", "check_byte_hex_full "+check_byte_hex_full)
    logInfo("broadlink.rules", "check_byte_hex "+check_byte_hex)
    logInfo("broadlink.rules", "check_byte_cor "+check_byte_cor)
    
    val command_bin = mode_byte_cor + temp_byte_cor + fixed_byte_cor + wind_byte_cor + fixed_byte_cor + timer_a_byte_cor + timer_b_byte_cor + timer_c_byte_cor + powermode_byte_cor + fixed_byte_cor + economy_byte_cor + fixed_byte_cor + fixed_byte_cor + check_byte_cor
    logInfo("broadlink.rules", "command_bin "+command_bin)
    logInfo("broadlink.rules", "command_hex "+mode_byte_hex + temp_byte_hex + fixed_byte_hex + wind_byte_hex + fixed_byte_hex + timer_a_byte_hex + timer_b_byte_hex + timer_c_byte_hex + powermode_byte_hex + fixed_byte_hex + economy_byte_hex + fixed_byte_hex + special_byte_hex + check_byte_hex)
    
    val zero = '0'
    var control = ""
    for (var i = 0; i<command_bin.length; i++){
        if (command_bin.substring(i,i+1).equals(zero)){
            control += low_str
        } else {
            control += high_str
        }
    }
    
    val String commandStr = prefix+header+control+suffix
    logInfo("broadlink.rules", "Command send: "+commandStr)
    postUpdate(BroadlinkRM3_Command,commandStr)
    var String result = executeCommandLine("python /etc/openhab2/scripts/broadlink_cli --type 10178 --host 192.168.1.6 --mac 780f775191f6 --send " +commandStr,10000)
    logInfo("broadlink.rules", "python: "+result)

end

if there is a better solution than using a rule, that is welcome too.
Thanks

Edit:
For now I will use “executeCommandLine” as a work around

postUpdate(BroadlinkRM3_Command,commandStr)
var String result = executeCommandLine("python /etc/openhab2/scripts/broadlink_cli --type 10178 --host <ip-address> --mac <mac-address> --send " +commandStr,10000)

using the broadlink github repository by mjg59

Edit (2019-03-24):
I updated the posted rule to the one, which I am using now.

Furthermore the items file including google home integration:

Group AC "Air conditioner"
Group g_AC "Klimaanlage" [ "Thermostat", "Celsius" ]
  Switch AC_power "Klimaanlage" (AC)
  String AC_mode_google "Modus" (g_AC) [ "homekit:HeatingCoolingMode" ]
  String AC_mode "Modus" (AC)
  Number AC_cur_temp "Temperatur" (AC, g_AC) [ "CurrentTemperature" ] 
  Number AC_set_temp "Zieltemperatur" (AC, g_AC) [ "TargetTemperature" ]
  Number AC_windlevel "Wind" (AC)
  Switch AC_windswing "Windrichtung" (AC)
  Switch AC_powermode "Power modus" (AC)
  Switch AC_economy "Economy" (AC)

and the relevant part of my sitemap:

    Text label="Air Conditioner" icon=aircon
    {
        Switch item=AC_power label="Power" icon="heating"
        Setpoint item=AC_set_temp minValue=18 maxValue=32 step=1 icon="temperature" label="Setpoint [%.0f °C]"
        Switch item=AC_mode label="Mode" icon="sofa" mappings=[0="Auto", 2="Dry", 3="Cool", 4="Heat", 6="Fan"]
        Switch item=AC_windlevel label="Wind level" icon="fan" mappings=[0="Auto", 1= "Calm", 3="▂", 4="▃", 5="▄", 6="▆", 7="▇"]
        Switch item=AC_windswing label="Wind swing" icon="aircon_swing"
        Switch item=AC_powermode label="Power mode" icon="wind"
        Switch item=AC_economy label="Economy" icon="econmode"
    }

and a separate rule to change the mode key which is received from google home to the one used by the airconditioner:

rule "AC Google mode"
when Item AC_mode_google received command
then 
    var mode = AC_mode_google.state()
    if (mode == NULL){
        sendCommand(AC_mode, "0")
    } else if (mode == "heat"){
        postUpdate(AC_power, ON)
        sendCommand(AC_mode, "4")
    } else if (mode == "cool"){
        postUpdate(AC_power, ON)
        sendCommand(AC_mode, "3")
    } else if (mode == "heatcool"){
        postUpdate(AC_power, ON)
        sendCommand(AC_mode, "0")
    } else if (mode == "on"){
        sendCommand(AC_power, ON)
    } else if (mode == "off"){        
        sendCommand(AC_power, OFF)
    }
end

Maybe this helps someone else.

4 Likes