Looking for recommendations on illustrating and tracking HVAC cycles and runtime

Please share you recommendations on how best to track and illustrate the following items:

Number HVACUpStairsRun "HVAC Runtime" //shown in total minutes of operation
Number HVACUpStairsCyc "HVAC Cycles" //shown in total cycles

Both values increment + 1 based on the operating state of the HVAC unit. I have both items stored in rrd4.persist. I still use the old sitemap for my UI, but open to use other GUIs

My goal is to use the data to track the HVAC system as I relocate the thermostat in order to reduce the cycles. The current location has the unit operating too often. Even with a ±4 swing.

For something similar I have setup additional items to store persistence data and only visualize these items. Something like total / avg runtime /cycles last 24 hours / 3 days / 7 days

E.g. with runtime last 24 hours you can easily compare usage over several days.

Would you mind sharing your setup for the time periods mentioned? Thank you for your time.

I have a dsl rule that is triggered on update of the main item with the following code, e.g. adopted for your runtime item:

runtime_last_24h.postUpdate(HVACUpStairsRun.deltaSince(now.minusHours(24)))

deltaSince and other functions are documented here:

I’m interested in this rule also. I have 2 Ecobee’s that I would like to track heating/cooling periods by floor.

Trying to wrap my head around what is happening here. I’m guessing the following situation on your example.

  1. runtime_last_24h.state is an item that is persisted every minute?

  2. HVACUpStairsRun.state is an item that is tied to your thermostat when it starts in heat or cool mode?

Can you provide the actual rule for above OR can you show us what runtime_last_24h.state is return after 24 hour period to understand what to expect from that syntax?

Best, Jay

