Skip to content

Commit 7178a61

Browse files
krobipdclaude
andcommitted
Fix alias devices listing datapoints from other devices (#597, #536)
Grouping several independent aliases under one alias device made the type-detector merge them: it filled the detected device's slots — indicators (UNREACH/LOWBAT/ERROR) as well as regular states like ON_ACTUAL — from the sibling channels of the grouping. The Devices tab then listed datapoints belonging to completely different devices, and editing or clearing such a slot could modify or delete another device's alias state. Drop detected state mappings that are aliases sitting in a different alias channel than the device's primary (required) state. Required states are never dropped, and real hardware states have no common.alias, so genuine device-level indicators on a neighbouring channel (e.g. Homematic LOWBAT on the .0 channel) are left intact. Applied to both the list detection and the edit dialog auto-fill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f9e0f61 commit 7178a61

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

src-admin/src/Devices/SmartDetector.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,40 @@ export default class IOBChannelDetector {
3434
return this.detector.detect(options);
3535
}
3636
}
37+
38+
/**
39+
* Devices-side guard for type-detector issue #597 / #536.
40+
*
41+
* When several independent aliases are grouped under one alias *device*, the
42+
* type-detector merges them into a single detected device: it fills the device's
43+
* slots — indicators (UNREACH, LOWBAT, ERROR) as well as regular states like
44+
* ON_ACTUAL — from the sibling channels of that grouping. The detected device then
45+
* lists datapoints that belong to completely different devices (see the issue
46+
* screenshots: a motion alias showing a Shelly's "online", or a dimmer showing
47+
* another light's status).
48+
*
49+
* We drop mappings that are aliases sitting in a *different* alias channel than the
50+
* device's primary (required) state. Required states are never dropped, and real
51+
* hardware states carry no `common.alias`, so genuine device-level states on a
52+
* neighbouring channel (e.g. a Homematic LOWBAT on the .0 channel) stay untouched.
53+
*/
54+
export function removeForeignAliasStates(control: PatternControl, objects: Record<string, ioBroker.Object>): void {
55+
const primary = control.states.find(s => s.id && s.required) || control.states.find(s => s.id);
56+
if (!primary?.id) {
57+
return;
58+
}
59+
const homeChannel = primary.id.substring(0, primary.id.lastIndexOf('.'));
60+
for (const state of control.states) {
61+
if (!state.id || state.required) {
62+
continue;
63+
}
64+
const common = objects[state.id]?.common as ioBroker.StateCommon | undefined;
65+
if (!common?.alias) {
66+
// real hardware (no alias) – keep genuine device-level states
67+
continue;
68+
}
69+
if (state.id.substring(0, state.id.lastIndexOf('.')) !== homeChannel) {
70+
state.id = '';
71+
}
72+
}
73+
}

src-admin/src/Dialogs/DialogEditDevice.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
extendDeviceTypeTranslation,
5353
} from '@iobroker/adapter-react-v5';
5454
import ChannelDetector, { type Types, type DetectorState, type ExternalPatternControl } from '@iobroker/type-detector';
55+
import { removeForeignAliasStates } from '../Devices/SmartDetector';
5556

5657
import DialogEditProperties, { type DialogEditPropertiesState } from './DialogEditProperties';
5758
import DialogAddState from './DialogAddState';
@@ -788,6 +789,10 @@ class DialogEditDevice extends React.Component<DialogEditDeviceProps, DialogEdit
788789
return;
789790
}
790791

792+
// #597/#536: strip indicator datapoints that leaked in from sibling channels
793+
// of an alias-device grouping so auto-fill does not re-introduce them.
794+
detected.forEach(control => removeForeignAliasStates(control, this.props.objects));
795+
791796
// Use the first detection result matching our device type
792797
const match = detected.find(d => d.type === deviceType);
793798
if (!match?.states) {

src-admin/src/Tabs/ListDevices.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ import {
9797
setSmartName,
9898
} from '../Components/helpers/utils';
9999
import type { PatternControlEx, ListItem } from '../types';
100-
import SmartDetector from '../Devices/SmartDetector';
100+
import SmartDetector, { removeForeignAliasStates } from '../Devices/SmartDetector';
101101
import DialogEdit from '../Dialogs/DialogEditDevice';
102102
import DialogNew from '../Dialogs/DialogNewDevice';
103103
import LocalUtils from '../Components/helpers/LocalUtils';
@@ -1207,7 +1207,12 @@ export default class ListDevices extends Component<ListDevicesProps, ListDevices
12071207
_keysOptional: keys,
12081208
ignoreCache: true,
12091209
});
1210-
result?.forEach(device => devices.push(device as PatternControlEx));
1210+
result?.forEach(device => {
1211+
// #597/#536: drop indicator datapoints that leaked in from sibling
1212+
// channels of an alias-device grouping before they reach the UI.
1213+
removeForeignAliasStates(device, this.objects);
1214+
devices.push(device as PatternControlEx);
1215+
});
12111216
if (di % DETECT_CHUNK === DETECT_CHUNK - 1) {
12121217
await new Promise(resolve => setTimeout(resolve));
12131218
if (detectGen !== this.detectGeneration) {

0 commit comments

Comments
 (0)