Rule for heating and cooling of brewing fermenter

  • Platform information: openHABian
    • Hardware: _Raspberry Pi 3
    • openHAB version: 2.4.0
  • Issue of the topic: rule for temperature control of brewing fermenter

Hi Guys,

Hoping to get some help with this rule I’m trying to implement to control both heating and cooling of my brewing fermenter. The fermenter sits in a fridge which does the cooling and there is also a heater inside the fridge for the heating. I plan on wiring both the fridge and heater to a Sonoff Dual with a DS18B20 temperature sensor attached. The sensor will sit in a thermowell in the center of the fermenter to record the temperature of the beer.

I have largely copied code from other examples on this forum and modified it for my own use, but haven’t quite been able to get it to work. If the target temperature is above the current temperature the heating function works ok, however if the target is below the current temperature the cooling function doesn’t come on. I think it may be down to the use of brackets, but I’m not sure. Any help would be most appreciated.

I have the following items:

Switch Sonoffdual02P1 "Fermenter Heat"     [ "Switchable" ] {mqtt=">[broker:garage/cmnd/sonoffdual-02/POWER1:command:*:default], <[broker:garage/stat/sonoffdual-02/POWER1:state:default]", autoupdate="false" }
Switch Sonoffdual02P2 "Fermenter Cool"     [ "Switchable" ] {mqtt=">[broker:garage/cmnd/sonoffdual-02/POWER2:command:*:default], <[broker:garage/stat/sonoffdual-02/POWER2:state:default]", autoupdate="false" }
Number Temperature_Setpoint_Mode
Number Temperature_Setpoint		"Temperature [%.1f °C]" <temperature>  { mqtt=">[broker:garage/cmnd/sonoffdual-02/set_point:command:*:default]" }

I have the following on my sitemap;

Text item=Sonoffdual02_temp label="Fermenter Temperature"
    Switch item=Temperature_Setpoint_Mode  label="Fermenter Control" mappings=[1="OFF", 2="ON"]
    Slider item=Temperature_Setpoint label="Fermenter Setpoint [%.1f °C]" icon="temperature" 

and the following rule file;

//This is the rules file for the brewery fermenter

//Ferementer Heat and Cool
rule "Fermenter Control"
when
   Item Sonoffdual02_temp changed or
      // triggers whenever temp changes
   Item Temperature_Setpoint_Mode changed
      // also triggers when mode changes for immediate action
      // else nothing would happen until temp changed, if it ever did
then
      // first we check if the temp reading is valid
   if ( Sonoffdual02_temp.state != NULL &&
        Sonoffdual02_temp.state != UNDEF &&
        Sonoffdual02_temp.state > 0) {
             // that != means "not equal" in an if(condition)
             // while the && means "and"

        // temp sensor is valid, so
        // let's set up a target temperature
      var Number temp = 0
      if (Temperature_Setpoint_Mode.state == 1) {
           // off
         temp = 0
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)

      } else if (Temperature_Setpoint_Mode.state == 2) {
            // on
         temp = Temperature_Setpoint.state as DecimalType
        }
         //HEAT
         // now let's check if the temperature is too low
      if (Sonoffdual02_temp.state < (temp - 0.5)) {
            // we allow 0.5 degree hysteresis
            // It's colder than target
            // so turn heater on if it isn't already
         if (Sonoffdual02P1.state != ON) {
            Sonoffdual02P1.sendCommand(ON)
         }

         // else lets check if we're at the target
      } else if (Sonoffdual02P1.state > temp) {
            // reached target temp
            // (our target could be 0 if mode is off)
            // so turn heater off if it isn't already
         if (Sonoffdual02P1.state != OFF) {
            Sonoffdual02P1.sendCommand(OFF)
         }
            // COOL
            // now let's check if the temperature is too high
      if (Sonoffdual02_temp.state > (temp + 0.5)) {
            // we allow 0.5 degree hysteresis
            // It's hotter than target
            // so turn fridge on if it isn't already
         if (Sonoffdual02P2.state != ON) {
            Sonoffdual02P2.sendCommand(ON)
         }

         // else lets check if we're at the target
      } else if (Sonoffdual02P2.state < temp) {
            // reached target temp
            // (our target could be 0 if mode is off)
            // so turn fridge off if it isn't already
         if (Sonoffdual02P2.state != OFF) {
            Sonoffdual02P2.sendCommand(OFF)
         }

      }  // else we do nothing here
         // temp could be within 0.5 degree of target
         // and the heater / fridge will continue doing what it is doing
         // That's the hysteresis

   } else {   // this else belongs to the valid temp reading check
      Sonoffdual02P1.sendCommand(OFF)
      Sonoffdual02P2.sendCommand(OFF)
          // temp sensor broken, turn off heater for safety
   }}
end

Thanks

I know this is not what you asked.

I would do the PID control on the sonoffdual. Otherwise if you have a hickup in your system you can loose the batch.

https://tasmota.github.io/docs/PID-Control/

1 Like

Try to be careful with your indents. It really makes it easier to tell what code is in what context. When you have an opening { all the code should be indented. The closing } should line up with the the line that started it.

And it should be consistent through the whole file. If you are using an indent of 4, you should always indent by 4 spaces (2 and 4 are the most common indents) and never have a closing } on any but a column divisible by 4. You’ve a mix of 2 and 4 indents and the closing } are all over the place and not every line is properly indented.