Here’s what I’ve put together. I haven’t charted the last## in this example.

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: ChangeMeTemp
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: >-
        /*--------------- 


        ------------Find and Replace the following-----------

        --Description—




        --Existing Item--

        ChangeMeTemp

        --New Items--

        Group ChangeMeTempChart "Template Temperature Group V1.1" <charticon> [TempTemplate] 

        Number ChangeMeTempPeriod "Template Temperature Period V1.1" <temperature> [TempTemplate] 

        Number ChangeMeTempAve "Template Temperature Average V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 

        Number ChangeMeTempMax "Template Temperature Maximum V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 

        Number ChangeMeTempMin "Template Temperature Minimum V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 

        Number ChangeMeTemplast24h "Template Temperature Last 24 Hrs V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]

        Number ChangeMeTemplast7d "Template Temperature Last 7 Days V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]

        Number ChangeMeTemplast30d "Template Temperature Last 30 Days V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]


        --Persistence--

        ChangeMeTemp,ChangeMeTempAve,ChangeMeTempMax,ChangeMeTempMin,ChangeMeTemplast24h,ChangeMeTemplast7d,ChangeMeTemplast30d


        --Sitemap--

        Text item=ChangeMeTemp //label="Template  Temperature [%.1f °F]" //enable if needed

        Text item=ChangeMeTempAve //label="Template Average Temperature [%.1f °F]" //enable if needed

        Text item=ChangeMeTempMax //label="Template Maximum Temperature [%.1f °F]" //enable if needed

        Text item=ChangeMeTempMin //label="Template Minimum Temperature [%.1f °F]" //enable if needed

        Text item=ChangeMeTemplast24h //label="Template Temperature Last 24 Hrs V1.1 [%.1f °F]" //enable if needed

        Text item=ChangeMeTemplast7d //label="Template Temperature Last 7 Days V1.1 [%.1f °F]" //enable if needed

        Text item=ChangeMeTemplast30d //label="Template Temperature Last 30 Days V1.1 [%.1f °F]" //enable if needed


        Switch item=ChangeMeTempPeriod label="Chart Period" mappings=[0="Hour", 1="Day", 2="Week", 3="Month"]

        Chart item=ChangeMeTempChart period=h refresh=6000 visibility=[ChangeMeTempPeriod==0, ChangeMeTempPeriod=="Uninitialized"]

        Chart item=ChangeMeTempChart period=D refresh=30000 visibility=[ChangeMeTempPeriod==1]

        Chart item=ChangeMeTempChart period=W refresh=30000 visibility=[ChangeMeTempPeriod==2]

        Chart item=ChangeMeTempChart period=M refresh=30000 visibility=[ChangeMeTempPeriod==3]


        --Rules--

        Template Temperature Stats V1.1


        ----------------*/


        val start = ZonedDateTime.now().with(LocalTime.MIDNIGHT)

        val debug = Debug.getStateAs(OnOffType)


        //var Number temp = ChangeMeTemp.averageSince(now.minusHours(4)) //enable for Ave

        //ChangeMeTempAve.postUpdate(temp) //enable for Ave

        //postUpdate(ChangeMeTempMax, ChangeMeTemp.maximumSince(start).state) //enable for Max

        //postUpdate(ChangeMeTempMin, ChangeMeTemp.minimumSince(start).state) //enable for Min

        //postUpdate(ChangeMeTempMin, ChangeMeTemp.minimumSince(start).state) //enable for Min

        //ChangeMeTemplast24h.postUpdate(ChangeMeTemp.deltaSince(now.minusHours(24)))  //enable for Min

        //ChangeMeTemplast7d.postUpdate(ChangeMeTemp.deltaSince(now.minusDays(7)))  //enable for Min

        //ChangeMeTemplast30d.postUpdate(ChangeMeTemp.deltaSince(now.minusDays(30)))  //enable for Min
          
        if (debug == ON)
          {
          //logInfo("Template Temperature Stats Rule", "Average temperature udpated to " + temp) //enable for Ave
          //logInfo("Template Temperature Stats Rule", "Maximum temperature udpated to " + ChangeMeTempMax.state) //enable for Max
          //logInfo("Template Temperature Stats Rule", "Minimum temperature udpated to " + ChangeMeTempMin.state) //enable for Min
          //logInfo("Template Temperature Stats Rule", "Last 24h temperature udpated to " + ChangeMeTemplast24h.state) //enable for Min
          //logInfo("Template Temperature Stats Rule", "Last 24h temperature udpated to " + ChangeMeTemplast7d.state) //enable for Min
          //logInfo("Template Temperature Stats Rule", "Last 24h temperature udpated to " + ChangeMeTemplast30d.state) //enable for Min


         
        }
    type: script.ScriptAction

1 Like

You need to have one main item, that is linked to your channel: e.g. runtime or temperature

In addition you need to have another item of the same type, that will store your calculation (e g. Min, max, avg, delta …)

On any update of the main item your rule can run and you can calculate e.g. avg temperature in the last 24 hours or in the case of runtime, where your main items value is always increasing, you can use deltaSince to only get the runtime of the last 24 hours (could also be used for energy metering if you know the total energy consumption).

In this example I’m charting the gas price: the green line is representing the item with the actual gas price, but the blue line is linked to an item storing a 7 day average, what will help me to understand trends and check if current gas price is cheep or expensive

Another example where I’m using this method is measuring energy consumption.
While my thing is only reporting current and total consumption, I’m not interested in total consumption at all, but more in daily (=last 24h) energy consumption.
For your reference I have put total (blue line) and last 24h consumption (green line) together in a chart

1 Like

I cleaned up the Rules from my previous post.

Trigger is when ChangeMeTemp has changed.

