Skip to content

Commit d248b05

Browse files
committed
fix(devices): 支持无设备实体的 KNX 实体显示
1 parent 45ad527 commit d248b05

3 files changed

Lines changed: 70 additions & 40 deletions

File tree

dist/skins-pro.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "skins-pro",
3-
"version": "2.5.3",
3+
"version": "2.5.4",
44
"description": "Skins Pro for Home Assistant",
55
"main": "dist/skins-pro.js",
66
"type": "module",

src/selectors/devices.ts

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (/pre-?release/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 (/pre-?release/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

71101
export function deviceTypeGroupKey(detail: string): string {

0 commit comments

Comments
 (0)