This isn’t just to make the code look pretty. It’s vital for you, the human coder, to understand (for example) where the code that will run when the if evaluates to true starts and stops.

For example, this:

      var Number temp = 0
      if (Temperature_Setpoint_Mode.state == 1) {
           // off
         temp = 0
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)

      } else if (Temperature_Setpoint_Mode.state == 2) {
            // on
         temp = Temperature_Setpoint.state as DecimalType
        }

Should be

    var Number temp = 0
    if (Temperature_Setpoint_Mode.state == 1) {
        // off
        temp = 0
        Sonoffdual02P1.sendCommand(OFF)
        Sonoffdual02P2.sendCommand(OFF)

    } else if (Temperature_Setpoint_Mode.state == 2) {
        // on
        temp = Temperature_Setpoint.state as DecimalType
    }

If the problem is with a wrongly placed closing bracket this will become apparent once you fix the indents.

It can also be helpful to label the closing brackets so you know which opening bracket it goes with

    var Number temp = 0
    if (Temperature_Setpoint_Mode.state == 1) {
        // off
        temp = 0
        Sonoffdual02P1.sendCommand(OFF)
        Sonoffdual02P2.sendCommand(OFF)

    } // Mode == 1
    else if (Temperature_Setpoint_Mode.state == 2) {
        // on
        temp = Temperature_Setpoint.state as DecimalType
    } // Mode == 2

This is particularly useful when the code is long enough that the opening bracket is off the screen.

It can also be helpful to avoid creating contexts. For example, check for a valid temp and if there isn’t one send the off commands and return;. Then you don’t need to indent the code that runs when the temp is valid at all.

    if ( Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0) {
        // temp sensor broken, turn off heater for safety
        Sonoffdual02P1.sendCommand(OFF)
        Sonoffdual02P2.sendCommand(OFF)
        return;
    }

    // Temperature sensor is valid
    var Number temp = 0
    if (Temperature_Setpoint_Mode.state == 1) { 
...

One thing I like to suggest is to use Strings instead of Numbers for things like Mode. Then you can use meaningful names in your code instead of needing to remember that 1 is ON and 2 is OFF (if these are the only two modes, why not use a Switch?).

In fact since 1 is OFF and you do the same thing for OFF as you do for a broken sensor, you can get rid of that whole if statement and just add another conditional to the first if statement.

    if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0) {
        // temp sensor broken, turn off heater for safety
        Sonoffdual02P1.sendCommand(OFF)
        Sonoffdual02P2.sendCommand(OFF)
        return;
    }

    // On and temp sensor is valid
    var temp = Temperature_Setpoint.state as Number
    // HEAT
    if(Sonofffdual02_temp.state < (temp - 0.5)) {
...

Use meaningful names for your Items. Instead of Sonoffdual02_temp which doesn’t really mean much on it’s own, why not a name like Fermenter_temp? Maybe someday you’ll replace the Sonoff with a Shelly or something. What it does hasn’t changed but because your old Item name was tied to the technology used either the Item name will be incorrect or it will need to be changed.

I find applying Design Pattern: How to Structure a Rule greatly helps to simplify Rules like this which also greatly helps with debugging problems.

We’ve already got step 1 sorted above with the return;. So what we need to do is separate the calculation of what needs to be done and actually doing it.

then
    // 1. Turn everything off and exit if we can't determine what to do or we are off.
    if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0) {
        // temp sensor broken, turn off heater for safety
        Sonoffdual02P1.sendCommand(OFF)
        Sonoffdual02P2.sendCommand(OFF)
        return;
    }

    // 2. Calculate what state the heater and cooler should be in. 
    // Initialize variable to the current state. If there is nothing to do nothing will change.
    var p1Cmd = Sonoffdual02P1.state
    var p2Cmd = Sonoffdual02P2.state
    var currTemp = Sonoffdual02_temp.state as Number // saves typing

    // If under the setpoint minus hysteresis turn off the cooler (shouldn't have been on anyway) and on the heater
    if(currTemp < (temp - 0.5)) {
        p1Cmd = ON
        p2Cmd = OFF
    }

    // If heater were ON and we reached the setpoint turn off the heater
    else if(p1Cmd == ON && currTemp >= temp) p1Cmd = OFF

    // If over the setpoint turn off the heat (shouldn't have been on anyway) and on the cool
    else if(SonoffDual02_temp > (temp + 0.5)) {
        p1Cmd = OFF
        p2Cmd = ON
    }

    // If the cooler were ON and we reached the setpoint turn off the cooler
    else if(p2Cmd == ON && currTemp <= temp) p2Cmd = OFF

    // 3. Do it, command the relays but only if they are not already in the needed state
    if(Sonoffdual02P1.state != p1Cmd) Sonoffdual02P1.sendCommand(p1Cmd)
    if(Sonoffdual02P2.state != p2Cmd) Sonoffdual02P1.sendCommand(p2Cmd)
end

The way it works is if the temp sensor is bad or the mode is OFF, turn off both the heater and cooler and return.

If we didn’t return we know the temp is valid and the mode is ON so we don’t need to have an if to check for that. Now we want to determine whether to turn on or off the heater or cooler based on the temp. So we initialize a variable with the current state of the Switches.

The order of the if/else checks is important. They will evaluate from top to bottom. So we first check to see if the currTemp is below (temp - 0.5) and if so turn ON the heating and off the cooling.

If that clause doesn’t run we know that the temp is above (temp - 0.5). Next we check to see if the heater is ON but the temp is at or above the target temp. If so we turn off the heater.

If that clause doesn’t run we know that the currTemp is above temp and the heater is not ON. Now we check to see if currTemp is greater than (temp + 0.5) and if so we turn on the cooler and off the heating.

Finally if that clause didn’t run we know that the currTemp is between (temp + 0.5) and (temp - 0.5) and the heater is not ON. If that were not the case than one of the other three clauses would have run already and this one skipped. When this is the case we want to turn off the cooler if it’s on and the currTemp is below temp.

In all other situations we want the heater and cooler to keep on doing what ever they were doing.

Now that we are past the checks and know what state the heater and cooler need to be in, we sendCommand to both devices, but only if they are not already doing what they need to be doing.

I believe the above version is easier to understand and modify should you need to in the future.

It is hard to say because the indents are all messed up and I don’t want to go through and reformat it properly, but I suspect the problem is else if (Sonoffdual02P1.state > temp) {. That will evaluate to true when the cooler is supposed to come on which will prevent the code that actually does turn on/off the cooler from running.

2 Likes

Hi James,

Thanks for the suggestion, I didn’t realise there was this option for Tasmota. I’ll look into that.
Agree local control would be a lot less risky. I’d hate to lose precious beer due to a network issue or something.

Cheers,

Lachy

1 Like

Hi Rich,

Wow! Thank you for spending the time to write such a detailed and helpful reply. For someone like me who has very little programming skills it is great to have people like yourself available to talk to through this forum.

I’ve adjusted my rules file with your suggestions (I think), and to my limited understanding it all makes sense, however it still doesn’t want to play ball. I’m getting the following error in the log;

2020-05-15 12:28:59.696 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'fermenter.rules', using it anyway:

Unreachable expression.

and when I switch Temperature_Setpoint_Mode from 1 to 2 or when the temperature changes it just seems to turn P1 & P2 off;

2020-05-15 14:01:49.046 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint_Mode' received command 2

2020-05-15 14:01:49.063 [vent.ItemStateChangedEvent] - Temperature_Setpoint_Mode changed from 1 to 2

2020-05-15 14:01:49.116 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-15 14:01:49.133 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P2' received command OFF

2020-05-15 14:02:20.513 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 16.9 to 16.7

2020-05-15 14:02:20.567 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-15 14:02:20.576 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P2' received command OFF

Here is my rules file now;

//This is the rules file for the brewery fermenter

//Ferementer Heat and Cool
rule "Fermenter Control"
when
   Item Sonoffdual02_temp changed or
      // triggers whenever temp changes
   Item Temperature_Setpoint_Mode changed
      // also triggers when mode changes for immediate action
      // else nothing would happen until temp changed, if it ever did
then
    // 1. Turn everything off and exit if we can't determine what to do or if we are off.
     if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0) 
         Sonoffdual02_temp.state >= 30{
         // temp sensor broken, turn off heater for safety
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)
         return;
    }
   // On and temp sensor is valid
    var temp = Temperature_Setpoint.state as Number

    // 2. Calculate what state the heater and cooler should be in. 
    // Initialize variable to the current state. If there is nothing to do nothing will change.
    var p1Cmd = Sonoffdual02P1.state
    var p2Cmd = Sonoffdual02P2.state
    var currTemp = Sonoffdual02_temp.state as Number // saves typing
    

    // If under the setpoint minus hysteresis turn off the cooler (shouldn't have been on anyway) and on the heater
    if(currTemp < (temp - 0.5)) {
        p1Cmd = ON
        p2Cmd = OFF
    }

    // If heater were ON and we reached the setpoint turn off the heater
    else if(p1Cmd == ON && currTemp >= temp) p1Cmd = OFF

    // If over the setpoint turn off the heat (shouldn't have been on anyway) and on the cool
    else if(currTemp > (temp + 0.5)) {
        p1Cmd = OFF
        p2Cmd = ON
    }

    // If the cooler were ON and we reached the setpoint turn off the cooler
    else if(p2Cmd == ON && currTemp <= temp) p2Cmd = OFF

    // 3. Do it, command the relays but only if they are not already in the needed state
    if(Sonoffdual02P1.state != p1Cmd) Sonoffdual02P1.sendCommand(p1Cmd)
    if(Sonoffdual02P2.state != p2Cmd) Sonoffdual02P1.sendCommand(p2Cmd)
end

Thanks again for your help.

Regards

Just one little warning from another brewer:
Refrigerators and freezers do not like to be started and stopped too often.
In addition; heaters have a mass that holds energy, so the “room” temperature will overshoot after a heating “cycle”.

But since you use a thermowell it may not be a problem, I think :slight_smile:
I would log everything into Grafana. It is then very easy to see what is going on.

Skål!

Thanks Nils.

The fermenter fridge is already in operation with an STC-1000 temperature controller which works great but I’d really like to integrate monitoring and control of the fermenter into OpenHAB for remote access.

