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

if any non Android users out there are struggling to get the mapping codes, you can get them from your apple backup

  1. Download e-control app and map your remotes and buttons. Take note of the order you do this in
  2. Backup your iPhone
  3. Use the free iBackup Viewer application and open the backup
  4. Click Applications
  5. Click e-control.app
  6. Highlight the Documents/BroadlinkDeviceList.sqlite file
  7. Click export
  8. Click Selected files
  9. Save the file
  10. Open the sqlite file in the free app DBBrowser for SQLite
  11. Open the sqlite file
  12. Select the browse data tab
  13. Select the broadlink_code table
  14. Here are listed all buttons. Refer to your list you made -or you can find all the remotes in the table named broadlink_subir (subirID), and all the buttons in broadlink_button (buttonID). Click on the BLOB field on the button row you want to copy the hex for
  15. In the right window, change the mode from text to binary
  16. Copy the values and paste into your broadlink MAP
  17. Remove the carriage returns to make one string
  18. Repeat

Its a bit of mucking around but the Windows app was struggling with a couple of the second time learns where the e-control app was ok

Hope this helps someone

1 Like

can you post your items im trying to work out how to manage state

Sorry, I forgot it. I edited my previous post!

Hi @daJoe,
Sorry for the slow reply, I was on holiday and forgot about your post!
Thanks for the report - it looks like my log message scheme worked and I now have the details to support this new variant of the RM Mini 3. Sounds like the Python Broadlink library had the same issue.

I’ll publish a new version this weekend to ensure people trying out this binding with very new RM Mini 3’s will have a good experience.

Cheers

could this binding be updated to 2.5.0 (I have no idea if this is possible or difficult or otherwise)

No problem, I hope you had enjoyable holidays. Thanks a lot!!
I will let you know, whether the new binding discovers my RM mini 3 automatically.

It seems to work with the latest 2.5.0 snapshot.

Ok I’m not sure if it’s 2.5 related but I’ve woken up to my rm2 pro sending a command to a ceiling fan every 1 to 2 seconds
This is followed by out of memory errors and eventually unresponsive OH.
I am at the stage of removing the jar and seeing if my OH returns to stable then I will explore rules etc

Anyone else experience this?

never had that issue… using the RM2 for 3 years i think
sounds like some kind of rule maybe ?

i am using node red for the complex RM2 commands…
i am not great with programming if i would done it with rules, i would end up with something scary
in node red you just need to send a string to the RM2(String Item)
so i am sending things like POWER_TV(from the Maping file)

this is a small example where i get rid of Multi press by the user
and will send the command twice to AMP, so it will never miss
its working great, and I am able to send MQTT to IR using this simple “sketch”
you got to love the simplicity of it :slight_smile:

I’m only using the A1 sensor, so no hub, work OK though.

Any way to use this with homekit ? I can send command on Basic UI but not on iOS

Ok circling back this was a mis configured rule that had multiple when conditions that within the rule retriggered the same rule causing a circular rule and quickly running out of memory. Binding is working great

Hello,

I can not access the tutorial you created, and it helped me to set up my Broadlink RM3 mini.
Could you tell me if you have posted it in another url?

Thanks

do you have Homekit configured in OH?

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

Hi! Thanks for the binding.
I’m not sure if I have the same problems as the others do, or it is some kind of network problem. So my RM3 mini (fw: v55) sometimes goes offline. When it is offline I cannot even ping it. After approxiately half a minute it comes back. For me it seems that it goes offline more frequently if I’m sending out IR signals (through this binding), but sometime it goes offline even when in idle. Do you think it is related the the binding or it is some kind of network issue?

My log is:
==> /var/log/openhab2/openhab.log <==

2019-03-30 10:58:02.677 [ERROR] [dlink.handler.BroadlinkRemoteHandler] - rm3:c8-f7-42-17-2a-89[^]: updateItemStatus: Online -> Offline

==> /var/log/openhab2/events.log <==

2019-03-30 10:58:02.702 [me.event.ThingUpdatedEvent] - Thing ‘broadlink:rm3:c8-f7-42-17-2a-89’ has been updated.

2019-03-30 10:58:02.747 [me.event.ThingUpdatedEvent] - Thing ‘broadlink:rm3:c8-f7-42-17-2a-89’ has been updated.

2019-03-30 10:58:02.752 [hingStatusInfoChangedEvent] - ‘broadlink:rm3:c8-f7-42-17-2a-89’ changed from ONLINE to OFFLINE (COMMUNICATION_ERROR): Could not find device at IP address 192.168.0.107

2019-03-30 10:58:32.788 [me.event.ThingUpdatedEvent] - Thing ‘broadlink:rm3:c8-f7-42-17-2a-89’ has been updated.

2019-03-30 10:58:32.827 [me.event.ThingUpdatedEvent] - Thing ‘broadlink:rm3:c8-f7-42-17-2a-89’ has been updated.

2019-03-30 10:58:32.855 [hingStatusInfoChangedEvent] - ‘broadlink:rm3:c8-f7-42-17-2a-89’ changed from OFFLINE (COMMUNICATION_ERROR): Could not find device at IP address 192.168.0.107 to ONLINE

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.

Thanks !

Yes, I can control my leds, but not broadlink.

i am using node red for rules but you can setup something like this a rule an item controlled by siri lets say TV on

Switch TVON “TV” (groupSiri) [ “Switchable” ]

than create a rule that will triger the IR command for the broadlink with that value (TVon = 35456343…)
and set it back to 0 again , if you are using node red i can give you a working example
dont know much about OH rules

1 Like

I am noob in node red. But I will try. Any tips where i start ?