/*--------------- 

------------Find and Replace the following-----------
--Description—
Template = Short Description of Item for stats
ChangeMeTemp = item to pull stats from


--Existing Item--
ChangeMeTemp
--New Items--
Group ChangeMeTempChart "Template Temperature Group V1.1" <charticon> [TempTemplate] 
Number ChangeMeTempPeriod "Template Temperature Period V1.1" <temperature> [TempTemplate] 
Number ChangeMeTempAve "Template Temperature Average V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 
Number ChangeMeTempMax "Template Temperature Maximum V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 
Number ChangeMeTempMin "Template Temperature Minimum V1.1" <temperature> (ChangeMeTempChart) [TempTemplate] 
Number ChangeMeTemplast24h "Template Temperature Last 24 Hrs V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]
Number ChangeMeTemplast7d "Template Temperature Last 7 Days V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]
Number ChangeMeTemplast30d "Template Temperature Last 30 Days V1.1" <temperature> (ChangeMeTempChart) [TempTemplate]

--Persistence--
ChangeMeTemp,ChangeMeTempAve,ChangeMeTempMax,ChangeMeTempMin,ChangeMeTemplast24h,ChangeMeTemplast7d,ChangeMeTemplast30d

--Sitemap--
Text item=ChangeMeTemp //label="Template  Temperature [%.1f °F]" //enable if needed
Text item=ChangeMeTempAve //label="Template Average Temperature [%.1f °F]" //enable if needed
Text item=ChangeMeTempMax //label="Template Maximum Temperature [%.1f °F]" //enable if needed
Text item=ChangeMeTempMin //label="Template Minimum Temperature [%.1f °F]" //enable if needed
Text item=ChangeMeTemplast24h //label="Template Temperature Last 24 Hrs V1.1 [%.1f °F]" //enable if needed
Text item=ChangeMeTemplast7d //label="Template Temperature Last 7 Days V1.1 [%.1f °F]" //enable if needed
Text item=ChangeMeTemplast30d //label="Template Temperature Last 30 Days V1.1 [%.1f °F]" //enable if needed

Switch item=ChangeMeTempPeriod label="Chart Period" mappings=[0="Hour", 1="Day", 2="Week", 3="Month"]
Chart item=ChangeMeTempChart period=h refresh=6000 visibility=[ChangeMeTempPeriod==0, ChangeMeTempPeriod=="Uninitialized"]
Chart item=ChangeMeTempChart period=D refresh=30000 visibility=[ChangeMeTempPeriod==1]
Chart item=ChangeMeTempChart period=W refresh=30000 visibility=[ChangeMeTempPeriod==2]
Chart item=ChangeMeTempChart period=M refresh=30000 visibility=[ChangeMeTempPeriod==3]

--Rules--
Template Temperature Stats V1.1

----------------*/

//val debug = Debug.getStateAs(OnOffType)
//var Number temp = ChangeMeTemp.averageSince(now.minusHours(4)) //enable for Ave
//val start = ZonedDateTime.now().with(LocalTime.MIDNIGHT) //enable for Max/Min
//val last24h = ChangeMeTemp.deltaSince(now.minusHours(24)) //enable for 24 hours
//val last7d = ChangeMeTemp.deltaSince(now.minusHours(7*24)) //enable for 7 days
//val last30d = ChangeMeTemp.deltaSince(now.minusHours(30*24)) //enable for 30 days
//
//ChangeMeTempAve.postUpdate(temp) //enable for Ave
//postUpdate(ChangeMeTempMax, ChangeMeTemp.maximumSince(start).state) //enable for Max
//postUpdate(ChangeMeTempMin, ChangeMeTemp.minimumSince(start).state) //enable for Min
//ChangeMeTemplast24h.postUpdate(last24h as Number) //enable for 24 hours
//ChangeMeTemplast7d.postUpdate(last7d as Number) //enable for 7 days
//ChangeMeTemplast30d.postUpdate(last30d as Number) //enable for 30 days
  
if (debug == ON)
{
  //logInfo("Template Temperature Stats Rule", "Average temperature udpated to " + temp) //enable for Ave
  //logInfo("Template Temperature Stats Rule", "Maximum temperature udpated to " + ChangeMeTempMax.state) //enable for Max
  //logInfo("Template Temperature Stats Rule", "Minimum temperature udpated to " + ChangeMeTempMin.state) //enable for Min
  //logInfo("Template Temperature Stats Rule", "Last 24 Hrs udpated to " + ChangeMeTemplast24h.state) //enable for Min
  //logInfo("Template Temperature Stats Rule", "Last 7 Days udpated to " + ChangeMeTemplast7d.state) //enable for Min
  //logInfo("Template Temperature Stats Rule", "Last 30 Days udpated to " + ChangeMeTemplast30d.state) //enable for Min  
}

The only active code in this rule seems to be -

which I’d expect to fail, as variable debug is undeclared.
You understand // makes lines into non-functional comments?

The intent is to enable only the values you want. All lines should be commented at the end with

//enable for ???

I would not use “has changed” as trigger, as the rule will only run when an items state is currently changing.

But if you e.g. want to know the last 30days delta or average, this can change more frequently as it’s dependent on the items state in the past and not on any current change.

Therefore I would use “on update” instead of “on change” as trigger

1 Like

Hey Matthias,

What I’m trying to track is actual runtime of the HVAC system NOT the temperatures. Your code example above is not showing me how to track something like that.

For example, Ecobee’s have a string value for equipment status of some of these items below.

auxHeat1,fan
auxCool1,fan
auxHeat1
auxCool1

What I want to do is track “how long” equipment status has a value of any of these values in minutes, hours, days, weeks, etc.

I appreciate and understand what your doing with temperature trends.

Best, Jay

It works the same. Just use runtime instead of ChangeMeTemp and enable the following.

22-01-25 UPDATED for errors in postUpdate

//val debug = Debug.getStateAs(OnOffType)
//var Number temp = ChangeMeTemp.averageSince(now.minusHours(4)) //enable for Ave
//val start = ZonedDateTime.now().with(LocalTime.MIDNIGHT) //enable for Max/Min
val last24h = ChangeMeTemp.deltaSince(now.minusHours(24)) //enable for 24 hours
val last7d = ChangeMeTemp.deltaSince(now.minusHours(7*24)) //enable for 7 days
val last30d = ChangeMeTemp.deltaSince(now.minusHours(30*24)) //enable for 30 days
//
//ChangeMeTempAve.postUpdate(temp) //enable for Ave
//postUpdate(ChangeMeTempMax, ChangeMeTemp.maximumSince(start).state) //enable for Max
//postUpdate(ChangeMeTempMin, ChangeMeTemp.minimumSince(start).state) //enable for Min
ChangeMeTemplast24h.postUpdate(last24h as Number) //enable for 24 hours
ChangeMeTemplast7d.postUpdate(last7d as Number) //enable for 7 days
ChangeMeTemplast30d.postUpdate(last30d as Number) //enable for 30 days

That’s of cause something different, if you want to track the runtime based on specifics criterias (e.g. only if state has a specific value).

I have not done something similar before, but my spontaneous thought is, that you need to build a runtime item by yourself, that will store the total runtime that’s relevant for you and have some rules that will update this runtime item accordingly.

E.g. if you only want to measure the runtime while state = ON, than I would do a rule triggered on change and this rule will store current timestamp (time when your state will change from OFF to ON).

Once the state will switch to OFF the rule will run again, calculates the runtime (= ending timestamp - beginning timestamp) and will add this runtime to your runtime item.

Depending on how often your device will switch the state it could also make sense to run this calculation more frequently.

I cleaned up the code a bit and removed temperature to make it more applicable across the board.

Put this in the script application/vnd.openhab.dsl.rule and edit as you see fit.

    configuration:
      itemName: ChangeMeItem
    type: core.ItemStateUpdateTrigger
/*--------------- 

------------Find and Replace the following-----------
--Description—
Template = Short Description of Item for stats
ChangeMeItem = item to pull stats from
ChangeMeIcon = Your icon of choice

I use Notepad ++ to perform the find and replace.

--Existing Item--
ChangeMeItem
--New Items--
Group ChangeMeItemChart "Template Group V1.1" <ChangeMeIcon> (ChangeMeItemChart)
Number ChangeMeItemPeriod "Template Period V1.1" <ChangeMeIcon> //Required for chart below
Number ChangeMeItemAve "Template Average V1.1" <ChangeMeIcon> (ChangeMeItemChart)
Number ChangeMeItemMax "Template Maximum V1.1" <ChangeMeIcon> (ChangeMeItemChart)
Number ChangeMeItemMin "Template Minimum V1.1" <ChangeMeIcon> (ChangeMeItemChart)
Number ChangeMeItemlast24h "Template Last 24 Hrs V1.1" <ChangeMeIcon> //Not Charted below
Number ChangeMeItemlast7d "Template Last 7 Days V1.1" <ChangeMeIcon> //Not Charted below
Number ChangeMeItemlast30d "Template Last 30 Days V1.1" <ChangeMeIcon>//Not Charted below

--Persistence--
ChangeMeItem,ChangeMeItemAve,ChangeMeItemMax,ChangeMeItemMin,ChangeMeItemlast24h,ChangeMeItemlast7d,ChangeMeItemlast30d

--Sitemap--
Text item=ChangeMeItem //label="Template  [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemAve //label="Template Average [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemMax //label="Template Maximum [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemMin //label="Template Minimum [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemlast24h //label="Template Last 24 Hrs V1.1 [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemlast3d //label="Template Last 3 Days V1.1 [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemlast7d //label="Template Last 7 Days V1.1 [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemlast14d //label="Template Last 14 Days V1.1 [%.1f °F]" //enable if needed //set unit [%.1f °F]
Text item=ChangeMeItemlast30d //label="Template Last 30 Days V1.1 [%.1f °F]" //enable if needed //set unit [%.1f °F]

Switch item=ChangeMeItemPeriod label="Chart Period" mappings=[0="Hour", 1="Day", 2="Week", 3="Month"]
Chart item=ChangeMeItemChart period=h refresh=6000 visibility=[ChangeMeItemPeriod==0, ChangeMeItemPeriod=="Uninitialized"]
Chart item=ChangeMeItemChart period=D refresh=30000 visibility=[ChangeMeItemPeriod==1]
Chart item=ChangeMeItemChart period=W refresh=30000 visibility=[ChangeMeItemPeriod==2]
Chart item=ChangeMeItemChart period=M refresh=30000 visibility=[ChangeMeItemPeriod==3]

Chart periods: h, 4h, 8h, 12h, D, 3D, W, 2W, M, 2M, 4M, Y

--Rules--
Template Stats V1.1

----------------*/

val debug = Debug.getStateAs(OnOffType)
//var Number temp = ChangeMeItem.averageSince(now.minusHours(4)) //enable for Ave
//val start = ZonedDateTime.now().with(LocalTime.MIDNIGHT) //enable for Max/Min
//val last24h = ChangeMeItem.deltaSince(now.minusHours(24)) //enable for 24 hours
//val last3d = ChangeMeItem.deltaSince(now.minusHours(3*24)) //enable for 3 days
//val last7d = ChangeMeItem.deltaSince(now.minusHours(7*24)) //enable for 7 days
//val last14d = ChangeMeItem.deltaSince(now.minusHours(14*24)) //enable for 14 days
//val last30d = ChangeMeItem.deltaSince(now.minusHours(30*24)) //enable for 30 days
//
//ChangeMeItemAve.postUpdate(temp) //enable for Ave
//postUpdate(ChangeMeItemMax, ChangeMeItem.maximumSince(start).state) //enable for Max
//postUpdate(ChangeMeItemMin, ChangeMeItem.minimumSince(start).state) //enable for Min
//ChangeMeItemlast24h.postUpdate(last24h as Number) //enable for 24 hours
//ChangeMeItemlast3d.postUpdate(last3d as Number) //enable for 3 days
//ChangeMeItemlast7d.postUpdate(last7d as Number) //enable for 7 days
//ChangeMeItemlast7d.postUpdate(last14d as Number) //enable for 14 days
//ChangeMeItemlast30d.postUpdate(last30d as Number) //enable for 30 days
  