Cheers

That’s a very unusual complaint. Unfortunate it does not offer any clues.

But …

pay close attention to closing bracket there

As rossko57 spied, that new condition that you added to the first if statement . It’s missing the || and you have to move the closing paren

     if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0 ||
         Sonoffdual02_temp.state >= 30){

I suspect that handles it all because the error above is disassociating the clause that is supposed to run when the if evaluates to true causing it to run every time regardless.

Hi guys,

Sorry to be a pain…
Still getting some strange errors. Do you think these are because of the rule or something wrong with system?
The heat side sometimes comes on but acts a bit erratically but can’t seem to get the cool side to function at all.

2020-05-16 11:01:46.332 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Fermenter Control': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(org.eclipse.smarthome.core.items.Item,java.lang.String) on instance: null

2020-05-16 11:01:46.576 [ERROR] [ersey.server.ServerRuntime$Responder] - An I/O error has occurred while writing a response message entity to the container output stream.

org.glassfish.jersey.server.internal.process.MappableException: org.eclipse.jetty.io.EofException

	at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:92) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162) ~[171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1130) ~[171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:711) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:329) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.internal.Errors.process(Errors.java:315) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.internal.Errors.process(Errors.java:297) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.internal.Errors.process(Errors.java:267) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) [171:org.glassfish.jersey.core.jersey-common:2.22.2]

	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) [172:org.glassfish.jersey.core.jersey-server:2.22.2]

	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) [169:org.glassfish.jersey.containers.jersey-container-servlet-core:2.22.2]

	at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) [169:org.glassfish.jersey.containers.jersey-container-servlet-core:2.22.2]

	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) [169:org.glassfish.jersey.containers.jersey-container-servlet-core:2.22.2]

	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) [169:org.glassfish.jersey.containers.jersey-container-servlet-core:2.22.2]

	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) [169:org.glassfish.jersey.containers.jersey-container-servlet-core:2.22.2]

	at com.eclipsesource.jaxrs.publisher.internal.ServletContainerBridge.service(ServletContainerBridge.java:76) [20:com.eclipsesource.jaxrs.publisher:5.3.1.201602281253]

	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:865) [85:org.eclipse.jetty.servlet:9.4.11.v20180605]

	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535) [85:org.eclipse.jetty.servlet:9.4.11.v20180605]

	at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.doHandle(HttpServiceServletHandler.java:71) [186:org.ops4j.pax.web.pax-web-jetty:7.2.3]

	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548) [82:org.eclipse.jetty.security:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1595) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1317) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.doHandle(HttpServiceContext.java:293) [186:org.ops4j.pax.web.pax-web-jetty:7.2.3]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) [85:org.eclipse.jetty.servlet:9.4.11.v20180605]

	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1564) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1219) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.ops4j.pax.web.service.jetty.internal.JettyServerHandlerCollection.handle(JettyServerHandlerCollection.java:80) [186:org.ops4j.pax.web.pax-web-jetty:7.2.3]

	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.Server.handle(Server.java:531) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260) [84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281) [75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102) [75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118) [75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680) [87:org.eclipse.jetty.util:9.4.11.v20180605]

	at java.lang.Thread.run(Thread.java:748) [?:?]

Caused by: org.eclipse.jetty.io.EofException

	at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:286) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:429) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:323) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:380) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpConnection$SendCallback.process(HttpConnection.java:798) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241) ~[87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:224) ~[87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpConnection.send(HttpConnection.java:538) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpChannel.sendResponse(HttpChannel.java:790) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpChannel.write(HttpChannel.java:846) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:240) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:216) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:541) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at java.io.OutputStream.write(OutputStream.java:75) ~[?:?]

	at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.write(ResponseWriter.java:320) ~[?:?]

	at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:218) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$UnCloseableOutputStream.write(WriterInterceptorExecutor.java:294) ~[?:?]

	at org.eclipse.smarthome.io.rest.core.internal.GsonProvider.writeTo(GsonProvider.java:71) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:265) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162) ~[?:?]

	at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162) ~[?:?]

	at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:86) ~[?:?]

	... 53 more

Caused by: java.io.IOException: Broken pipe

	at sun.nio.ch.FileDispatcherImpl.writev0(Native Method) ~[?:?]

	at sun.nio.ch.SocketDispatcher.writev(SocketDispatcher.java:51) ~[?:?]

	at sun.nio.ch.IOUtil.write(IOUtil.java:148) ~[?:?]

	at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:504) ~[?:?]

	at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:266) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:429) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:323) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:380) ~[75:org.eclipse.jetty.io:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpConnection$SendCallback.process(HttpConnection.java:798) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241) ~[87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:224) ~[87:org.eclipse.jetty.util:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpConnection.send(HttpConnection.java:538) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpChannel.sendResponse(HttpChannel.java:790) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpChannel.write(HttpChannel.java:846) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:240) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:216) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:541) ~[84:org.eclipse.jetty.server:9.4.11.v20180605]

	at java.io.OutputStream.write(OutputStream.java:75) ~[?:?]

	at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.write(ResponseWriter.java:320) ~[?:?]

	at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:218) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$UnCloseableOutputStream.write(WriterInterceptorExecutor.java:294) ~[?:?]

	at org.eclipse.smarthome.io.rest.core.internal.GsonProvider.writeTo(GsonProvider.java:71) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:265) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162) ~[?:?]

	at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106) ~[?:?]

	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162) ~[?:?]

	at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:86) ~[?:?]

	... 53 more

