Binding for Sensibo Sky available

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.

Thank you for your efforts.

Is anyone interested in continuing the development of this binding?
As I can see the only “big” thing which needs to be added is to fetch all data from the device (not just temp and humidity).

Anyway is there someone who uses the older rules to control Sensibo? Is it still working? Which is better right now?

New implementation submitted, prebuilt binary and documentation found by reading https://github.com/openhab/openhab2-addons/pull/5576

Testing feedback welcome.

Arne

Thank you very much! I will test it this weekend!

Can it do multiple devices I will test now

I can’t install it on 2.4 stable, GSON import is missing:

Error while starting bundle: file:/usr/share/openhab2/addons/org.openhab.binding.sensibo-2.5.0-SNAPSHOT.jar

org.osgi.framework.BundleException: Could not resolve module: org.openhab.binding.sensibo [284]

  Unresolved requirement: Import-Package: com.google.gson; version="[2.8.0,3.0.0)"

	at org.eclipse.osgi.container.Module.start(Module.java:444) ~[?:?]

	at org.eclipse.osgi.internal.framework.EquinoxBundle.start(EquinoxBundle.java:383) ~[?:?]

	at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundle(DirectoryWatcher.java:1260) [10:org.apache.felix.fileinstall:3.6.4]

	at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundles(DirectoryWatcher.java:1233) [10:org.apache.felix.fileinstall:3.6.4]

	at org.apache.felix.fileinstall.internal.DirectoryWatcher.doProcess(DirectoryWatcher.java:520) [10:org.apache.felix.fileinstall:3.6.4]

	at org.apache.felix.fileinstall.internal.DirectoryWatcher.process(DirectoryWatcher.java:365) [10:org.apache.felix.fileinstall:3.6.4]

	at org.apache.felix.fileinstall.internal.DirectoryWatcher.run(DirectoryWatcher.java:316) [10:org.apache.felix.fileinstall:3.6.4]

Yes, 2.5.0+ builds have gotten this dependency upgrade requirement.

You can install the newer gson bundle from maven repo; jar (235 KB)

Just copy it into the addons folder like you did with the binding file.

Hmm, I have added it, but the same error happens. Maybe a restart is required…
I will test this further on the weekend

Yes it works after restart

@seime

Thank You for getting this going excellent work.

I running openHAB 2.4.0-1 (Release Build) openhabian on RPI3
It is working good for both my sky’s
The only thing is the names come up as the mac addresses like

sensibo:sensibosky:home:45cfd940ee11:targetTemperature

Things File

Bridge sensibo:account:home "Sensibo account" [apiKey="Awesom_Work", refreshInterval=120] {
    Thing sensibosky lounge "Sensibo Sky Lounge" [ macAddress="45:cf:d9:40:ee:11" ]
    Thing sensibosky bedroom "Sensibo Sky Bedroom" [ macAddress="45:cf:58:0e:bd:d6" ]
}

The naming of the thing I thought would have been
sensibo:sensibosky:home:lounge
sensibo:sensibosky:home:bedroom

Like the hue:0210:mybridge:bulb1 example in

Thankyou again I can delete redundant rules now.

For others following along.
Easy way to find mac address is to navigate to
https://home.sensibo.com/api/v2/users/me/pods?fields=*&apiKey=YOURKEY
and find “macAddress”: “42:cb:d9:04:re:11”,

Changed things file and they are coming up as expected now.

Bridge sensibo:account:home "Sensibo account" [apiKey="ENTER_KEY_HERE", refreshInterval=120] {
    Thing sensibosky lounge "Sensibo Sky Lounge" @ "Lounge" [ macAddress="" ]
    Thing sensibosky bedroom "Sensibo Sky Bedroom" @ "Bedroom" [ macAddress="" ]
}