Binding for Sensibo Sky available

Hi.
I’m new to home automation and after being burned by HASSIO, I made the switch to openHAB only to notice that there’s no integration for my A/C controller. Well since today there’s an early version of my binding available for testing (pull request can be found on github). It is still work in progress and the features are limited, but I was able to read and set the state of my A/C successfully. I hope enough people will test it, so all errors and mistakes can be found.

Release Information

Since the Build server for pull requests currently has problems and for archival purposes, from now on all releases that I will put to the public will appear at

I will also try to keep the download through the eclipse marketplace in sync with the releases on my github page and as soon as the merge is approved I will update the link there to the jfrog repo.

The marketplace entry can be found here

Further development will still be done in the fork and every time I reach a milestone, I will also add those changes to this repository and create a new release from it.

The current passing build from the fork will still be available for download here:

https://openhab.jfrog.io/openhab/libs-pullrequest-local/org/openhab/binding/org.openhab.binding.sensibosky/

2 Likes

This is great, Could you post a link to a compiled JAR files, please.
Most users do not know how to build a jar from a repo.

This way more people will be able to download, install and test.
You could also add you binding to the market place in the meantime, while the code is being reviewed for inclusion.

I will post a link as soon as I can get it to build. The current build skipped my binding and I don’t know why.

Fantastic news, thank you :slight_smile:

I will test for sure

Andrea

I guess it will take a bit longer until I can post a link. The building system is giving me a headache.

Thank you so much for the binding. I just tried it and found out that I could not fill in the DeviceID in the configuration page (I have multiple Sensible devices tiring to 1 API key). Currently, the DeviceID parameter seems to accept only integer but the actual DeviceID is text. It would be great if you could have a quick fix for this. Just can’t wait to test the binding!! :slight_smile: Thanks again.

Whoops. I changed it in the code but forgot to change it in the thing definition. A new build is on the way. Let’s hope in goes through without any problems unlike yesterday. If it fails (which I’ll know in 2 hours), I’ll make a local build and upload it to google drive or something.

UPDATE: The new build should contain the fix. The URL didn’t change

Wow. What a quick fix! The binding is now configured and so far most functions are working just fine for my multiple Sensibo devices. :smiley:

Just one little issue on ‘Swing Mode’ channel. Currently it is ‘Switch’ type (ON/OFF), but I believe it should be ‘String’ type (ie. stop, top, middle, bottom).

I just came here to post that issue. Both mine are connected

Okay I will change that later in the evening. My A/C only supports rangeFull and stopped so I assumed it could be binary.
Can you tell me which swing modes your A/C supports?
I will try to find a way later on to load the capabilities of the A/C from the Sensibo API.

Or even better. I’ll try to make it to a bridge setup, so that you first need to create a SensiboSkyBridge with the API Key and then separate A/C controller things which get their capabilities from the Sensibo API through the bridge. Provided that a bridge works how I think it does. But it will take at least a few days to get it running.

I have some info that may help I still learning like all of us. Bridge is the way forward I think

https://home.sensibo.com/api/v2/pods/{device_id}?fields=acState&apiKey={api_key}

returns

{"status": "success", "result": {"acState": {"on": false, "fanLevel": "medium", "temperatureUnit": "C", "targetTemperature": 24, "mode": "cool", "swing": "rangeFull"}}}

Can’t check the swing modes as everyone is asleep

My A/C supports the following swing modes:

  • stopped
  • fixedTop
  • fixedMiddleTop
  • fixedMiddle
  • fixedMiddleBottom
  • fixedBottom
  • rangeFull

I will add those options later so everyone can use their A/C to tue fullest and then start a rework with the bridge solution. Also the binding is now available on the eclipse marketplace

I also notified Sensibo about the development so they can maybe update their smart home integration website.

UPDATE: I changed the swing mode from switch to string with the options provided. Note that some A/C may not be compatible with all of them (your A/C won’t react to the change then, I checked it). The fix should be available under the already provided URL after the build finished (around 1,5 to 2 hours).
I’ll be concentrating on redesigning the binding to use a bridge to determine the available options, but I won’t be able to do that before the weekend and it may take some time, so please be patient.