thanks again. I feel like it’s close but I’m not adding a lot of value to solving the problem.

Cheers

Have you read earlier replies? Did you fix the broken if()? Don’t forget we cannot see what you are doing. What does the rule look like now?

Fairly obviously something to do with a sendCommand, not so obvious which one.

I’m going to guess that youv’e run into a subtle trap, where states are not commands.
A Switch type Item can have a state ON. You can send a Switch type Item a command ON. But …
someItem.sendCommand( thatItem.state )
will fail, because state is not command even if they look the same to us.

Your rule will do that sometimes, because -

The get round is turn whatever it is you are trying to send into a simple string, and let the sendCommand function parse it back into whatever type it wants.

... Sonoffdual02P1.sendCommand(p1Cmd.toString)

Sorry I should just get in the habit of re-posting the rule every time.
Below is the rule as it stands now and hopefully including the changes you suggested above.
I wasn’t quite sure were if I should add the ‘toSting’ elsewhere, but added it to the last to lines as suggested.

//This is the rules file for the brewery fermenter

//Ferementer Heat and Cool
rule "Fermenter Control"
when
   Item Sonoffdual02_temp changed or
      // triggers whenever temp changes
   Item Temperature_Setpoint changed or
      // triggers whenever the temp setpoint changes
   Item Temperature_Setpoint_Mode changed
      // also triggers when mode changes for immediate action
      // else nothing would happen until temp changed, if it ever did
then
    // 1. Turn everything off and exit if we can't determine what to do or if we are off.
     if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0 ||
         Sonoffdual02_temp.state >= 30){
         // temp sensor broken, turn off heater for safety
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)
         return;
    }
   // On and temp sensor is valid
    var temp = Temperature_Setpoint.state as Number

    // 2. Calculate what state the heater and cooler should be in. 
    // Initialize variable to the current state. If there is nothing to do nothing will change.
    var p1Cmd = Sonoffdual02P1.state
    var p2Cmd = Sonoffdual02P2.state
    var currTemp = Sonoffdual02_temp.state as Number // saves typing
    

    // If under the setpoint minus hysteresis turn off the cooler (shouldn't have been on anyway) and on the heater
    if(currTemp < (temp - 0.5)) {
        p1Cmd = ON
        p2Cmd = OFF
    }

    // If heater were ON and we reached the setpoint turn off the heater
    else if(p1Cmd == ON && currTemp >= temp) p1Cmd = OFF

    // If over the setpoint turn off the heat (shouldn't have been on anyway) and on the cool
    else if(currTemp > (temp + 0.5)) {
        p1Cmd = OFF
        p2Cmd = ON
    }

    // If the cooler were ON and we reached the setpoint turn off the cooler
    else if(p2Cmd == ON && currTemp <= temp) p2Cmd = OFF

    // 3. Do it, command the relays but only if they are not already in the needed state
    if(Sonoffdual02P1.state != p1Cmd) Sonoffdual02P1.sendCommand(p1Cmd.toString)
    if(Sonoffdual02P2.state != p2Cmd) Sonoffdual02P1.sendCommand(p2Cmd.toString)
end

I noticed that the commands where only being sent when the temperature changed, so I added a line under ‘when’ to include changes to the setpoint value.
In the log below you can see I manually changed the setpoint from 16 to 4 degrees and the system turns both Sonoffdual02P1 and Sonoffdual02P2 to OFF because the temperature setpoint mode is 1. So that’s all fine.
I then turned it on by switching setpoint mode from 1 to 2 and Sonoffdual02P1 (heat) receives the ON command (although the setpoint of 4 degrees is below the curr temp 9.4). But then, the next time the temp changes it receives the OFF command. Sonoffdual02P1 then continues to go ON and OFF at every temperature change. Sonoffdual02P2 never seems to change state.

2020-05-17 08:35:50.225 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 16 to 4

2020-05-17 08:35:50.258 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-17 08:35:50.269 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P2' received command OFF

2020-05-17 08:35:56.273 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint_Mode' received command 2

2020-05-17 08:35:56.295 [vent.ItemStateChangedEvent] - Temperature_Setpoint_Mode changed from 1 to 2

2020-05-17 08:35:56.413 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

2020-05-17 08:35:56.478 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-17 08:36:32.372 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 9.4 to 9.5

2020-05-17 08:36:32.492 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-17 08:36:32.639 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF

2020-05-17 08:37:02.321 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 9.5 to 9.6

2020-05-17 08:37:02.433 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

2020-05-17 08:37:02.483 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-17 08:37:03.740 [vent.ItemStateChangedEvent] - GarageHumidity changed from 76.00 to 75.00

2020-05-17 08:37:14.472 [vent.ItemStateChangedEvent] - Sonoffth16_temp changed from 8.8 to 8.9

2020-05-17 08:37:24.571 [vent.ItemStateChangedEvent] - Sonoffth16_humi changed from 87.3 to 87.2

2020-05-17 08:37:44.453 [vent.ItemStateChangedEvent] - Sonoffth16_humi changed from 87.2 to 87.1

