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

Refactoring to use Entity Descriptors and fixing hold functionality #201

Merged
merged 6 commits into from
Oct 31, 2024

Conversation

ocrease
Copy link
Collaborator

@ocrease ocrease commented Oct 29, 2024

Hi @MindrustUK, this is a rather large PR based on the work I've been doing for the hold functionality of NeoStat climate entities, NeoStats working in Timer mode and NeoPlugs (not tested since I don't have any). I have refactored everything to use EntityDescriptors. Let me try to explain everything that I have done:

  • I have added a coordinator.py which extracts the coordinator logic into its own class. Logic was previously in __init__.py
  • I have added a base HeatmiserNeoEntity in entity.py and a base HeatmiserNeoEntityDescription. All of the entities previously had duplicated logic for the device info, unique id, availability etc. The base class now has this logic and the entity descriptor provides ways of customizing the entity were necessary (most of the quality default integrations seem to use the EntityDescriptor model)
  • I got rid of number.py. Number entities are used to report writable numbers from a device. The three that existed in the dev branch for hold minutes, hold hours and hold temperature were for read only values from the devices. There are sensors (in sensors.py) for these values instead
  • I got rid of switch.py. There were switches related to hold, standby and (broken?) neo plug functionality. All of these have been replaced with features elsewhere
  • Added binary_sensor.py. Most of these were previously defined in sensors.py, but that is not the right way to do it. Entities should be defined in the correct file for their platform. Binary sensors rewritten to make use of the base entity and descriptor
    • Moving the binary sensor for the contact sensor to this file means that the device class now works correctly and the state should be shown as Open / Closed (Door sensor not reporting to HA #165)
  • button.py rewritten to make use of the base entity and descriptor
  • climate.py
    • rewritten to make use of the base entity and descriptor
    • added support for presets: Home, Away, Boost
      • Home is standard operation
      • Away sets the away mode on the hub (and therefore affects all entities). You get standby by setting the mode instead (say from heat to off)
      • Boost sets a hold at a temperature of 2 degrees higher than target for 30 minutes. Custom temperatures/durations have to be done via the service call
    • Improved hold service schema validation
  • Added select.py - NeoStats in timeclock mode and NeoPlugs are now modelled as select entities
  • sensor.py rewritten to make use of the base entity and descriptor and removed binary_sensor entities
  • services.yaml added the actual state to hold on the timer hold service (eg so we can override to off)
  • The base entity class has _attr_has_entity_name = True and the entity descriptors do one of three things HA Docs
    • If it is the main feature of a device, the name property is set to None. HA then uses the device name only for this entity. Examples are the climate entities themselves, the neostats in timer mode, the neoplugs, the contact sensors and the temperature sensor (as I write this, I think I may need to change some of this, see below)
    • Alternatively the name property can be omitted from the descriptor if the device_class is set. Things like battery, connectivity, the identify button do this. HA will set the name to the device name + a standard name based on the device class
    • Finally, if a name property is provided, the device name + name property is used (but in the UI when looking at a device, HA removes the device name since it is implicit
  • Some sensors are hidden by default, things like away/holiday which really should be on the hub and floor temperature / current temperature
  • Floor temperature and current temperature are set up with state_class=SensorStateClass.MEASUREMENT so long term statistics should work (Feature: Add support for statistics / long term statistics on data returned by integration #158)
  • All the files I modified are formatted with the standard home assistant format (ruff check --fix and ruff format). Very few errors remain on those files. This should help Feature: Official core integration? #60 is still an aim, though of course lots of other things remain
    • If you are interested in how I'm doing this, let me know (basically followed the devcontainer instructions in the HA developer docs, then cloned this repo into a development folder and did a symlink to add heatmiserneo to the custom_components in the config directory.

Regarding the entity naming, I know you were working round some issues with naming and that's how you ended up with the long names. Could you elaborate what the issues were? I do suspect the door contact, external thermostat sensors and maybe neoplugs need to be explicitly named, but could you attach the output of the new diagnostics functionality to see what the data looks like? Also note that the entities can always be renamed by the user, so the default just needs to be sensible.

Here are some screenshots to accompany the above:

Neo Stat previously (with the long names and number entities)

image

Neo Stat now (notice sensible naming, mode on the climate entity (Home) and various hidden sensors)

image

Neo Stat in timer mode (notice the select dropdown. Also "Device Temperature". My neostat reports the temperature even though its not used by the timer. Might be useful for someone, so I added it as a diagnostic sensor disabled by default)

image

Climate card now has a preset mode selector

image

Renaming a sensor

image

Also note I haven't implemented anything to change the default time / temperature increment for the hold service. We need to explore changes to the config flow to support this, but I think that should be done separately

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 29, 2024

Regarding the extra floor temperature sensor, I see there was a discussion in #89 talking about whether it should be a separate sensor or an attribute and the decision was to use an attribute instead. Looking at the entity docs, this sounds like a mistake. It says:

Warning

Entities that generate a significant amount of state changes can quickly increase the size of the database when the extra_state_attributes also change frequently. Minimize the number of extra_state_attributes for these entities by removing non-critical attributes or creating additional sensor entities.

Also, having it as sensor means that you can add it to long term statistics for example. I've added it as a disabled entity (and its shouldn't be added to a remote air sensor), so I guess users can choose. Having said that, I don't have any floor sensors connected to my stats, so if you want to remove the floor temperature sensor I'm happy to delete it

@MindrustUK
Copy link
Owner

@ocrease Just had a brief run through, all really good! Thanks very much.

I've only spotted a few minor things, when I get some time I'll give this a proper review. I'm assuming you've essentially spent most of your evenings and weekend on this by the looks of the volume of code!

Apologies that it's going to take me a little longer to review (read: probably Thursday / Friday by the time I get a proper eyes on due to real world stuff). I'll try and dip into the review if I get a minute before then.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 29, 2024

@MindrustUK No worries. I realise it's quite a big PR! In the meantime, if you can download diagnostics for your environment (assuming you have more than just NeoStat V2s like I do) I might be able to spot any issues

@MindrustUK
Copy link
Owner

@ocrease

config_entry-heatmiserneo-01JBBYB1WMEZYMXKHBGFAQQA72.zip

If you see any unusual names like swimming pool or anything else, for clarity I don't have a swimming pool I just wish I did :) So don't expect the test devices to necessarily be deployed in the way you'd expect. I'll hook up my NeoHub V1 and an underfloor temperature probe to one of the stats when I get some time.

Also of note the Heatmiser Neo HC and Neo Air 1 are actively broken, I'm avoiding putting any work into fixing them until I address: #78 where I want to refactor the product list to be a dictionary that contains attributes such as "HAS_BATTERY" or "HAS_TEMP_RANGE" etc. This should make it easier to evaluate when adding devices rather than "If device_model in [1,3,5,7]" etc.

@MindrustUK
Copy link
Owner

PS: Just realised my Kitchen and bathroom both use in floor probes, so these may do the job for whatever testing was needed with externals (I forget what it was).

PPS: The NeoAir V1 has three modes of operation, I've got it in Mode 1 at the minute.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 29, 2024

@MindrustUK thanks, the diagnostics data should be very helpful, but... I suspect the diagnostics download has a bug and not all the data is being downloaded. I think devices are grouped by zone name and my original implementation put the device in a dict so only one device per zone is being returned. I'll raise a PR (hopefully later today) to resolve that so that you can download the diagnostics again.

In the meantime, maybe don't spend too much time reviewing this PR.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 29, 2024

@MindrustUK would you be able to add a few screenshots from the heatmiser app for the NeoAir and Neostat HC? It would help to have an idea of how Heatmiser intends them to be used.

For the NeoAir, I think these are reported as two separate devices, one for the thermostat and one for the timer. Can you put each half on standby separately from the app? Or if you put the timer on standby the thermostat also goes on standby (and viceversa)?

@MindrustUK
Copy link
Owner

@ocrease

Home Assistant Screenshots:

NeoAir:
image

NeoStatHC
image

The NeoAir behaves 1:1 in mode 1 as any NeoStat V2 or V1 behaves but with increased latency due nature of the reduced polling time as to reduce power consumption. The NeoAir has no onboard switching, i.e. no relay and no external probe inputs. From the manual operation modes 1&3 are thermostatic modes, mode 2 is a timeclock mode.

Screenshot_20241030_084438_Heatmiser Neo

Screenshot_20241030_084510_Heatmiser Neo

The NeostatHC is a different beast all together. Configurable fans, Heating Coils etc. Electrically it just appears be five normally open relays instead of one.

Screenshot_20241030_084449_Heatmiser Neo

Screenshot_20241030_084451_Heatmiser Neo

Screenshot_20241030_084456_Heatmiser Neo

Let me know if you have any more requirements and I'll see what I can gather up.

@MindrustUK
Copy link
Owner

RE: For the NeoAir, I think these are reported as two separate devices, one for the thermostat and one for the timer. Can you put each half on standby separately from the app? Or if you put the timer on standby the thermostat also goes on standby (and viceversa)?

I think this is mode 3, I've not set this mode up yet. Let's get mode 1 working, then I can try experimenting with modes 2 and 3.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 30, 2024

@MindrustUK Ok thanks, I think you can review / test when you get a chance

@MindrustUK
Copy link
Owner

@ocrease Looks like I crashed into a missing lib for some reason

    from propcache import cached_property
ModuleNotFoundError: No module named 'propcache'```

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 30, 2024

@MindrustUK I've been developing against the dev branch of Home Assistant and it looks like there is a change coming which is not on the released version yet home-assistant/core#127339

I made a change to use the previous option. Can you give it a try?

@MindrustUK
Copy link
Owner

A quick smoke test and everything looks good. As previous will be a bit of time before I test everything properly and finish review but it all looks good right now.

@ocrease ocrease mentioned this pull request Oct 30, 2024
@ocrease
Copy link
Collaborator Author

ocrease commented Oct 30, 2024

@MindrustUK great, of course take your time.

Based on the logs provided in #202, it looks like the NeoAir Thermostat and Timer devices are reported completely separate, even with different serial numbers. I think I'll revert a couple of changes I made thinking that they would all be under the same device in HA

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 30, 2024

@MindrustUK changes made

@MindrustUK
Copy link
Owner

@ocrease Looks like we've got a bug on import

File "/config/custom_components/heatmiserneo/binary_sensor.py", line 22, in <module>
    from .const import (
ImportError: cannot import name 'HEATMISER_TYPE_IDS_HOLD' from 'custom_components.heatmiserneo.const' (/config/custom_components/heatmiserneo/const.py)

@MindrustUK
Copy link
Owner

@ocrease you can ignore the above, missed a commit when rolling forwards.

@MindrustUK
Copy link
Owner

@ocrease Early feedback

Failed to perform the action select/select_option. 'NeoStat' object has no attribute 'data' when changing Neoplug states.

@MindrustUK
Copy link
Owner

I'll need to setup a test plan for this I think, to ensure that every feature and configuration gets hit. I'll try and find some time to look into this.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 31, 2024

@MindrustUK the bug with the NeoPlug should be fixed. I also removed the "Identify" button from the accessories

@MindrustUK
Copy link
Owner

@ocrease other than a few my question on string substitution (https://peps.python.org/pep-0498/ I believe). I think everything is good enough to merge and get more eyes on.

The NeoStat-HC is not quite working as expected but let's deal with that as a separate issue in terms of adding support for a "new" device.

@ocrease
Copy link
Collaborator Author

ocrease commented Oct 31, 2024

@MindrustUK The string substitution changes are following these guidelines in the Home Assistant developer docs. As you can see f-strings are the preferred method, with the exception being logging. With log statements, if the log level is disabled (say debug), then by using the old style formatting the string substitution can be skipped altogether. With f-string the string substitution would be done and then the new string discarded.

The actual ruff error code is G004

Running "ruff check *.py" on the dev branch results in 415 errors. Running it on this branch its 36, 33 of which are in the config_flow.py which I haven't touched.

Did you check the behaviour of the plug, contact sensor and air sensors?

@MindrustUK
Copy link
Owner

@ocrease Interesting... answers my question on the f-string front. All good on that one.

Plug seems all good as does the contact and air sensors. Thanks again for all your work.

Merging...

@MindrustUK MindrustUK merged commit 7c0a3a4 into MindrustUK:dev Oct 31, 2024
@ocrease ocrease deleted the EntityDescriptors branch November 5, 2024 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants