Skip to content

Fix rain count sensors' state class of Ecowitt#158204

Merged
joostlek merged 9 commits intohome-assistant:devfrom
upsuper-forks:ecowitt-rain-state-class
Jan 5, 2026
Merged

Fix rain count sensors' state class of Ecowitt#158204
joostlek merged 9 commits intohome-assistant:devfrom
upsuper-forks:ecowitt-rain-state-class

Conversation

@upsuper
Copy link
Contributor

@upsuper upsuper commented Dec 7, 2025

Proposed change

Ecowtt products produce many rain count sensors, there are largely three types:

  • Total sensors - lifetime accumulating
  • Resetting sensors - resetting to zero in certain condition, e.g. daily or after 24h no rain
  • Rolling window sensors - total for the last X period

Total sensors and resetting sensors should have state class of TOTAL_INCREASING, and the rolling window ones should have MEASUREMENT as their state class correspondingly.

Previously, the code only counted hourly sensors as rolling window sensors, and #155321 was raised because 24h sensors are also rolling window but the code didn't take that into consideration.

As described in #157882 (comment), #155358 by @ogruendel was raised to fix the above issue, however, it was done in a wrong approach by changing all resetting sensors to TOTAL, and subsequent PRs #155812 and #157409 are heading down the wrong way further by completely removing the state class, causing long-term statistics for those resetting sensors to be deleted.

This PR reverts the three aforementioned PRs, and tries to assign the right state class for all the sensors.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Copilot AI review requested due to automatic review settings December 7, 2025 21:37
@upsuper upsuper requested a review from pvizeli as a code owner December 7, 2025 21:37
@home-assistant
Copy link

home-assistant bot commented Dec 7, 2025

Hey there @pvizeli, mind taking a look at this pull request as it has been labeled with an integration (ecowitt) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of ecowitt can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign ecowitt Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes incorrect state class assignments for Ecowitt rain count sensors by reverting three previous PRs and implementing proper logic to distinguish between sensor types. Rain sensors are categorized as: total/resetting sensors (TOTAL_INCREASING) and rolling window sensors (MEASUREMENT). The fix adds default TOTAL_INCREASING state class to rain count sensor descriptions, then overrides it to MEASUREMENT for sensors matching a rolling window pattern (hourly, 24h, and piezo variants).

Key changes:

  • Adds regex pattern to identify rolling window rain sensors (hourly, last24h, and piezo variants)
  • Sets TOTAL_INCREASING as default state class for RAIN_COUNT_MM and RAIN_COUNT_INCHES sensor descriptions
  • Implements logic to override state class to MEASUREMENT for rolling window sensors during sensor setup

Comment on lines 298 to 303
if _ROLLING_WINDOW_RAIN_COUNT_SENSOR.fullmatch(sensor.key):
description = dataclasses.replace(
description,
state_class=SensorStateClass.TOTAL_INCREASING,
state_class=SensorStateClass.MEASUREMENT,
)

Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

This logic for detecting and overriding state classes for rolling window sensors lacks test coverage. Given the critical nature of state class assignments for long-term statistics (as mentioned in the PR description), tests should be added to verify:

  1. Rolling window sensors (matching the regex) correctly get MEASUREMENT state class
  2. Non-rolling window rain count sensors correctly retain TOTAL_INCREASING state class
  3. The regex pattern matches all expected sensor key formats (hourlyrainmm, hourlyrainin, last24hrainmm, last24hrainin, hrain_piezo variants, etc.)

Consider adding tests in tests/components/ecowitt/test_sensor.py following the Home Assistant testing patterns with fixtures and snapshots.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

For sure ecowitt tests are extremely limited!
I strongly suggest to add snapshot tests (maybe in a preliminary PR?) to ensure this is properly tested and follow-up PRs do not accidentaly mess it up.

@il77781
Copy link

il77781 commented Dec 8, 2025

I suggest using the SensorStateClass.MEASUREMENT everywhere as it is more correct (in my opinion).

@frenck
Copy link
Member

frenck commented Dec 8, 2025

Also related #158070

@frenck
Copy link
Member

frenck commented Dec 8, 2025

Assigning @sairon for review.

@frenck frenck requested a review from sairon December 8, 2025 07:54
@frenck
Copy link
Member