Thanks for the binding I can now control my AC.

How do I read the status of and initialize the different channels?
Current_temperature and Humidity works but the other channels are not initialized unless I change them from PaperUI or BasicUI.

If I use https://home.sensibo.com/api/v2/pods/{device_id}?fields=acState&apiKey={api_key} status is returned correct.

I use OpenHABian on a Pi and when I start OpenHAB I have the following in my log:

Yes currently only the temperature and humidity is being synchronized. I’ll add the rest as soon as I can solve my current problem. I’ll check the exception then too.

The binding is awesome work however I not using currently as wife uses old remote and I use the google home sometimes.

I have 2 Sensibo Sky units working currently in my rules. These update all states to openhab periodically. So you can send Sensibo the command that the state the aircon is in. Sensibo ignores this so to me that’s ok.

Rules

/*** openHAB Sensibo Rules ***/

// Goto the website https://home.sensibo.com/me/api first and create APIKey

val String APIKey = "<Enter API key>"


var PodID_01 = "<enter your pod id here>"  //Living Room
var PodID_02 = "<enter your pod id here>"  //Bedroom


rule "Read Sensibo State  PodID_01"

  when
    System started or
    Time cron "0 0/5 * * * ?" //Update states from senibo every 5 min
  then

  //Thread::sleep(1000)

  try {
    var String PodStatus 
      
    do {
      Thread::sleep(5000)
      PodStatus = executeCommandLine('curl -sSH "Accept: application/json"     "https://home.sensibo.com/api/v2/pods/' + PodID_01 + '/acStates?apiKey=' + APIKey + '&limit=1&fields=acState"', 5000)
      //logInfo("Sensibo result - PodStatus", PodStatus)
    } while (!PodStatus.contains('"status": "success"'))
    
    val String PodOn = (transform("JSONPATH", "$.result[0].acState.on", PodStatus))
    val String PodMode = (transform("JSONPATH", "$.result[0].acState.mode", PodStatus))
    val Number PodTarget = new Integer(transform("JSONPATH", "$.result[0].acState.targetTemperature", PodStatus))
    val String PodFan = (transform("JSONPATH", "$.result[0].acState.fanLevel", PodStatus))

    postUpdate(Sensibo_Living_State, PodOn)
    postUpdate(Sensibo_Living_Target, PodTarget)
    postUpdate(Sensibo_Living_Mode, PodMode)
    postUpdate(Sensibo_Living_Fan, PodFan)

    if (PodStatus.contains('"swing": ')) {
      val String PodSwing = (transform("JSONPATH", "$.result[0].acState.swing", PodStatus))
      postUpdate(Sensibo_Living_Swing, PodSwing)
    }
  }

  catch(Throwable t) {
    logError("Sensibo read state", "Error was caught: {}", t)
  }

end



rule "Read Sensibo Measurements PodID_01"

  when
    Time cron "0 0/5 * * * ?"  //Update states from senibo every 5 min
  then

  try {
    var String PodMeasurements
      
    do {
      PodMeasurements = executeCommandLine('curl -sSH "Accept: application/json"     "https://home.sensibo.com/api/v2/pods/' + PodID_01 + '/measurements?apiKey=' + APIKey + '&fields=temperature,humidity,batteryVoltage"', 5000)
      //logInfo("Sensibo result - PodMeasurements", PodMeasurements)
      Thread::sleep(5000)
    } while (!PodMeasurements.contains('"status": "success"'))
        
    var PodTemperature = new Double(transform("JSONPATH", "$.result[0].temperature", PodMeasurements))
    var PodHumidity = new Double(transform("JSONPATH", "$.result[0].humidity", PodMeasurements))

    postUpdate(Sensibo_Living_Temp, PodTemperature)
    postUpdate(Sensibo_Living_Humidity, PodHumidity)

    if (!PodMeasurements.contains('"batteryVoltage": null')) {
      val Number PodBattery = new Integer(transform("JSONPATH", "$.result[0].batteryVoltage", PodMeasurements))
      postUpdate(Sensibo_Living_Battery, PodBattery)
    }
  }
  
  catch(Throwable e) {
    logError("Sensibo measurements", "Error was caught: {}", e)
  }

end


rule "Write Sensibo State PodID_01"

  when
    Item Sensibo_Living_State received command or
    Item Sensibo_Living_Mode received command or
    Item Sensibo_Living_Fan received command or
    Item Sensibo_Living_Swing received command or
    Item Sensibo_Living_Target received command
  then
  
  try {

    //Thread::sleep(1000)         // Avoid race condition where state has not yet been updated

    var CommandURL = 'https://home.sensibo.com/api/v2/pods/' + PodID_01 + '/acStates?apiKey=' + APIKey

    var CommandState = '{"acState":{"on":' + Sensibo_Living_State.state + ','
    var CommandMode = '"mode":"' + Sensibo_Living_Mode.state + '",'
    var CommandFan = '"fanLevel":"' + Sensibo_Living_Fan.state + '",'
    var CommandSwing = ""

    if (Sensibo_Living_Swing.state != "")
      { CommandSwing = '"swing":"' + Sensibo_Living_Swing.state + '"}}' }

    var String CommandTemp = '"targetTemperature":' + (Sensibo_Living_Target.state as DecimalType).intValue + ','
    var String CommandTempUnit = '"temperatureUnit":"C",'
    var String CommandData = CommandState + CommandMode + CommandFan + CommandTemp + CommandTempUnit + CommandSwing

    var String UpdateResult
    var Number Attempts = 0

    var CommandExec = 'curl@@-sSH@@"Accept: application/json"@@-H@@"Content-Type: application/json"@@-X@@POST@@-d@@' + CommandData + '@@' + CommandURL
  
    do {
      Attempts += 1
      //logInfo("Sensibo command", CommandData)
      UpdateResult = executeCommandLine(CommandExec, 10000)
      //logInfo("Sensibo result", UpdateResult)
      Thread::sleep(5000)
    } while (!UpdateResult.contains('"status": "success"') && Attempts < 5)
  
    if (Attempts == 5) {
      logError("Sensibo write", "5 failures updating Sensibo")
    }      
  }

  catch(Throwable t) {
    logError("Sensibo write", "Error was caught: {}", t)
  }

end

/***  Bedroom Sensibo ***/

rule "Read Sensibo State  PodID_02"

  when
    System started or
    Time cron "0 0/5 * * * ?"  //Update states from senibo every 5 min
  then

  //Thread::sleep(1000)

  try {
    var String PodStatus 
      
    do {
      Thread::sleep(5000)
      PodStatus = executeCommandLine('curl -sSH "Accept: application/json"     "https://home.sensibo.com/api/v2/pods/' + PodID_02 + '/acStates?apiKey=' + APIKey + '&limit=1&fields=acState"', 5000)
      //logInfo("Sensibo result - PodStatus", PodStatus)
    } while (!PodStatus.contains('"status": "success"'))
    
    val String PodOn = (transform("JSONPATH", "$.result[0].acState.on", PodStatus))
    val String PodMode = (transform("JSONPATH", "$.result[0].acState.mode", PodStatus))
    val Number PodTarget = new Integer(transform("JSONPATH", "$.result[0].acState.targetTemperature", PodStatus))
    val String PodFan = (transform("JSONPATH", "$.result[0].acState.fanLevel", PodStatus))

    postUpdate(Sensibo_Bedroom_State, PodOn)
    postUpdate(Sensibo_Bedroom_Target, PodTarget)
    postUpdate(Sensibo_Bedroom_Mode, PodMode)
    postUpdate(Sensibo_Bedroom_Fan, PodFan)

    if (PodStatus.contains('"swing": ')) {
      val String PodSwing = (transform("JSONPATH", "$.result[0].acState.swing", PodStatus))
      postUpdate(Sensibo_Bedroom_Swing, PodSwing)
    }
  }

  catch(Throwable t) {
    logError("Sensibo read state", "Error was caught: {}", t)
  }

end

