Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voltage-Compensated Dummy Load Power Measurement #2810

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

RubenKelevra
Copy link
Contributor

@RubenKelevra RubenKelevra commented Dec 15, 2024

Current State

  • Currently, we measure the power consumption of the dummy load once.
  • Users are instructed to wait for a 2-hour preheating period before taking measurements. This was a cautious, estimated guideline.
  • The system assumes that the dummy load's power consumption remains constant throughout.

Problem Statement

  • This assumption is flawed for resistive loads, as their power consumption varies with grid voltage.
  • While two measurements (before and after the main measurement) could help account for slow drift, it does not fully address how voltage is regulated in the power grid.

Proposed Solution

This PR introduces a method to adjust dummy load power consumption measurements based on grid voltage.

Technical Explanation

Resistive loads exhibit varying power consumption depending on grid voltage. In Germany, the allowed voltage range is 230 V ± 10%, meaning a 4800 Ω resistor's power consumption can range between:

  • 8.93 W @ 207 V
  • 13.34 W @ 253 V

Although voltage fluctuations are typically minor in residential areas over short periods, long-term measurements (spanning several hours) are affected by slow voltage drifts. These drifts can be suddenly corrected by transformers, introducing a "sawtooth" noise in the readings.

To mitigate this, the following approach is proposed:

  1. Resistive Measurement: Instead of storing the power consumption of the dummy load, we will record its resistance.
  2. Voltage Compensation: On each device measurement, the grid voltage will also be recorded. The dummy load's power consumption will then be calculated dynamically based on this voltage.
  3. Long-Term Measurements:
    Rather than relying on a single measurement from the power meter, we now collect voltage and power consumption data over a 10-minute period. Using this data, we calculate the resistor's value and average it for improved accuracy.
  4. Preheat Detection:
    To automate the determination of resistor stability, we leverage the same 10-minute data collection. During this period, we capture 20 consecutive 30-second averages and assess the stability by comparing the average drift of the first and second sets of ten measurements.
    • If both sets drift in the same direction, we assume the resistor is still heating and has not yet stabilized.
    • This eliminates the need for users to manually assess stabilization, ensuring consistent and reliable measurements.

Advantages

  • Provides more accurate dummy load compensation.
  • Reduces noise in long-term power measurements caused by grid voltage fluctuations.
  • Avoids user errors in determining if the resistor is already sufficiently heated.

Limitations

  • Hardware Compatibility:

    • Measurements with a dummy load will not work with equipment that cannot record voltage (e.g., Shelly Gen1 devices).
    • Devices and integrations (e.g., Tuya, Tasmota, Kasa, MyStrom) are unsupported due to a lack of test hardware.
  • Home Assistant (HA) Entity Naming:

    • This implementation assumes HA entity names follow the pattern:
      • pattern_name_power
      • pattern_name_voltage.
  • OCR Exclusion:

    • Optical Character Recognition is not supported due to inherent complexities.

Accuracy of Measurements

To validate the accuracy of this approach, I conducted three test runs in my setup. In each instance, the script successfully determined whether the resistors were sufficiently preheated.

The measured resistances and corresponding power consumptions were as follows:

  • Run 1: 4785.41 Ω → 11.0544 W @ 230 V
  • Run 2: 4782.00 Ω → 11.0623 W @ 230 V
  • Run 3: 4787.99 Ω → 11.0485 W @ 230 V

The variation across these runs is minimal, with the power differences staying well below the 0.1 W resolution of Gen3 Shelly sensors.

Conclusion

This PR addresses inaccuracies in resistive load power measurement by compensating for grid voltage fluctuations. Although this approach introduces some limitations, it enhances measurement reliability for supported hardware.


Due to size and complexity, I consider this a draft atm.

@github-actions github-actions bot added the measure-tool Measure tooling related issue label Dec 15, 2024
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch 2 times, most recently from 13aacb3 to 89d92b9 Compare December 15, 2024 23:33
@RubenKelevra
Copy link
Contributor Author

RubenKelevra commented Dec 15, 2024