2020-05-17 08:37:54.491 [vent.ItemStateChangedEvent] - Sonoffth16_temp changed from 8.9 to 9.0

2020-05-17 08:38:04.537 [vent.ItemStateChangedEvent] - Sonoffth16_temp changed from 9.0 to 8.9

2020-05-17 08:38:04.548 [vent.ItemStateChangedEvent] - Sonoffth16_humi changed from 87.1 to 87.0

2020-05-17 08:38:12.348 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 9.6 to 9.7

2020-05-17 08:38:12.469 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-17 08:38:12.531 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF

2020-05-17 08:38:14.494 [vent.ItemStateChangedEvent] - Sonoffth16_temp changed from 8.9 to 9.0

When I raise the curr temp to above 30 Sonoffdual02P1 turns OFF as expected and when it drops to within the target range turns OFF as expected. The issue only seems to be when the setpoint is below the curr temp.

Thanks again for your input, it’s most appreciated. Although I’m sure this is very basic for you guys, I’m learning little snippets with every update so I thank you.

Regards

Let’s guess the error reports have gone now.

Good move.

Going to have to add some logInfo() to follow the path through the code.

Thanks @rossko57,

Yes the error report has gone away.

Now your talking way over my head. I get the theory of logInfo() but have no idea how or where to implement in a rule file like this. I assume you would put several throughout the code to pinpoint where the particular issue is, and monitor the log file to see what is returned. I might need to do some reading to understand how this works. Any assistance, would be most appreciated.

Regards

Lots of examples of logInfo() use. Useful both for following progress through a rule and seeing internal values, output appears in openhab.log

We can guess it’s the if-else stuff that’s going wrong - an unexpected value or comparison. Rules DSL can be weird about greater/less comparisons and typings, but I can’t see whats amiss.

logInfo("test", "begin with setp " + temp.toString + " curr " + currTemp.toString)
if (currTemp < (temp - 0.5)) {
   logInfo("test", "well below setpoint")
   p1Cmd = ON
   p2Cmd = OFF
} else if (p1Cmd == ON && currTemp >= temp) {
   logInfo("test", "still heating at setpoint")
   p1Cmd = OFF
} else if (currTemp > (temp + 0.5)) {
   logInfo("test", "well above setpoint")
   p1Cmd = OFF
   p2Cmd = ON
} else if (p2Cmd == ON && currTemp <= temp) {
   logInfo("test", "still cooling while below setpoint")
   p2Cmd = OFF
} else {
   logInfo("test", "no change")
}

ok great thanks for that. I plugged in the logInfo() as above and here is my rule now;

//This is the rules file for the brewery fermenter

//Ferementer Heat and Cool
rule "Fermenter Control"
when
   Item Sonoffdual02_temp changed or
      // triggers whenever temp changes
   Item Temperature_Setpoint changed or
      // triggers whenever the temp setpoint changes
   Item Temperature_Setpoint_Mode changed
      // also triggers when mode changes for immediate action
      // else nothing would happen until temp changed, if it ever did
then
    // 1. Turn everything off and exit if we can't determine what to do or if we are off.
     if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0 ||
         Sonoffdual02_temp.state >= 30){
         // temp sensor broken, turn off heater for safety
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)
         return;
    }
   // On and temp sensor is valid
    var temp = Temperature_Setpoint.state as Number

    // 2. Calculate what state the heater and cooler should be in. 
    // Initialize variable to the current state. If there is nothing to do nothing will change.
    var p1Cmd = Sonoffdual02P1.state
    var p2Cmd = Sonoffdual02P2.state
    var currTemp = Sonoffdual02_temp.state as Number // saves typing
    

    // If under the setpoint minus hysteresis turn off the cooler (shouldn't have been on anyway) and on the heater
    if(currTemp < (temp - 0.5)) {
        p1Cmd = ON
        p2Cmd = OFF
    }

    // If heater were ON and we reached the setpoint turn off the heater
    else if(p1Cmd == ON && currTemp >= temp) p1Cmd = OFF

    // If over the setpoint turn off the heat (shouldn't have been on anyway) and on the cool
    else if(currTemp > (temp + 0.5)) {
        p1Cmd = OFF
        p2Cmd = ON
    }

    // If the cooler were ON and we reached the setpoint turn off the cooler
    else if(p2Cmd == ON && currTemp <= temp) p2Cmd = OFF

    // 3. Do it, command the relays but only if they are not already in the needed state
    if(Sonoffdual02P1.state != p1Cmd) Sonoffdual02P1.sendCommand(p1Cmd.toString)
    if(Sonoffdual02P2.state != p2Cmd) Sonoffdual02P1.sendCommand(p2Cmd.toString)

logInfo("test", "begin with setp " + temp.toString + " curr " + currTemp.toString)
if (currTemp < (temp - 0.5)) {
   logInfo("test", "well below setpoint")
   p1Cmd = ON
   p2Cmd = OFF
} else if (p1Cmd == ON && currTemp >= temp) {
   logInfo("test", "still heating at setpoint")
   p1Cmd = OFF
} else if (currTemp > (temp + 0.5)) {
   logInfo("test", "well above setpoint")
   p1Cmd = OFF
   p2Cmd = ON
} else if (p2Cmd == ON && currTemp <= temp) {
   logInfo("test", "still cooling while below setpoint")
   p2Cmd = OFF
} else {
   logInfo("test", "no change")
}

