Control a new air conditioning using Exec

Good morning

This is my little contribution…

My Story

I’ve been trying to control my White Westinghouse air conditioner.

One thing to know is that usual remotes controls, for TV, HI-FI, … send a signal for each key pressed (often in loops while the key is pressed). An air conditioning remote often displays information about the parameters selected. But of course parameters can be changed on the remote while the unit is out of reach, which could lead to synchronization problems between the display and the unit in some cases if it worked like the TV remotes. This implies that when such a remote sends a signal, it sends the whole parameters set.
Something that with LIRC would have been quite tedious, then searching the internet I found an alternative solution that thanks to EXEC I was able to execute with the help of a very interesting tutorial…

The tool I use is based on pigpio library:

The pigpio library

Download link:

IR Record and Playback

(On the website explains how to use the code, recording them and their use)

In my case, my air has the automatic function (depending on the internal and external temperature, know if the room need heating or cooling), this solves a lot of things because I only have to record the temperatures while the (auto) function is fixed.

For this record from the minimum temperature 17 ºC to 30 ºC, also record the function on (at 24 º C as it is the recommended temperature of use) and the shutdown function.

With the help of the following tutorial, base all my code

Let’s get started…

Things

Thing exec:command:ac [
            command="/etc/openhab2/scripts/irrp.py -p -g23 -f /etc/openhab2/scripts/Auto24 %2$s",
            interval=0,
            autorun=true]

Thing exec:command:actemp [
            command="/etc/openhab2/scripts/irrp.py -p -g23 -f /etc/openhab2/scripts/Auto24 %2$s",
            interval=0,
            autorun=true]

One things for on/off fuction and the other for set temperature

Items

Switch Aireacondicionado
String PlaceHolder ""

Switch Remote_Send      { channel="exec:command:ac:run"    }
String Remote_Send_Args { channel="exec:command:ac:input"  }
String Remote_Send_Out  { channel="exec:command:ac:output" }

String PlaceHolder1 ""

String Remote_Send_Argstemp { channel="exec:command:actemp:input"  }

Group Thermostat "Aire acondicionado"
Number Aireacondicionado_numero "Temperatura Aire [%.1f ºC]" (Thermostat)
Number Aireacondicionado_setpoint "Target [%.1f ºC]" (Thermostat)

Sitemap

sitemap demo label="Menú"
{	Frame label="Habitación" {
		Switch item=Aireacondicionado label="Aire" icon="ac"
		Setpoint item=Aireacondicionado_setpoint minValue=17 maxValue=30 step=1 icon="temperature1" visibility=[Aireacondicionado==ON]
	}
}

Well and the rules, this section is totally based on @josar 's tutorial, total recognition of his work.

import java.util.concurrent.locks.ReentrantLock

val ReentrantLock transmitter = new ReentrantLock

rule "Aire on/off"
  when
    Item Aireacondicionado received command
  then

    logInfo("Prueba", "Módulo " + triggeringItem.name + " to " + receivedCommand)
    
    try {
      transmitter.lock()

      if(receivedCommand == ON){
        Remote_Send_Args.sendCommand("on")
      }
      else {
        Remote_Send_Args.sendCommand("off")
      }

      while(Remote_Send.state != OFF){
        Thread::sleep(100)
      }

      Thread::sleep(400)
      logInfo("Prueba", Remote_Send_Out.state.toString.replaceAll("\r|\n"," Ejecutado ") )
    }catch(Throwable t) {}
    finally {
        // Free the resource for the next call.
        transmitter.unlock()
    } 
end

rule "Set 24ºC - ON"
  when
    Item Aireacondicionado changed to ON
  then 
    Aireacondicionado_setpoint.sendCommand(24)
end

rule "Set 24ºC - OFF"
  when
    Item Aireacondicionado changed to OFF
  then 
    Aireacondicionado_setpoint.sendCommand(24)
end

rule "Set Temperatura"
  when
    Item Aireacondicionado_setpoint received command 
  then
      if (Aireacondicionado.state == ON) {

    Thread::sleep(2000)

    logInfo("Prueba", "Temperatura " + triggeringItem.name + " to " + receivedCommand)
    
    try {
      transmitter.lock()
    var Number TT = Aireacondicionado_numero.state 

        Remote_Send_Argstemp.sendCommand( TT )
      }
    finally {
        transmitter.unlock()
    }}
end

rule "Inicio Aire - On/Off"
    when
	    System started
    then
        createTimer(now.plusSeconds(120)) [|
		if (Aireacondicionado.state == NULL) Aireacondicionado.postUpdate(OFF)
	]
end

rule "Inicio Aire - Setpoint"
    when
	    System started
    then
        createTimer(now.plusSeconds(130)) [|
		if (Aireacondicionado_setpoint.state == NULL) Aireacondicionado_setpoint.sendCommand(24)
	]
end

And this is it.



Please if there is any doubt or mistake, let me know.
I’m not a programmer or anything like that, I started all this a month ago so I can fail, your comments are welcome.
I know there’s a lot of extra code that could be simplified but I still haven’t learned yet, again your comments are welcome.

Thank you!

2 Likes

Thanks for posting! A couple of comments on the Rules.

ReentrantLocks are dangerous. Thread::sleeps are likewise, dangerous. Even worse is when you put those into a “forever” loop. See Why have my Rules stopped running? Why Thread::sleep is a bad idea for details.

As far as I can tell, the while loop waiting for Remote_Send to change to OFF and the following sleep only serves to delay when the logInfo runs. So instead of using the while loop and sleep, use a Design Pattern: Looping Timers.

    val timer = createTimer(now.plusMillis(100), [ |
        if(Remote_Send.state != OFF) timer.reschedule(now.plusMillis(100))
        else createTimer(now.plusMillis(400), [ | logInfo("Prueba", Remote_Send_Out.state.toString.replaceAll("\r|\n"," Ejecutado ") )])
    ])

And instead of using the ReentrantLock, use Design Pattern: Gate Keeper. This will queue up your commands and ensure that they never run at the same time or too close together without the risks imposed by the ReentrantLock and without consuming precious Rules threads.

1 Like

Thanks for the advice! Lately I was seeing bugs in my console of the type

2019-10-07 20:48:51.227 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2019-10-07T20:48:51.086-03:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@bc8ce3
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.10.0.oh240]
Caused by: java.lang.NullPointerException
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:66) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]

Once I turned off Thread::sleeps they stopped appearing, maybe that’s how the conflict was generated.

I will try your advice and comment!

That error is definitely coming from one of your Timers. Unfortunately it won’t say what the actual error is. The code in the Timers looks pretty benign so I’m not sure what would be that cause or why removing the sleeps would change anything.

One thing to keep in mind is if you reload your .rules file (e.g. make and save a change) any Timers you have running become orphaned and their context destroyed and you will see this exception in your logs. Do these correspond to a change in your .rules file?

Definitely, those errors come from modifying .rules a bunch of times. I was making many changes in .rules since after finishing confirming that the code worked I was modifying it to be able to use my AC with homekit, that experimentation took me to a long trial-error and the errors began. Solution? I deleted Thread::sleep, I restarted and continued editing .rules to work with homekit.

Thanks for the advice!