beestat / app

The actual beestat app.
https://beestat.io
GNU General Public License v3.0
209 stars 16 forks source link

Fan runtime interpretation is sometimes wrong when HVAC controls fan #204

Open MJRantala opened 4 years ago

MJRantala commented 4 years ago

I hope I'm reading the detailed graph and data correctly. In my HVAC setup, there is a delay between the start of the furnace and when the blower kicks in. Once a certain temp is reached in the furnace, then the blower is turned on so as to not blow cold air for the first couple minutes. This delayed start of the fan is shown in the raw ecobee data that I download. However, in the runtime detail (and other places ?) - it appears that this staggered start of the fan isn't factored in (each heat cycle, the fan starts at the same time). This causes a slight misrepresentation of the data in the graph, but more importantly, it will be throwing of total run times, etc if used elsewhere.

If included a screenshot showing an example of the raw data on the left and the detailed graph data for that same cycle on the right

fandata_error

ziebelje commented 4 years ago

Is the delayed fan start a configuration setting on your ecobee or do you allow the HVAC to control the fan? If you let the HVAC do it, does the ecobee actually indicate properly when the fan started? I was under the impression that when the ecobee did not control the fan it would not know when the fan ran.

In any case, this is happening in beestat because I made the assumption that if the heat or air is on, the fan should be running.

Relevant code is here: https://github.com/beestat/app/blob/08403cedc64c97349d746c636b8042f0ae418930/api/runtime_thermostat.php#L447-L456

I may need to tweak this a bit.

MJRantala commented 4 years ago

Hey there. OK, yes, you're right, it has to do with the HVAC Control over the fan. I verified that I do have it set for HVAC Control. The only reason that the data downloaded from EB.com shows any fan run time data is because I have the Heat Dissipation setting at 360 (actually 350 on the ECOBEE screen)-so EB will run longer flushing out all the heat left if the system. Unfortunately, I hadn't noticed the fact that every cycle in the EB data, the fan run time was always 360. Also, if I would have thought about it a little more, the fan on the furnace usually kicks in within a minute or so of the furnace starting, and of course, that can't be calculated since the data isn't recorded by EB.

So, the BeeStat data and the graph built from the it (with your adjustment for this fan time) is correct. I downloaded the data from BeeStat, and yes, the fan adjusted times are included and it's accurate including any additional heat dissipation time that might be set.

Sorry about wasting your time, if I would have noticed a couple clues on the EB.com data I probably would have been able to answer this on my own. Either way, it's all good information to know. BTW, great work on BeeStat !! Very impressive and very helpful. So a big Thank You for your time and effort dedicated to it!! Many thanks, beestat report

Matt

PS as a side note, It's odd. On the EB screen to select who controls the fan, it does state that using HVAC would reduce the amount of cold air that is pushed through the system. Which makes sense, because the EB had control the the fan and furnace would turn on at the same time I'm assuming. It also says that it only applies to Gas/Electric heating systems-Which I have an oil furnace. I'll have to test it but I don't know why it wouldn't work the same on oil - unless if it means that an oil furnace heats up faster ? I don't know. More to investigate ! There's a lot of little quirks to the EB that can make it a challenge.

ziebelje commented 4 years ago

Yep, that all sounds about right. Guess there's nothing to do after all. Thanks for following through and figuring this one out!

MJRantala commented 4 years ago

Hello Again !! In regards to this fan scenario we discussed this past week. I didn't want to open up another ticket on this, since I'm really not sure how big of a deal this is for your project over all and whether or not you want to adjust for it-although it would probably be a fairly easy change. In the attached scenario, when I had the Heat Dis set for 360s, in your data, the 3rd row-you set the fan value to 225. However there was already 75s of data in that cell, to be really accurate, it should 225+75=300. In thinking about this, I guess the logic to account for this would depend on whether or not the HVAC is furnace or Ecobee controlled. Would it be simple to just do a check that if the FAN time > Heat time keep the FAN Time (for Ecobee Fan Controlled Setup), Otherwise Fan Time = Fan Time+Heat time (for HVAC Fan Controlled Setup)? Just a thought. Again, not a big deal for me, but wanted to let you know about. Thanks. Matt.

ziebelje commented 4 years ago

Yeah, you're right about that. I'm not sure the best way to handle that...I'll give it some more thought and see if there's an easy way to address this. Your logic might be correct but I'll need to test it to be sure.

joesc1 commented 4 years ago

I see something else wrong with the fan number on my data. I have a two stage furnace, but often the fan run time is shorter than the total time when both stages are shown. So for example I'd have a 15 min fan runtime, but when adding up the total heat time for first and second stage would be 17 mins. So I think you should definitely have that check to ensure fan run time isn't shorter than the heat time if there's more than one stage. Of course my HVAC controls the fan when heating since I have a variable speed fan.