end

and this is what a portion of the log file looks like when I put the setpoint below the current temp and turn it on;

2020-05-18 13:27:10.302 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint' received command 5

2020-05-18 13:27:10.320 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 0 to 5

2020-05-18 13:27:10.369 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-18 13:27:10.383 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P2' received command OFF

2020-05-18 13:27:11.359 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint_Mode' received command 2

2020-05-18 13:27:11.384 [vent.ItemStateChangedEvent] - Temperature_Setpoint_Mode changed from 1 to 2

2020-05-18 13:27:11.475 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

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

2020-05-18 13:27:11.496 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 5 curr 19.6

2020-05-18 13:27:11.521 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 13:27:11.588 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-18 13:27:13.647 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.6 to 19.5

2020-05-18 13:27:13.740 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

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

2020-05-18 13:27:13.772 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 5 curr 19.5

2020-05-18 13:27:13.796 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 13:27:13.814 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF


2020-05-18 13:27:33.672 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.5 to 19.6

2020-05-18 13:27:33.804 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

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

2020-05-18 13:27:33.821 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 5 curr 19.6

2020-05-18 13:27:33.840 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 13:27:33.887 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-18 13:28:13.637 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.6 to 19.7

2020-05-18 13:28:13.727 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

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

2020-05-18 13:28:13.747 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 5 curr 19.7

2020-05-18 13:28:13.771 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 13:28:13.980 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF

2020-05-18 13:28:23.639 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.7 to 19.6

2020-05-18 13:28:23.748 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

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

2020-05-18 13:28:23.760 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 5 curr 19.6

2020-05-18 13:28:23.779 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 13:28:23.832 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

P2 never receives a command to turn on Cool, however P1 cycles ON and OFF.

When I adjust the setpoint to within the target we get;

2020-05-18 13:32:15.094 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint' received command 20

2020-05-18 13:32:15.125 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 5 to 20

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

2020-05-18 13:32:15.232 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 20 curr 19.6

2020-05-18 13:32:15.258 [INFO ] [.eclipse.smarthome.model.script.test] - no change

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

2020-05-18 13:32:23.660 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.6 to 19.5

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

2020-05-18 13:32:23.784 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 20 curr 19.5

2020-05-18 13:32:23.809 [INFO ] [.eclipse.smarthome.model.script.test] - no change

so that seems ok except P1 was already ON and stayed ON.
when I adjust the setpoint to above the current temp and turn it on we get, it seams to function correctly with P1 coming on and staying on;

2020-05-18 13:38:51.972 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint' received command 30

2020-05-18 13:38:51.996 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 0 to 30

2020-05-18 13:38:52.031 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-18 13:38:52.041 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P2' received command OFF

2020-05-18 13:38:53.457 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint_Mode' received command 2

2020-05-18 13:38:53.473 [vent.ItemStateChangedEvent] - Temperature_Setpoint_Mode changed from 1 to 2

2020-05-18 13:38:53.563 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

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

2020-05-18 13:38:53.581 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 30 curr 19.5

2020-05-18 13:38:53.594 [INFO ] [.eclipse.smarthome.model.script.test] - well below setpoint

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

2020-05-18 13:38:53.656 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-18 13:38:54.408 [vent.ItemStateChangedEvent] - Sonoffth16_humi changed from 58.0 to 57.9

2020-05-18 13:39:03.652 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 19.5 to 19.6

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

2020-05-18 13:39:03.753 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 30 curr 19.6

2020-05-18 13:39:03.763 [INFO ] [.eclipse.smarthome.model.script.test] - well below setpoint

I’ve tried adjusting and removing a few of the brackets to see if that might be causing the issue but usually this just results in the rule not working at all.
Could my items file have issues that are causing the problem? Long shot…I only have one items file for all my home items, however I created a separate rules file for the fermenter. I also have another rules file for other items which I’ve checked to make sure there is nothing effecting these items.

Thanks

Um, you’ve added the new code to the rule. Have a proper look at it. The if-elseif block I suggested is a replacement for the original if-elseif block. It’s doing everything twice.

ahh ok sorry about that, I didn’t quite understand where it was supposed to go. That makes sense. :roll_eyes:

Rule file now;

//This is the rules file for the brewery fermenter

//Ferementer Heat and Cool
rule "Fermenter Control"
when
   Item Sonoffdual02_temp changed or
      // triggers whenever temp changes
   Item Temperature_Setpoint changed or
      // triggers whenever the temp setpoint changes
   Item Temperature_Setpoint_Mode changed
      // also triggers when mode changes for immediate action
      // else nothing would happen until temp changed, if it ever did
