@@ -27,45 +27,75 @@ export function getRealDevicesForRender(
2727) : RenderedDevice [ ] {
2828 if ( ! deviceRegistry || ! entityRegistry || ! hass ) return [ ] ;
2929
30- return deviceRegistry
31- . filter ( ( device ) => ! device . disabled_by )
32- . map ( ( device , index ) => {
33- const entities = entityRegistry
34- ?. filter ( ( entry ) => entry . device_id === device . id && ! entry . hidden_by && ! entry . disabled_by )
35- . map ( ( entry ) => entry . entity_id ) || [ ] ;
36- if ( entities . length === 0 ) return undefined ;
37-
38- const nonUpdateEntities = entities . filter ( ( entityId ) => ! entityId . startsWith ( 'update.' ) && ! entityId . startsWith ( 'device_tracker.' ) ) ;
39- if ( nonUpdateEntities . length === 0 ) return undefined ;
40- const preferredEntity = nonUpdateEntities . find ( ( entityId ) => PREFERRED_DOMAINS . test ( entityId ) ) || nonUpdateEntities [ 0 ] ;
41- if ( ! preferredEntity || ! hass ) return undefined ;
42-
43- const stateObj = hass . states [ preferredEntity ] ;
44- const state = stateObj ?. state || 'unknown' ;
45- const domain = preferredEntity . split ( '.' ) [ 0 ] || 'sensor' ;
46- const icon = String ( stateObj ?. attributes ?. icon || iconForDomain ( domain ) ) ;
47- const name = String ( stateObj ?. attributes ?. friendly_name || preferredEntity ) ;
48- if ( / p r e - ? r e l e a s e / i. test ( name ) ) return undefined ;
49- const subtitle = areaNameForEntity ( preferredEntity , entityRegistry , deviceRegistry , areas ) || '' ;
50- const detail = domain || '--' ;
51-
52- return {
53- entityId : preferredEntity ,
54- name,
55- subtitle,
56- detail,
57- state,
58- icon,
59- color : DEVICE_COLORS [ index % DEVICE_COLORS . length ] ! ,
60- } ;
30+ const usedEntityIds = new Set < string > ( ) ;
31+ const rendered : RenderedDevice [ ] = [ ] ;
32+
33+ const renderEntry = (
34+ entityId : string ,
35+ index : number ,
36+ ) : RenderedDevice | undefined => {
37+ if ( usedEntityIds . has ( entityId ) ) return undefined ;
38+ if ( entityId . startsWith ( 'update.' ) || entityId . startsWith ( 'device_tracker.' ) ) return undefined ;
39+
40+ const stateObj = hass ! . states [ entityId ] ;
41+ const state = stateObj ?. state || 'unknown' ;
42+ const domain = entityId . split ( '.' ) [ 0 ] || 'sensor' ;
43+ const icon = String ( stateObj ?. attributes ?. icon || iconForDomain ( domain ) ) ;
44+ const name = String ( stateObj ?. attributes ?. friendly_name || entityId ) ;
45+ if ( / p r e - ? r e l e a s e / i. test ( name ) ) return undefined ;
46+ const subtitle = areaNameForEntity ( entityId , entityRegistry , deviceRegistry , areas ) || '' ;
47+ const detail = domain || '--' ;
48+
49+ usedEntityIds . add ( entityId ) ;
50+ return {
51+ entityId,
52+ name,
53+ subtitle,
54+ detail,
55+ state,
56+ icon,
57+ color : DEVICE_COLORS [ index % DEVICE_COLORS . length ] ! ,
58+ } ;
59+ } ;
60+
61+ for ( const device of deviceRegistry ) {
62+ if ( device . disabled_by ) continue ;
63+ const entities = entityRegistry
64+ . filter ( ( entry ) => entry . device_id === device . id && ! entry . hidden_by && ! entry . disabled_by )
65+ . map ( ( entry ) => entry . entity_id ) ;
66+ if ( entities . length === 0 ) continue ;
67+
68+ const nonUpdateEntities = entities . filter ( ( id ) => ! id . startsWith ( 'update.' ) && ! id . startsWith ( 'device_tracker.' ) ) ;
69+ if ( nonUpdateEntities . length === 0 ) continue ;
70+ const preferredEntity = nonUpdateEntities . find ( ( id ) => PREFERRED_DOMAINS . test ( id ) ) || nonUpdateEntities [ 0 ] ;
71+ if ( ! preferredEntity ) continue ;
72+
73+ const renderedDevice = renderEntry ( preferredEntity , rendered . length ) ;
74+ if ( renderedDevice ) rendered . push ( renderedDevice ) ;
75+ }
76+
77+ // Include standalone entities (e.g. KNX) that belong to no device
78+ const deviceIds = new Set ( deviceRegistry . filter ( ( d ) => ! d . disabled_by ) . map ( ( d ) => d . id ) ) ;
79+ const orphanEntities = entityRegistry
80+ . filter ( ( entry ) => {
81+ if ( entry . hidden_by || entry . disabled_by ) return false ;
82+ if ( ! entry . device_id ) return true ;
83+ return ! deviceIds . has ( entry . device_id ) ;
6184 } )
62- . filter ( ( device ) : device is RenderedDevice => Boolean ( device ) )
63- . filter ( ( d ) => {
64- if ( filters . filterRoom && d . subtitle !== filters . filterRoom ) return false ;
65- if ( filters . filterType && deviceTypeGroupKey ( d . detail ) !== filters . filterType ) return false ;
66- if ( filters . hideUnassigned && ! d . subtitle ) return false ;
67- return true ;
68- } ) ;
85+ . filter ( ( entry ) => PREFERRED_DOMAINS . test ( entry . entity_id ) || entry . entity_id . startsWith ( 'binary_sensor.' ) )
86+ . slice ( 0 , 300 ) ;
87+
88+ for ( const entry of orphanEntities ) {
89+ const renderedDevice = renderEntry ( entry . entity_id , rendered . length ) ;
90+ if ( renderedDevice ) rendered . push ( renderedDevice ) ;
91+ }
92+
93+ return rendered . filter ( ( d ) => {
94+ if ( filters . filterRoom && d . subtitle !== filters . filterRoom ) return false ;
95+ if ( filters . filterType && deviceTypeGroupKey ( d . detail ) !== filters . filterType ) return false ;
96+ if ( filters . hideUnassigned && ! d . subtitle ) return false ;
97+ return true ;
98+ } ) ;
6999}
70100
71101export function deviceTypeGroupKey ( detail : string ) : string {
0 commit comments