ziebelje commented 4 years ago

Would it be simple to just do a check that if the FAN time > Heat time keep the FAN Time (for Ecobee Fan Controlled Setup), Otherwise Fan Time = Fan Time+Heat time (for HVAC Fan Controlled Setup)?

Unfortunately not. The first bit you described is what it does today with the max(fan, compressor, ...). The second part won't work because I cannot guarantee that all the data I receive has fan_control = off.

For example, let's say I sync a week of data. The first half of the week you had ecobee fan_control = on. Then the second part of the week you had ecobee fan_control = off. Since this setting is global and not attached to each row, I would have to assume all the data has fan_control off. Then the problem is that I would be doing Fan Time + Heat time for data when fan_control was on, thus double-counting the fan runtime for that range.

I've actually written a bunch of tests to try and figure this out but I always run into problems. I'm OK with it not being perfect since this is just trying to fill in missing data, but I'd like it to be as close as I can get.

Here are the tests if you're interested. My current strategy is to try and determine fan_control on a per_row basis (I can't guarantee I will have any context when syncing so I can't look at other rows). I can usually determine it but when the system fan_control is off and heat_dissipation is on then it can break.

Still working on this.

console.clear();
var tests = [
  // Test 1: All 0
  {
    'dissipation': true,
    'fan_control': false,
    'rows': [
      {'fan': 0,  'compressor_1': 0,   'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
    ],
    'results': [0]
  },
  // Test 2: Compressor running for 300; fan should be 300
  {
    'dissipation': true,
    'fan_control': false,
    'rows': [
      {'fan': 0,  'compressor_1': 300, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
    ],
    'results': [300]
  },
  // Test 3: Compressor running for 200, dissipation fan for 50; fan should be 250
  {
    'dissipation': true,
    'fan_control': false,
    'rows': [
      {'fan': 50, 'compressor_1': 200, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
    ],
    'results': [250]
  },
  // Test 4: Fan control used to be on, but was off when the data was obtained
  {
    'dissipation': true,
    'fan_control': false,
    'rows': [
      {'fan': 10, 'compressor_1': 10, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 300, 'compressor_1': 300, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 300, 'compressor_1': 50, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 0, 'compressor_1': 0, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},

      {'fan': 0, 'compressor_1': 10, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 0, 'compressor_1': 300, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 250, 'compressor_1': 50, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
      {'fan': 0, 'compressor_1': 0, 'compressor_2': 0, 'auxiliary_heat_1': 0, 'auxiliary_heat_2': 0, 'accessory': 0},
    ],
    'results': [10,300,300,0,10,300,300,0]
  },

  // Test 5: Same as Test 4, but set fan and compressor runtime to be the same.
  // TODO
];

tests.forEach(function(test, i) {

  var test_number = (i + 1);
  console.log('Checking test ' + test_number);

  var actual_fans = [];
  test.rows.forEach(function(row) {
    // Fails test 2
    // actual_fans.push(row.fan);

    // Fails test 3
    // actual_fans.push(Math.max(
    //   row.compressor_1,
    //   row.compressor_2,
    //   row.auxiliary_heat_1,
    //   row.auxiliary_heat_2,
    //   row.accessory,
    // ));

    // Fails test 4 because fan control was on, then turned off.
    // if(test.fan_control === false) {
    //   actual_fans.push(Math.max(
    //     row.compressor_1,
    //     row.compressor_2,
    //     row.auxiliary_heat_1,
    //     row.auxiliary_heat_2,
    //     row.accessory,
    //   ) + row.fan);
    // } else {
    //   actual_fans.push(row.fan);
    // }

    // Determine fan_control on a per_row basis.
    // Fails test 4 because fan = 300, compressor_1 = 50 detects as fan_control false, they get summed, then total is wrong.
    var fan_control;
    var max_fan = Math.max(
      row.compressor_1,
      row.compressor_2,
      row.auxiliary_heat_1,
      row.auxiliary_heat_2,
      row.accessory,
    );
    if(row.fan != max_fan) {
      fan_control = false;
    } else {
      fan_control = true;
    }
    // console.table([row.fan, max_fan, fan_control])
    console.log(fan_control);

    if(fan_control === false) {
      actual_fans.push(Math.max(
        row.compressor_1,
        row.compressor_2,
        row.auxiliary_heat_1,
        row.auxiliary_heat_2,
        row.accessory,
      ) + row.fan);
    } else {
      actual_fans.push(row.fan);
    }
  });

  console.log(test.results);
  console.log(actual_fans);

  actual_fans.forEach(function(actual_fan, j) {
    if(actual_fan !== test.results[j]) {
      console.error('Failed Test ' + test_number);
    } else {
      console.log('Passed Test ' + test_number);
    }
  });
});