if (debug == ON)
{
  //logInfo("Template Stats Rule", "Average udpated to " + temp) //enable for Ave
  //logInfo("Template Stats Rule", "Maximum udpated to " + ChangeMeItemMax.state) //enable for Max
  //logInfo("Template Stats Rule", "Minimum udpated to " + ChangeMeItemMin.state) //enable for Min
  //logInfo("Template Stats Rule", "Last 24 Hrs udpated to " + ChangeMeItemlast24h.state) //enable for Min
  //logInfo("Template Stats Rule", "Last 3 Days udpated to " + ChangeMeItemlast3d.state) //enable for Min
  //logInfo("Template Stats Rule", "Last 7 Days udpated to " + ChangeMeItemlast7d.state) //enable for Min
  //logInfo("Template Stats Rule", "Last 14 Days udpated to " + ChangeMeItemlast14d.state) //enable for Min
  //logInfo("Template Stats Rule", "Last 30 Days udpated to " + ChangeMeItemlast30d.state) //enable for Min  
}

I tried the logic above with an Ecobee but could not get it to work using different triggers and items to graph. I ended up creating my own simple logic for now.

Using RRDJ every minute and using a simple charting widget for 1d, 2d and 3d.

3 = HVAC Heat/Cool On | 2 = Fan Only | 1 = Off

As you can see the HVAC is running a lot, it’s cold in Chicago.

rule "Calculate Ecobee MF_equipmentStatus Period for Heating and Cooling"
	when
    	Item MF_equipmentStatus received update
	then
	
		if (systemStarted.state != ON && gInternet.state == ON) {	
	
			logInfo("tAlive","CREATING Generic 247 Timer!")
			createTimer(now().plusNanos(5000), [ |
	
				var EcobeeMF = getThingStatusInfo("ecobee:thermostat:3bbffee0f2")
				if ((systemStarted.state != ON && EcobeeMF !== null) && (EcobeeMF.getStatus().toString() == "ONLINE")) {	

					if (systemStarted.state != ON && (MF_equipmentStatus.state.toString() == 'auxHeat1,fan' || MF_equipmentStatus.state.toString() == 'auxCool1,fan' || MF_equipmentStatus.state.toString() == 'auxHeat1' || MF_equipmentStatus.state.toString() == 'auxCool1' )) {	

						MFRuntimelast24h.postUpdate(3) 				// heat or cool on		
						return;
					}

					if (systemStarted.state != ON && MF_equipmentStatus.state.toString() == 'fan' ) {	

						MFRuntimelast24h.postUpdate(2) 				// fan only				
						return;
						
					} else {
					
						MFRuntimelast24h.postUpdate(1) 				// off									
					}
				}	
			])	
		}	
end

Ideally, it would be nice if someone had a script to calculate number of hours/minutes MFRuntimelast24h.state was in each category (3, 2, 1) using the RRDJ history.

Best, Jay

What if you ran a simple Rule to increment the MFRuntimelast24h every minute based on its state?

ItemStateToCount = Item that is to be incremented every minute that it is in a particular state. On in this example.
ItemRuntime = The incremented time that for every minute that the ItemStateToCount is ON

configuration: {}
triggers:
  - id: "1"
    configuration:
      cronExpression: " 0 0/1 * 1/1 * ? * "
    type: timer.GenericCronTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: ItemStateToCount
      state: ON
      operator: =
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: |-
        if(ItemRuntime.state == NULL) {
        ItemRuntime.postUpdate(0)
        }
        else{
        val Number varCount = ItemRuntime.state as Number 
        ItemRuntime.postUpdate(varCount + 1)
        }
    type: script.ScriptAction

FWIW- I use a hybrid with OH, mysql and Excel(mysql for excel).
I have two zones, up and main and have a persisted gHeater group that is either 0,1,2 and use the time stamp to calculate totals. I try to maximize both zones being on at the same time by adjusting setpoints, so summarize that too.
The excel output looks like this;

Separately I have the furnace and AC on Aeotec energy monitors and track daily KWH, but that is another story.

Bob