frenck commented Dec 8, 2025

I suggest using the SensorStateClass.MEASUREMENT everywhere as it is more correct (in my opinion).

That one may not be used on aggregations, thus not a valid suggestion; see also our developer documentation.

@BJReplay
Copy link

BJReplay commented Dec 8, 2025

This will also fix #157903

Copy link
Member

@sairon sairon left a comment

Choose a reason for hiding this comment

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

To give some context, when I opened my PR, it was aimed at fixing the removal of total rain from the long term statistics. At that point it wasn't known that this sensor is not always present. But now I agree it is reasonable to keep the other values in LTS as TOTAL_INCREASING too, unless they cause any problems.

Not entirely sure if it makes sense to preserve/reintroduce the rolling window ones in long-term statistics - the consensus from the previous discussions was to remove them from there, which my change only made consistent for hourly sensors as well.

The major change I'd do though is that I'd use an explicit list of the sensors instead of the regexp, the only advantage of the regexp is that it's slightly more concise, but it's harder to read and orders of magnitude slower. And in longer run, I think it would be best if the difference between rolling window sensors was implemented on the aioecowitt library level, but I'd not go through that complexity for the purpose of this fix.

@sairon sairon requested review from joostlek and zweckj December 8, 2025 10:47
@sairon
Copy link
Member

sairon commented Dec 8, 2025

Adding @zweckj and @joostlek as the reviewers of the previous PRs.

@upsuper upsuper requested a review from sairon December 8, 2025 21:10
_ROLLING_WINDOW_RAIN_COUNT_SENSOR = re.compile(
"(?:hourly|last24h)rain(?:in|mm)|(?:last24)?hrain_piezo(?:mm)?"
)
_ROLLING_WINDOW_RAIN_COUNT_SENSORS = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I stiln think this should be a positive list, not a negative list.

Copy link
Contributor Author

@upsuper upsuper Dec 8, 2025

Choose a reason for hiding this comment

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

I strongly disagree.

There are 30 rain count sensors in total, in which 8 are rolling window and 22 are accumulating.

If we want to really be comprehensive, then I'd go with something like

_RAIN_COUNT_SENSORS_STATE_CLASS_MAPPING: Final = {
    "eventrainin": SensorStateClass.TOTAL_INCREASING,
    "hourlyrainin": None,
    "totalrainin": SensorStateClass.TOTAL_INCREASING,
    "dailyrainin": SensorStateClass.TOTAL_INCREASING,
    "weeklyrainin": SensorStateClass.TOTAL_INCREASING,
    "monthlyrainin": SensorStateClass.TOTAL_INCREASING,
    "yearlyrainin": SensorStateClass.TOTAL_INCREASING,
    "last24hrainin": None,
    "eventrainmm": SensorStateClass.TOTAL_INCREASING,
    "hourlyrainmm": None,
    "totalrainmm": SensorStateClass.TOTAL_INCREASING,
    "dailyrainmm": SensorStateClass.TOTAL_INCREASING,
    "weeklyrainmm": SensorStateClass.TOTAL_INCREASING,
    "monthlyrainmm": SensorStateClass.TOTAL_INCREASING,
    "yearlyrainmm": SensorStateClass.TOTAL_INCREASING,
    "last24hrainmm": None,
    "erain_piezo": SensorStateClass.TOTAL_INCREASING,
    "hrain_piezo": None,
    "drain_piezo": SensorStateClass.TOTAL_INCREASING,
    "wrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "mrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "yrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "last24hrain_piezo": None,
    "erain_piezomm": SensorStateClass.TOTAL_INCREASING,
    "hrain_piezomm": None,
    "drain_piezomm": SensorStateClass.TOTAL_INCREASING,
    "wrain_piezomm": SensorStateClass.TOTAL_INCREASING,
    "mrain_piezomm": SensorStateClass.TOTAL_INCREASING,
    "yrain_piezomm": SensorStateClass.TOTAL_INCREASING,
    "last24hrain_piezomm": None,
}

but then we need to introduce another warning for a sensor not in this list, which just makes this change increasing more complicated.

Given that

  • we are fixing a serious regression many users face that can cause data loss,
  • previous code was using a negative list,
  • there are far more accumulating sensors than rolling window sensors, and
  • the long term direction is to move this discrimination into upstream aioecowitt library,