rule "Read Sensibo Measurements PodID_02"

  when
    Time cron "0 0/5 * * * ?"  //Update states from senibo every 5 min
  then

  try {
    var String PodMeasurements
      
    do {
      PodMeasurements = executeCommandLine('curl -sSH "Accept: application/json"     "https://home.sensibo.com/api/v2/pods/' + PodID_02 + '/measurements?apiKey=' + APIKey + '&fields=temperature,humidity,batteryVoltage"', 5000)
      //logInfo("Sensibo result - PodMeasurements", PodMeasurements)
      Thread::sleep(5000)
    } while (!PodMeasurements.contains('"status": "success"'))
        
    var PodTemperature = new Double(transform("JSONPATH", "$.result[0].temperature", PodMeasurements))
    var PodHumidity = new Double(transform("JSONPATH", "$.result[0].humidity", PodMeasurements))

    postUpdate(Sensibo_Bedroom_Temp, PodTemperature)
    postUpdate(Sensibo_Bedroom_Humidity, PodHumidity)

    if (!PodMeasurements.contains('"batteryVoltage": null')) {
      val Number PodBattery = new Integer(transform("JSONPATH", "$.result[0].batteryVoltage", PodMeasurements))
      postUpdate(Sensibo_Bedroom_Battery, PodBattery)
    }
  }
  
  catch(Throwable e) {
    logError("Sensibo measurements Bedroom", "Error was caught: {}", e)
  }

end

rule "Write Sensibo State PodID_02"

  when
    Item Sensibo_Bedroom_State received command or
    Item Sensibo_Bedroom_Mode received command or
    Item Sensibo_Bedroom_Fan received command or
    Item Sensibo_Bedroom_Swing received command or
    Item Sensibo_Bedroom_Target received command
  then
  
  try {

    //Thread::sleep(1000)         // Avoid race condition where state has not yet been updated

    var CommandURL = 'https://home.sensibo.com/api/v2/pods/' + PodID_02 + '/acStates?apiKey=' + APIKey

    var CommandState = '{"acState":{"on":' + Sensibo_Bedroom_State.state + ','
    var CommandMode = '"mode":"' + Sensibo_Bedroom_Mode.state + '",'
    var CommandFan = '"fanLevel":"' + Sensibo_Bedroom_Fan.state + '",'
    var CommandSwing = ""

    if (Sensibo_Bedroom_Swing.state != "")
      { CommandSwing = '"swing":"' + Sensibo_Bedroom_Swing.state + '"}}' }

    var String CommandTemp = '"targetTemperature":' + (Sensibo_Bedroom_Target.state as DecimalType).intValue + ','
    var String CommandTempUnit = '"temperatureUnit":"C",'
    var String CommandData = CommandState + CommandMode + CommandFan + CommandTemp + CommandTempUnit + CommandSwing

    var String UpdateResult
    var Number Attempts = 0

    var CommandExec = 'curl@@-sSH@@"Accept: application/json"@@-H@@"Content-Type: application/json"@@-X@@POST@@-d@@' + CommandData + '@@' + CommandURL
  
    do {
      Attempts += 1
      //logInfo("Sensibo command", CommandData)
      UpdateResult = executeCommandLine(CommandExec, 10000)
      //logInfo("Sensibo result", UpdateResult)
      Thread::sleep(5000)
    } while (!UpdateResult.contains('"status": "success"') && Attempts < 5)
  
    if (Attempts == 5) {
      logError("Sensibo write", "5 failures updating Sensibo")
    }      
  }

  catch(Throwable t) {
    logError("Sensibo write", "Error was caught: {}", t)
  }

end

Items

String Sensibo_Living_State  
Number Sensibo_Living_Temp
Number Sensibo_Living_Humidity
Number Sensibo_Living_Battery
Number Sensibo_Living_Target
String Sensibo_Living_Fan
String Sensibo_Living_Mode
String Sensibo_Living_Swing