@bramstroker I just checked with the suggested method of using an incandescent light bulb as dummy load. Granted, I don't have one, but I had one of these regular shaped light bulbs with a Halogen Bulb inside.

Waiting one minute to let it heat up, and the script will be happy on it's first try and determines, that the dummy load has sufficiently heated. So they work fine as dummy load.


Measuring a dummy load with the script and save the resistance after it has stabilized. Then abort and wait two hours and let it measure, but do not attach a light will lead to ±0.02 W noise on the standard settings (SAMPLE_COUNT=2), with 4 lights. So the noise with one light is ±0.08 W (worst actual measurement was 0.068 W). That's much better than I had expected.

@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch 2 times, most recently from 9b4d904 to a180936 Compare December 16, 2024 01:14
@bramstroker
Copy link
Owner

@RubenKelevra thanks. Will review in a few days.

@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from a180936 to 2edbbc2 Compare December 16, 2024 17:56
Copy link
Owner

@bramstroker bramstroker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job! just did a first quick glance. here are my first comments. Will do a more throrough review later on

utils/measure/measure/util/measure_util.py Show resolved Hide resolved
utils/measure/measure/util/measure_util.py Show resolved Hide resolved
utils/measure/measure/util/measure_util.py Outdated Show resolved Hide resolved
utils/measure/measure/util/measure_util.py Outdated Show resolved Hide resolved
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch 4 times, most recently from 4ec7baa to f2cc9f5 Compare December 19, 2024 21:34
@RubenKelevra
Copy link
Contributor Author

Probably missed a couple of things now, and no testing yet. Will have another look at this tomorrow or at the weekend

@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 356ac0f to 037f68f Compare December 23, 2024 19:10
@RubenKelevra
Copy link
Contributor Author

Testing complete. Should work now for HA and Shelly V2/3 devices. It reads the new .env var successfully, does auto detect and if this fails asks the user to select the voltagemeter.

@RubenKelevra RubenKelevra marked this pull request as ready for review December 23, 2024 19:20
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 7174430 to 9360a7d Compare December 23, 2024 19:43
@bramstroker
Copy link
Owner

@RubenKelevra before merging, ruff linting has to be fixed, see failed action.
Also tests are failing. Let me know if you need help with that.

@RubenKelevra
Copy link
Contributor Author

@RubenKelevra before merging, ruff linting has to be fixed, see failed action. Also tests are failing. Let me know if you need help with that.

Yeah ruff fails for the fixmes, and makes 2-3 suggestions which IMHO reduce readability. You really want those changes to be made?

Tests I'll look later.

Copy link
Owner

@bramstroker bramstroker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my suggestions how to both simplify the code and make ruff happy.
For the fix me you can add ignores # noqa: FIX001, that's the annotation you can use to ignore ruff errors.
Also see https://docs.astral.sh/ruff/linter/#error-suppression

utils/measure/measure/util/measure_util.py Outdated Show resolved Hide resolved
utils/measure/measure/powermeter/shelly.py Outdated Show resolved Hide resolved
@RubenKelevra

This comment was marked as outdated.

@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 94ed399 to 8c239b8 Compare December 24, 2024 15:39
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 8c239b8 to 1c5dc61 Compare December 24, 2024 15:43
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 74a8de4 to 5c62fcd Compare December 24, 2024 15:44
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 690119d to 7ae0c80 Compare December 24, 2024 17:23
@RubenKelevra
Copy link
Contributor Author

RubenKelevra commented Dec 24, 2024

@bramstroker you need to take a look at the test. Looks to me like the max retries is not read from the config when running the test which is why the default of 5 is not set, and it's None. Which is why it before 7ae0c80 just ran into RecursionError. The limit of 100 now works, but the logger message can't be printed, because the config option is None, which is not a number.

And yet, the measurement is failing for some reason, but I think the "None" config option needs to be fixed as well. :)

https://github.com/bramstroker/homeassistant-powercalc/actions/runs/12484551223/job/34842108258?pr=2810