I'd suggest we don't add complexity here and keep the fix as simple and minimal as possible to avoid introducing even more issues. We can always evaluate any improvement separately in the future if needed.

Copy link
Member

Choose a reason for hiding this comment

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

but then we need to introduce another warning for a sensor not in this list

Sorry, I don't see it, which one are you talking about?

Copy link
Contributor

@epenet epenet Dec 9, 2025

Choose a reason for hiding this comment

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

I'm not saying you need to set a full list, but I think all sensors should default to state_class=None

Then only the "valid" sensors should get the state_class overriden to SensorStateClass.TOTAL_INCREASING

_TOTAL_INCREASING_RAIN_COUNT_SENSORS: Final = {
    # Lifetime
    "totalrainin": SensorStateClass.TOTAL_INCREASING,
    "totalrainmm": SensorStateClass.TOTAL_INCREASING,
    # Yearly, resets 1st day of the year
    "yearlyrainin": SensorStateClass.TOTAL_INCREASING,
    "yearlyrainmm": SensorStateClass.TOTAL_INCREASING,
    "yrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "yrain_piezomm": SensorStateClass.TOTAL_INCREASING,
    # Monthly, resets 1st day of the month
    "monthlyrainin": SensorStateClass.TOTAL_INCREASING,
    "monthlyrainmm": SensorStateClass.TOTAL_INCREASING,
    "mrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "mrain_piezomm": SensorStateClass.TOTAL_INCREASING,
    # Weekly, resets 1st day of the week
    "weeklyrainin": SensorStateClass.TOTAL_INCREASING,
    "weeklyrainmm": SensorStateClass.TOTAL_INCREASING,
    "wrain_piezo": SensorStateClass.TOTAL_INCREASING,
    "wrain_piezomm": SensorStateClass.TOTAL_INCREASING,
}

I'm not sure about the long-term value for "daily" or for "event"

Copy link

Choose a reason for hiding this comment

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

I'm not sure about the long-term value for "daily" or for "event"

@epenet

As a user of the integration:

  • Event represents rain that doesn't have a break of more than 24 hours. It is rain that continues on and off - not continuous - but with no more than 23 hours between rainfall being recorded. A rain event may span days, or even weeks, while day and week sensors reset on their boundaries.
  • Daily is of use for planning irrigation (sprinkler) automation; if you have had a certain amount of rain total in a certain number of days, then there is lesser or no need for irrigation.

That's the near-term need for irrigation automation, but the long-term need is for adjusting the amount of irrigation (duration of cycle).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And again, this discrimination shouldn't live in Home Assistant code, so this list should be rather short-lived before necessary arrangement is done on aioecowitt library.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it's as short-lived as you say it will be, then no harm in making the default None

Copy link
Contributor Author

@upsuper upsuper Dec 10, 2025

Choose a reason for hiding this comment

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

There is. The short-live can still be a while, and it also puts more code in Home Assistant unnecessarily.

I don't want to continue this unproductive discussion. I have been trying to address your arguments but you ignore mine. And it seems both of us are rather determined on this.

If you strongly believe it shouldn't be this way, feel free to raise a different PR and leave it to someone else's judgement on which one should be merged. I'm not going to change mine without seeing a convincing argument.

Copy link
Contributor

@epenet epenet Dec 10, 2025

Choose a reason for hiding this comment

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

If you strongly believe it shouldn't be this way

I do, because your way can introduce actual bugs:

  • a missing state class is a feature request, which can be improved upon
  • a invalid state class is a bug

feel free to raise a different PR

As you wish - I have opened alternative #158528

Copy link
Member

Choose a reason for hiding this comment

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

I'm unresolving this conversation. I thin it isn't finished.

If we want to really be comprehensive, then I'd go with something like

Honestly, I think that solution is the best approach.

EXPLICIT ALWAYS WINS.

I suggest define them all fully explicitly as full integration descriptions.

Hacks like: if sensor.key in _ROLLING_WINDOW_RAIN_COUNT_SENSORS is the source of all weirdness to begin with.

We need to start extensively defining every sensor. I'm going to put my feet down on this one.

../Frenck

@frenck
Copy link
Member

frenck commented Dec 10, 2025

@sairon @joostlek @zweckj What's the next step for this PR?

Wait for review. Please don't ping for that. If you want to learn more about our review processes, please check out developer documentation here: https://developers.home-assistant.io/docs/review-process

Copy link
Member

@frenck frenck left a comment

Choose a reason for hiding this comment

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

As per discussion above. We need to make this all explicit, as the magic approach is clearly not working.

This needs to come out as a solid solution, that addresses the issue for now and the future.

../Frenck

                       

Blogging my personal ramblings at frenck.dev

@home-assistant home-assistant bot marked this pull request as draft December 10, 2025 13:58
@upsuper upsuper marked this pull request as ready for review December 10, 2025 22:10
@home-assistant home-assistant bot requested a review from frenck December 10, 2025 22:10
@upsuper
Copy link
Contributor Author

upsuper commented Dec 10, 2025

Okay, I changed it to the comprehensive list.

When I post #158204 (comment) I was under the impression that adding a warning is more cumbersome that requires extra boilerplate like introducing translations etc. but it turns out that just a single _LOGGER.warning is all I need. That would indeed be the best solution then.

Thanks for pushing on that, @frenck.

frenck
frenck previously requested changes Dec 10, 2025
Copy link
Member

@frenck frenck left a comment

Choose a reason for hiding this comment

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

This is not what I meant. Let get rid of the constant fully and extend the entity descriptions instead. Make the list of expected entities and their behavior explicit, removing the need for the whole state class replacing magic.

@home-assistant home-assistant bot marked this pull request as draft December 10, 2025 22:55
@upsuper
Copy link
Contributor Author

upsuper commented Dec 10, 2025

This is not what I meant. Let get rid of the constant fully and extend the entity descriptions instead. Make the list of expected entities and their behavior explicit, removing the need for the whole state class replacing magic.

I don't think that's a good idea.

aioecowitt has in total 288 sensors in its SENSOR_MAP. It's pretty unpractical to extend all of them into individual entity descriptions (both for adding and reviewing such a change and for ongoing maintenance).

The EcoWittSensorTypes from the library means to categorize them for easier mapping in Home Assistant. I think there is a concensus that the ideal long-term solution is to divide the existing RAIN_COUNT_{INCHES,MM} into different types based on their characteristic, e.g. to RAIN_COUNT_ACC_{INCHES,MM} and RAIN_COUNT_WINDOW_{INCHES,MM}, so that they can be mapped differently in the HA component code. And the list here serves as a quick fix before such changes can be arranged.

The alternative is we abandon this quick fix, and try to merge an upstream change then update the dependency and mapping code here.

@upsuper upsuper marked this pull request as ready for review December 10, 2025 23:04
@home-assistant home-assistant bot requested a review from frenck December 10, 2025 23:04
@upsuper
Copy link
Contributor Author

upsuper commented Dec 10, 2025

I've also raised home-assistant-libs/aioecowitt#312 to change the upstream code. It's not clear to me how the process would look like, but my understanding is to fix it in the proper way, we need to

  • merge a PR (e.g. my one above) upstream to aioecowitt
  • publish a new version of aioecowitt
  • raise a new PR in HA core to bump the dependency and update the mapping code

@bramkragten bramkragten added this to the 2026.1.0 milestone Jan 5, 2026
@epenet
Copy link
Contributor

epenet commented Jan 5, 2026

Even if my review was stale, the solution here has already been rejected by Frenck

The logic should be moved to the backend library

@joostlek
Copy link
Member

joostlek commented Jan 5, 2026

I think in the end, yes we do want entity descriptions for every data point. Except removing the state class from all the values was a regression and while in the end I would also like to see an entity description based setup, it currently doesn't record statistics for all entities and I think that's way more inconvenient for the end user. So let's merge this for now, and let's review an entity description based setup in the future.

@joostlek joostlek merged commit 7c81df6 into home-assistant:dev Jan 5, 2026
35 of 36 checks passed
@upsuper upsuper deleted the ecowitt-rain-state-class branch January 5, 2026 20:32
@zweckj
Copy link
Member

zweckj commented Jan 6, 2026

Fixes #159188

@github-actions github-actions bot locked and limited conversation to collaborators Jan 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ecowitt GW2000B: "The entity no longer has a state class"