String Sensibo_Bedroom_State
Number Sensibo_Bedroom_Temp
Number Sensibo_Bedroom_Humidity
Number Sensibo_Bedroom_Battery
Number Sensibo_Bedroom_Target
String Sensibo_Bedroom_Fan
String Sensibo_Bedroom_Mode
String Sensibo_Bedroom_Swing

Sitemap Example

sitemap sensibo label="Sensibo" {
  Frame label="Sensibo Living" {
    Switch    item=Sensibo_Living_State      label="Sensibo_Living_State" mappings=["true"="ON", "false"="OFF"] icon="switch"
                                
    Switch    item=Sensibo_Living_Mode       label="Mode"                     icon="heating"        mappings=[cool="Cool",heat="Heat",fan="Fan"]
    Setpoint  item=Sensibo_Living_Target     label="Target [%.0f ºC]"         icon="temperature"    minValue=18 maxValue=26 step=1.0
    Switch    item=Sensibo_Living_Fan        label="Fan"                      icon="fan"            mappings=[low="Low",medium="Med",high="High",auto="Auto"]
    Switch    item=Sensibo_Living_Swing      label="Swing"                    icon="fan"            mappings=[stopped="Stop",fixedMiddle="Middle",fixedTop="High",rangeFull="Swing"]
    Text      item=Sensibo_Living_Temp       label="Temperature [%.1f ºC]"    icon="temperature"
    Text      item=Sensibo_Living_Humidity   label="Humidity [%.0f %%]"       icon="water"
   // Text      item=Sensibo_Living_Battery    label="Battery [%d mV]"          icon="energy"
  }
  
  Frame label="Sensibo Bedroom" {
    Switch    item=Sensibo_Bedroom_State      label="Sensibo Bedroom State"     mappings=["true"="ON", "false"="OFF"] icon="switch"

    Switch    item=Sensibo_Bedroom_Mode       label="Mode"                     icon="heating"        mappings=[cool="Cool",heat="Heat",fan="Fan"]
    Setpoint  item=Sensibo_Bedroom_Target     label="Target [%.0f ºC]"         icon="temperature"    minValue=18 maxValue=26 step=1.0
    Switch    item=Sensibo_Bedroom_Fan        label="Fan"                      icon="fan"            mappings=[low="Low",medium="Med",high="High",auto="Auto"]
    Switch    item=Sensibo_Bedroom_Swing      label="Swing"                    icon="fan"            mappings=[stopped="Stop",fixedMiddle="Middle",fixedTop="High",rangeFull="Swing"]
    Text      item=Sensibo_Bedroom_Temp       label="Temperature [%.1f ºC]"    icon="temperature"
    Text      item=Sensibo_Bedroom_Humidity   label="Humidity [%.0f %%]"       icon="water"
   // Text      item=Sensibo_Bedroom_Battery    label="Battery [%d mV]"          icon="energy"
  }
}

I hard coded the api and pod_id because you only do it once.

However @Zodiarc Looks like he is doing a binding that will fill in that data for you. I am new to this and am quickly learning how to do stuff. Also @Zodiarc “I’ll add the rest as soon as I can solve my current problem” maybe it is something we can help you with. I probably won’t be able to help but if I know what the problem is I or others may be able to help. If it all comes together in binding I won’t have to rewrite all of my rules to use PATCH instead of posting all of the commands again to Sensibo
Keep up the good work. I have a solution running and am not trying to hurry you at all.

1 Like

@denominator I described my problem here: Is there a proper documentation on dynamic state options?

@Zodiarc

I would just like to thank you for all the effort you are putting into openHAB I read the other tread and it looks like you are nearly there. It looks like others are having the same issues as you are and their is a gap in the documentation. I see that you got the swing chanel to work so you are nearly there.

You may have probably seen the yamaha binding it may be another example of running code that works.

The problem is not the channel type but the channel options.

Development of the binding has been canceled. Sorry to inform you about it but I won´t develop this binding any further. I made no progress for over a month since dynamic state descriptors simply don´t work although my implementation has been confirmed for correct and now the build is even failing die to errors in files I didn´t touch for weeks. I also don´t have the time anymore to work on it further. The code will remain up on my github, so if anyone wants to take over, feel free to do so.