@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch 2 times, most recently from f6d22a2 to 8398df2 Compare December 24, 2024 18:30
@RubenKelevra RubenKelevra force-pushed the voltage-compensation-for-dummy_load branch from 8398df2 to 4ab94d7 Compare December 24, 2024 19:25
@RubenKelevra
Copy link
Contributor Author

@bramstroker after adding a workaround for the self.config.max_retries is None in the test, it looks like test just encounters the new check for 0.00 W measurements I've added. So the test needs to be redesigned, I guess?

https://github.com/bramstroker/homeassistant-powercalc/actions/runs/12485085692/job/34843389826?pr=2810

@bramstroker
Copy link
Owner

Did have a quick look but the test is failing because it enters dummy load mode.

There is something not right with order of the questions, or the handling of these events.
It's difficult to test these console anyway, so that's why I only have one test currently testing this.
And all other tests are setting up mocked config, so the CLI is not prompting anything.

Screenshot 2024-12-24 at 20 28 09

Will have a further look later.

Comment on lines +278 to +291
if not (answers.get(QUESTION_DUMMY_LOAD, False) and isinstance(self.power_meter, HassPowerMeter)):
pass
elif self.power_meter.autodetect_voltage_entity(answers.get(QUESTION_POWERMETER_ENTITY_ID, None)):
# Autodetection succeeded
pass
else:
# Ask voltage question if autodetection failed
voltage_question = self.power_meter.get_voltage_question()
if voltage_question:
voltage_answer = self.ask_questions(voltage_question)
answers.update(voltage_answer)
else:
_LOGGER.error("Voltage Meter question result was empty")
exit(1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw I just notice there is several code added now specifically for powermeters, and even more specifically for the hass powermeter only.

There is specific extension point for this designed. See line 248.
Powermeter protocol has get_questions method where you can extend the questions with more questions in hass.py.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one still needs resolving.

@bramstroker
Copy link
Owner

I have the test fixed. Could you replace the test method with this?

def test_wizard(mock_config_factory) -> None:  # noqa: ANN001
    """Test the CLI wizard, can we actually select certain options"""
    mock_config = mock_config_factory(
        set_question_defaults=False,
    )
    measure = _create_measure_instance(
        mock_config,
        console_events=event_factory(
            # MEASURE_TYPE
            key.ENTER,
            # GENERATE_MODEL_JSON
            "y",
            # DUMMY_LOAD
            "n",
            # MODEL_NAME
            "a", key.ENTER,
            # MEASURE_DEVICE
            "a", key.ENTER,
            # COLOR_MODE
            key.DOWN, key.DOWN, key.ENTER,
            # GZIP
            key.ENTER,
            # MULTIPLE_LIGHTS
            "n",
        ),
    )

    with patch("builtins.input", return_value=""):
        measure.start()

    assert os.path.exists(os.path.join(PROJECT_DIR, "export/dummy/brightness.csv.gz"))
    assert os.path.exists(os.path.join(PROJECT_DIR, "export/dummy/model.json"))

@bramstroker
Copy link
Owner

Also replace line 242 of light.py from time.sleep(2) to time.sleep(self.config.sleep_time), that will make the full test also run instant.
Now it's blocking for 2 seconds.
I can also globally patch time.sleep probably, but this is a cleaner solution imo.

@bramstroker
Copy link
Owner

@RubenKelevra Did you see my comments? Are you able to do final rework, than we can merge.

@RubenKelevra
Copy link
Contributor Author

@bramstroker yes, just haven't had time yet. Maybe next year 😜

Guten Rutsch :)

@bramstroker
Copy link
Owner

No worries, take your time.

Gelukkig nieuwjaar ;-)

@RubenKelevra
Copy link
Contributor Author

I have the test fixed. Could you replace the test method with this?

Sure: dde0a80

@RubenKelevra
Copy link
Contributor Author

Also replace line 242 of light.py from time.sleep(2) to time.sleep(self.config.sleep_time), that will make the full test also run instant. Now it's blocking for 2 seconds. I can also globally patch time.sleep probably, but this is a cleaner solution imo.

Sure: f708bb3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
measure-tool Measure tooling related issue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants