@@ -182,26 +182,70 @@ export const deviceAutomationEditorMode = (
182182 : "unknown-device" ;
183183} ;
184184
185- // Like deviceAutomationsEqual, but ignores device_id and entity_id so an
186- // automation can be matched to the equivalent one on a different device (for
187- // example when a referenced device was replaced by a split device).
188- export const deviceAutomationsSimilar = (
185+ // Whether two device automations describe the same kind of automation, meaning
186+ // the same domain, type, subtype and event. Which device and which entity they
187+ // apply to is ignored, so an automation can be matched against the ones another
188+ // device offers.
189+ const deviceAutomationsSameType = ( a : DeviceAutomation , b : DeviceAutomation ) =>
190+ deviceAutomationIdentifiers
191+ . filter ( ( property ) => property !== "device_id" && property !== "entity_id" )
192+ . every ( ( property ) => Object . is ( a [ property ] , b [ property ] ) ) ;
193+
194+ // Whether two device automations point at the same entity. Both the entity
195+ // registry id and the entity id are accepted as reference, on either side.
196+ const deviceAutomationsSameEntity = (
197+ entityRegistry : EntityRegistryEntry [ ] ,
189198 a : DeviceAutomation ,
190199 b : DeviceAutomation
191200) => {
192- if ( typeof a !== typeof b ) {
201+ if ( ! a . entity_id && ! b . entity_id ) {
202+ return true ;
203+ }
204+ if ( ! a . entity_id || ! b . entity_id ) {
193205 return false ;
194206 }
195- return deviceAutomationIdentifiers
196- . filter ( ( property ) => property !== "device_id" && property !== "entity_id" )
197- . every ( ( property ) => {
198- const inA = property in a ;
199- const inB = property in b ;
200- if ( ! inA && ! inB ) {
201- return true ;
202- }
203- return Object . is ( a [ property ] , b [ property ] ) ;
204- } ) ;
207+ return (
208+ a . entity_id === b . entity_id ||
209+ compareEntityIdWithEntityRegId ( entityRegistry , a . entity_id , b . entity_id )
210+ ) ;
211+ } ;
212+
213+ // Finds, among the automations a device offers, the one matching the given
214+ // automation, so a device automation can follow its device after the device was
215+ // replaced. A device exposes the same automation type once per entity, so
216+ // matching on the type alone picks an arbitrary entity. Splitting a device
217+ // leaves the entity registry ids untouched, which makes the entity the reliable
218+ // match, the type only serving as a fallback for entity-less automations.
219+ export const findEquivalentDeviceAutomation = < T extends DeviceAutomation > (
220+ entityRegistry : EntityRegistryEntry [ ] ,
221+ automations : T [ ] ,
222+ automation : DeviceAutomation
223+ ) : T | undefined => {
224+ const sameType = automations . filter ( ( candidate ) =>
225+ deviceAutomationsSameType ( candidate , automation )
226+ ) ;
227+ const sameEntity = sameType . find ( ( candidate ) =>
228+ deviceAutomationsSameEntity ( entityRegistry , candidate , automation )
229+ ) ;
230+ return sameEntity || sameType [ 0 ] ;
231+ } ;
232+
233+ // Everything the automation list does not return: the extra fields of the
234+ // capabilities schema (`for`, `above`, ...) and the config of the row holding
235+ // the automation (`enabled`, `id`, `alias`, ...).
236+ export const deviceAutomationExtraConfig = < T extends DeviceAutomation > (
237+ automation : T
238+ ) : Partial < T > => {
239+ const extraConfig : Partial < T > = { } ;
240+ for ( const property in automation ) {
241+ if (
242+ property !== "metadata" &&
243+ ! deviceAutomationIdentifiers . includes ( property )
244+ ) {
245+ extraConfig [ property ] = automation [ property ] ;
246+ }
247+ }
248+ return extraConfig ;
205249} ;
206250
207251const compareEntityIdWithEntityRegId = (
0 commit comments