[Reactor] [Dev] Clarifications/Docs for refreshCapabilities and Entity action implementations return types
-
Hi @toggledbits
I'd like to update my controllers with these new features, but I'm struggling to find any guidance in the docs - and in general to understand the context.
Could you please elaborate more? Thanks.
-
refreshCapabilities()/refreshCapability)
These methods may be called when you detect a change in the Reactor configuration or mapping implementations. It is up to your Controller class to determine how to do that. When a change is detected (more on that at the end of this post), calling
refreshCapabilities()
will re-apply the capabilities already extended to the target Entity and sync the attribute dictionary. Entities maintain local copies of capability definitions because an entity can tweak a capability to reflect it's actual abilities/data.Let's say for example that I add an attribute called
ramp_rate
to the systemdimming
capability. None of the entities that have this capability already assigned would automatically inheritramp_rate
when you upgrade Reactor. To get it, you'd have to use one of therefresh...()
methods.Action Responses
Returning a response from an action implementation is as straightforward as
resolve()
-ing the Promise that an action implementation is required to return with an argument containing the data for the response. If you are defining the action for a capability of yours, you addresponse: true
to the action definition for the capability, so that the UI presents the field to allow the user to specify where the response should be stored.
Detecting Changes -- When to Call refresh...()
Many of the built-in Controller classes use extensive implementation maps to convert data from the related hub/source into attributes, and to provide implementations for actions. These Controllers have large and complex
xxx_capabilities
andxxx_devices
files containing this data. When the Controller class is loaded, these data files are read, and later the individual Controller instances then use the loaded data.From time to time, device support will change for one of these Controllers, so the map files change to reflect that. This is usually accompanied by a change in the
version
andrevision
values in the file headers. Reactor's system-wide capabilities are defined in a similar way, and have similarversion
andrevision
values.The built-in Controllers look at these version and revision numbers and compare them to stored data in an entity instance. If they are not identical, the Controller will call
refreshCapabilities()
on the entity.In some cases, like MQTTController, where configuration/implementation of an entity is derived from multiple templates in user configuration rather than system files, handling/requiring a
version
andrevision
for each template would add complexity for the user and be really tedious to deal with, so a slightly different approach is used. After all theinclude
s are processed by the referenced templates and a full template has been assembled for the entity, it is hashed usingutil.hash()
. The first time an entity is configured (detected by a hash value not being present on the entity), the hash value is stored on the entity. Thereafter, the new hash derived at Controller startup is compared to the hash stored on the entity, and if they differ, the entity is refreshed and the new hash stored on the entity. In this way, the hash on the entity always reflects the latest configuration/templates, and if they change, that is quickly detected and the changes applied. -
Thanks for the response.
Regarding
response: true
, I got the point. I'll try to build anOpenAIController
based on this and release it in the next couple of days.Correct me if I'm wrong, but if I don't have files with local mapping, but I always reload entities and their capabilities, I don't need to use refres*?
See this for reference:
Thanks!
-
Because the entities are saved in persistent storage with their states, and the capability definitions are stored (cloned to) the entities when extended, you still need to refresh.
At line 666 (!) of your
mapDevice()
function, where you are extending capabilities if given, you can use theextendCapabilities()
method to extend an array of capabilities (eliminating your loop there that does pretty much the same thing). You could then add a test to see if there is a change to the version of your Controller class or the system capabilities, for example, and refresh based on that:// capabilities if (capabilities) { this.log.debug(5, "%1 [%2] adding capabilities: %3", this, id, capabilities); e.extendCapabilities( capabilities ); // Check controller and system capabilities versions for changes const vinfo = { ...Capabilities.getSysInfo(), controller: VERSION }; const hash = util.hash( JSON.stringify(vinfo) ); if ( e._hash !== hash ) { e.refreshCapabilities(); e._hash = hash; } }
You might also consider a mechanism to have it do the check only once per run/startup of the Controller instance, the first time it looks at the entity, rather than every time this method is called with capabilities.
-
Because the entities are saved in persistent storage with their states, and the capability definitions are stored (cloned to) the entities when extended, you still need to refresh.
At line 666 (!) of your
mapDevice()
function, where you are extending capabilities if given, you can use theextendCapabilities()
method to extend an array of capabilities (eliminating your loop there that does pretty much the same thing). You could then add a test to see if there is a change to the version of your Controller class or the system capabilities, for example, and refresh based on that:// capabilities if (capabilities) { this.log.debug(5, "%1 [%2] adding capabilities: %3", this, id, capabilities); e.extendCapabilities( capabilities ); // Check controller and system capabilities versions for changes const vinfo = { ...Capabilities.getSysInfo(), controller: VERSION }; const hash = util.hash( JSON.stringify(vinfo) ); if ( e._hash !== hash ) { e.refreshCapabilities(); e._hash = hash; } }
You might also consider a mechanism to have it do the check only once per run/startup of the Controller instance, the first time it looks at the entity, rather than every time this method is called with capabilities.
@toggledbits thanks for the help. I will eventually improve this code and apply capabilities only if the device is somewhat new/changed or at startup. OpenSprinklerController is peculiar, but I think I could manage to achieve this result.
This code is shared among my controllers, I've modified them to better handle updates. Thanks!
-
T toggledbits unlocked this topic on
-
T toggledbits locked this topic on