then
    // 1. Turn everything off and exit if we can't determine what to do or if we are off.
     if ( Temperature_Setpoint_Mode.state == 1 ||
         Sonoffdual02_temp.state == NULL ||
         Sonoffdual02_temp.state == UNDEF ||
         Sonoffdual02_temp.state <= 0 ||
         Sonoffdual02_temp.state >= 30){
         // temp sensor broken, turn off heater for safety
         Sonoffdual02P1.sendCommand(OFF)
         Sonoffdual02P2.sendCommand(OFF)
         return;
    }
   // On and temp sensor is valid
    var temp = Temperature_Setpoint.state as Number

    // 2. Calculate what state the heater and cooler should be in. 
    // Initialize variable to the current state. If there is nothing to do nothing will change.
    var p1Cmd = Sonoffdual02P1.state
    var p2Cmd = Sonoffdual02P2.state
    var currTemp = Sonoffdual02_temp.state as Number // saves typing
    

    // If under the setpoint minus hysteresis turn off the cooler (shouldn't have been on anyway) and on the heater
logInfo("test", "begin with setp " + temp.toString + " curr " + currTemp.toString)
if (currTemp < (temp - 0.5)) {
   logInfo("test", "well below setpoint")
   p1Cmd = ON
   p2Cmd = OFF
} else if (p1Cmd == ON && currTemp >= temp) {
   logInfo("test", "still heating at setpoint")
   p1Cmd = OFF
} else if (currTemp > (temp + 0.5)) {
   logInfo("test", "well above setpoint")
   p1Cmd = OFF
   p2Cmd = ON
} else if (p2Cmd == ON && currTemp <= temp) {
   logInfo("test", "still cooling while below setpoint")
   p2Cmd = OFF
} else {
   logInfo("test", "no change")
}

    // 3. Do it, command the relays but only if they are not already in the needed state
    if(Sonoffdual02P1.state != p1Cmd) Sonoffdual02P1.sendCommand(p1Cmd.toString)
    if(Sonoffdual02P2.state != p2Cmd) Sonoffdual02P1.sendCommand(p2Cmd.toString)

end

The behavior of the rule is still similar to above. Only the heat or P1 ever receives a command and when it’s below the setpoint it cycles ON and OFF with each temperature change. Setpoint above the current temp seams to function correctly and cuts out at 30 degrees.

2020-05-18 21:22:17.281 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint_Mode' received command 2

2020-05-18 21:22:17.309 [vent.ItemStateChangedEvent] - Temperature_Setpoint_Mode changed from 1 to 2

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

2020-05-18 21:22:17.411 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 4 curr 14.4

2020-05-18 21:22:17.446 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 21:22:17.467 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

2020-05-18 21:22:17.545 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

2020-05-18 21:22:43.994 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 14.4 to 14.5

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

2020-05-18 21:22:44.078 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 4 curr 14.5

2020-05-18 21:22:44.103 [INFO ] [.eclipse.smarthome.model.script.test] - still heating at setpoint

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

2020-05-18 21:22:44.122 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-18 21:22:44.279 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF

2020-05-18 21:23:24.048 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 14.5 to 14.4

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

2020-05-18 21:23:24.136 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 4 curr 14.4

2020-05-18 21:23:24.170 [INFO ] [.eclipse.smarthome.model.script.test] - well above setpoint

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

2020-05-18 21:23:24.195 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

2020-05-18 21:23:24.242 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON


2020-05-18 21:25:44.096 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 4 curr 14.3

2020-05-18 21:25:44.119 [INFO ] [.eclipse.smarthome.model.script.test] - still heating at setpoint

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

2020-05-18 21:25:44.134 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command OFF

2020-05-18 21:25:44.190 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from ON to OFF


//Changed setpoint to 14

2020-05-18 21:26:04.180 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint' received command 14

2020-05-18 21:26:04.210 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 4 to 14

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

2020-05-18 21:26:04.336 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 14 curr 14.3

2020-05-18 21:26:04.385 [INFO ] [.eclipse.smarthome.model.script.test] - no change

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

2020-05-18 21:26:14.004 [vent.ItemStateChangedEvent] - Sonoffdual02_temp changed from 14.3 to 14.4

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

2020-05-18 21:26:14.138 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 14 curr 14.4

2020-05-18 21:26:14.188 [INFO ] [.eclipse.smarthome.model.script.test] - no change

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

2020-05-18 21:26:34.702 [vent.ItemStateChangedEvent] - GarageTemperature changed from 18.00 to 17.00

2020-05-18 21:26:34.802 [vent.ItemStateChangedEvent] - GarageHumidity changed from 61.00 to 60.00

//Changed setpoint to 26


2020-05-18 21:27:33.506 [ome.event.ItemCommandEvent] - Item 'Temperature_Setpoint' received command 26

2020-05-18 21:27:33.527 [vent.ItemStateChangedEvent] - Temperature_Setpoint changed from 14 to 26

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

2020-05-18 21:27:33.609 [INFO ] [.eclipse.smarthome.model.script.test] - begin with setp 26 curr 14.4

2020-05-18 21:27:33.622 [INFO ] [.eclipse.smarthome.model.script.test] - well below setpoint

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

2020-05-18 21:27:33.638 [ome.event.ItemCommandEvent] - Item 'Sonoffdual02P1' received command ON

2020-05-18 21:27:33.697 [vent.ItemStateChangedEvent] - Sonoffdual02P1 changed from OFF to ON

I don’t know how many times I looked hard at these lines for exactly this mistake, but there it is.
Both lines send command to the same Item

Good eye and a perfect example why it is so important to use meaningful names for Items. If these were named, as I suggested in my first reply something like “Fermenter_Heater” and “Fermenter_Cooler” this typo never would have occurred.