Smarte Rauchmelder von X-Sense ohne externe Cloud

Rauchmelder sind inzwischen fast überall auch im privaten Bereich Pflicht – und das aus gutem Grund, denn sie retten im Ernstfall Leben.

Gerade in mehrstöckigen Einfamilienhäusern werden zusätzlich vernetzte Rauchmelder empfohlen: Löst einer der Melder aus, ertönt der Alarm auf allen Geräten. So bekommt man beispielsweise einen Alarm im Keller auch dann mit, wenn man sich gerade im Dachgeschoss aufhält. Ein logischer nächster Schritt ist die Einbindung der Rauchmelder in das eigene Smarthome.

Die meisten am Markt erhältlichen Systeme für smarte Rauchmelder setzen allerdings eine externe Cloud des Herstellers voraus, ohne die das System teils gar nicht oder nur eingeschränkt funktionsfähig ist. Eine erfreuliche Ausnahme bietet hier das System von X-Sense mit der Basisstation SBS50: Für die Einrichtung ist zwar eine Verbindung zur X-Sense-Cloud erforderlich, diese kann jedoch nach dem Aktivieren von MQTT wieder getrennt werden. Dank MQTT ist anschließend auch die Einbindung in Smarthome-Systeme wie zum Beispiel ioBroker problemlos möglich.

Hinweise

Achtung

Dieser Beitrag wurde nach bestem Wissen und Gewissen erstellt. Er stellt jedoch keine verbindliche Rechts- oder Sicherheitsberatung dar; alle Angaben erfolgen ohne Gewähr.
Es handelt sich ausdrücklich nicht um Werbung, sondern um einen persönlichen Erfahrungsbericht.

Rauchmelder haben eine begrenzte Lebensdauer! Die Herstellerangaben zum Austauschzeitpunkt – häufig zehn Jahre nach dem Herstellungsdatum – sind unbedingt zu beachten. Die Melder sollten spätestens zum angegebenen Zeitpunkt ersetzt werden, da ihre zuverlässige Funktion danach nicht mehr gewährleistet ist.

Die hier beschriebenen Rauchmelder von X-Sense tragen jeweils einen Aufkleber mit dem Herstellungsdatum (Date of Manufacture) und dem empfohlenen Austauschdatum (Replacement Date).

Verwendete Geräte

  • X-Sense Basisstation SBS50
  • X-Sense Rauchmelder XS01-M
  • X-Sense CO-Melder XC01-M

Erstinstallation

Hinweis

Für die Erstinstallation werden eine Online-Verbindung zur X-Sense-Cloud und ein Konto bei X-Sense benötigt.
Konto und Cloud sind nur für die Konfiguration nötig – die Cloud-Verbindung kann anschließend wieder getrennt werden.

Als Erstes laden wir die App X-Sense Home Security aus dem Google Play Store beziehungsweise dem Apple App Store herunter und installieren sie. In der App erstellen wir ein Konto oder melden uns mit einem bestehenden Konto an.

Wichtig

Die Basisstation benötigt ein 2,4-GHz-WLAN – ein 5-GHz-WLAN wird nicht unterstützt.

Nun schließen wir die Basisstation SBS50 an die Stromversorgung an, tippen in der App auf das +-Symbol und scannen den Barcode auf ihrer Unterseite.

Anschließend folgen wir den Anweisungen in der App, um die Basisstation per Bluetooth mit dem Smartphone zu koppeln. Dabei geben wir die SSID und das Passwort unseres WLANs ein und bestätigen die Eingabe.

Die Basisstation bestätigt den Verbindungsaufbau zunächst mit der Sprachausgabe “Connecting to WiFi” und kurz darauf mit “WiFi connected”.

Die Rauchmelder fügen wir ebenfalls nach den Anweisungen der App hinzu.
Achtung: Der jeweilige Melder piept dabei zweimal laut. Zum Abschluss bestätigt die Basisstation die Einbindung mit der Ansage “Device added”.

Tipp

In der App lässt sich bei jedem Gerät unter Geräteinformationen das auf dem Melder aufgedruckte Austauschdatum hinterlegen.

Wichtig

Damit die Basisstation MQTT unterstützt, muss ihre Firmware aktuell sein. Wir führen daher in der App unter Geräteinformationen die für die Basisstation angebotene Firmwareaktualisierung durch – das kann etwas dauern.

Verbindung per MQTT einrichten

In der App öffnen wir die Geräteeinstellungen der Basisstation und wählen dort den Punkt Mit Home Assistant verbinden.

Tipp

Der Menüpunkt heißt zwar Home Assistant, richtet aber lediglich eine Verbindung zu einem MQTT-Broker ein – die Einbindung funktioniert daher ebenso mit ioBroker und anderen Systemen.

Hier tragen wir die IP-Adresse unseres MQTT-Brokers – ein Hostname wird nicht akzeptiert – sowie den Port ein, in der Regel 1883. In die Felder Konto und Passwort gehören die Zugangsdaten für die Anmeldung am MQTT-Broker.

Sind die Daten korrekt, verbindet sich die Basisstation mit dem MQTT-Broker und zeigt den MQTT-Status als verbunden an.

Wie ein MQTT-Broker eingerichtet und in ioBroker eingebunden wird, habe ich bereits in den Beiträgen Projekt HomePi - MQTT Broker Mosca und Einbindung von MQTT in ioBroker beschrieben.

MQTT-Topics

Die Daten der Melder werden über Topics wie homeassistant/binary_sensor/SBS5016296EDB_00000001/SBS5016296EDB_00000001_smokealarm veröffentlicht. Der Smarthome-Client – zum Beispiel ioBroker – sollte daher das Topic homeassistant/# abonnieren, um alle Statusmeldungen der Basisstation zu empfangen.

Der MQTT-Client der Basisstation sollte daher für den Topic-Baum homeassistant/ sowohl Veröffentlichungs- als auch Abonnementrechte besitzen.

Trennen der Cloud-Verbindung

Sobald alle Melder an der Basisstation angemeldet sind und ihre Daten zuverlässig per MQTT liefern, kann die Verbindung zur X-Sense-Cloud getrennt werden – etwa wenn man seine Daten lieber für sich behalten möchte.

Warnung

Mit dem Trennen der Cloud-Verbindung wird auch die Verbindung zur App unterbrochen. Alarmbenachrichtigungen der App stehen anschließend nicht mehr zur Verfügung.

Damit es später keine bösen Überraschungen gibt, reservieren wir der Basisstation zunächst im Router per DHCP eine feste IP-Adresse. Anschließend sperren wir im Router den Internetzugriff für das Gerät beziehungsweise dessen IP-Adresse. Die Basisstation kommuniziert dann nur noch im lokalen Netzwerk – unter anderem mit dem MQTT-Broker –, während die lokale Alarmierung der vernetzten Rauchmelder weiterhin funktioniert.

Stromversorgung der Basisstation

Die Basisstation wird über USB-C mit Strom versorgt. Ein passendes Netzteil ist im Lieferumfang enthalten.

Meinen Messungen zufolge benötigt die Station im Normalbetrieb bei einer Versorgungsspannung von 5,1 V lediglich etwa 50 mA. In kurzen Spitzen – vermutlich während der Kommunikation – steigt die Stromaufnahme auf bis zu 120 mA.

Der Strombedarf ist damit erfreulich gering. Sofern ein freier USB-Port des Routers dauerhaft die erforderliche Spannung und Stromstärke bereitstellt, kann er die Basisstation versorgen und das zusätzliche Netzteil überflüssig machen.

Dabei ist allerdings zu beachten, dass die Basisstation bei einem Neustart oder Ausfall des Routers ebenfalls vorübergehend nicht mit Strom versorgt wird. Die lokale Alarmierung bleibt währenddessen erhalten, da die Melder unabhängig von der Basisstation miteinander kommunizieren. Lediglich die von der Basisstation bereitgestellten Funktionen, darunter die MQTT-Anbindung, stehen in dieser Zeit nicht zur Verfügung.

Auswertung der Daten in ioBroker

In ioBroker werden die Daten zunächst als JSON gespeichert. Für battery, lifeend, online, smokealarm und smokefault entstehen jeweils die States config und state. config enthält allgemeine Informationen zum jeweiligen Datenpunkt, während state dessen aktuellen Status enthält.

Beispiel mqtt.0.homeassistant.binary_sensor.SBS5016296EDB_00000002.SBS5016296EDB_00000002_smokealarm.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "Smoke Status",
"unique_id": "SBS5016296EDB_00000002_smokealarm",
"state_topic": "homeassistant/binary_sensor/SBS5016296EDB_00000002/SBS5016296EDB_00000002_smokealarm/state",
"device_class": "smoke",
"payload_on": "Detected",
"payload_off": "Cleared",
"value_template": "{{ value_json.status }}",
"qos": 1,
"retain": true,
"device": {
"identifiers": [
"SBS5016296EDB_00000002"
],
"name": "Smoke Alarm(SBS5016296EDB_00000002)",
"manufacturer": "X-SENSE",
"model": "XS01-M",
"sw_version": "v1.9.0"
}
}
Beispiel mqtt.0.homeassistant.binary_sensor.SBS5016296EDB_00000002.SBS5016296EDB_00000002_smokealarm.state
1
2
3
{
"status": "Cleared"
}

Mit dem folgenden ioBroker-Skript lassen sich die Daten jedes Melders in gut auswertbare States unter 0_userdata.0.x-sense.* überführen. Das Skript erkennt die vom Melder gelieferten Informationen automatisch und legt dafür passende ioBroker-States an.

Der Konfigurationsabschnitt sollte geprüft und gegebenenfalls angepasst werden. Der restliche Code kann unverändert übernommen werden.

X-Sense-Skript für ioBroker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* ioBroker-Skript zur Umwandlung der X-Sense-MQTT-Daten in übersichtliche ioBroker-States.
*
* (c) 2026 Peter Müller <peter@crycode.de>
*/

/**
* Muster für MQTT-States, die für die Auswertung beobachtet werden sollen.
* Ein `*` am Ende ist erforderlich.
*/
const stateIdBase = 'mqtt.0.homeassistant.binary_sensor.SBS5016296EDB_*';

/**
* ID-Bereich, unter dem die aufbereiteten States angelegt werden sollen.
*/
const userdataIdBase = '0_userdata.0.x-sense';

/**
* Mapping von Geräte-IDs zu Namen der Geräte.
*/
const deviceNames = {
SBS5016296EDB_00000001: 'Rauchmelder Wohnzimmer',
SBS5016296EDB_00000002: 'Rauchmelder Schlafzimmer',
SBS5016296EDB_00000003: 'Rauchmelder Kinderzimmer',
SBS5016296EDB_00000004: 'Rauchmelder Flur',
SBS5016296EDB_00000005: 'CO-Melder Keller',
};

/******************************************************************************/
/* Config Ende */
/******************************************************************************/

const mqttStateIdPattern = `${stateIdBase}.*.state`;
const mqttConfigIdPattern = `${stateIdBase}.*.config`;
const stateIdPrefix = stateIdBase.slice(0, -1);
const configuredDevices = new Set();
const configuredStates = new Set();
const pendingDeviceCreations = new Map();
const pendingStateCreations = new Map();

function globToRegExp(pattern) {
const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`^${escapedPattern.replace(/\*/g, '[^.]*')}$`);
}

function parseJsonState(id) {
const state = getState(id);
if (!state || state.val === null || state.val === undefined) {
return null;
}

if (typeof state.val === 'object') {
return state.val;
}

try {
const value = JSON.parse(state.val);
return value && typeof value === 'object' ? value : null;
} catch (error) {
log(`Ungültiges JSON in ${id}: ${error.message}`, 'warn');
return null;
}
}

function getMqttStateInfo(id) {
if (!id.startsWith(stateIdPrefix) || !id.endsWith('.state')) {
return null;
}

const parts = id.split('.');
const deviceId = parts.at(-3);
const entityId = parts.at(-2);
const entityPrefix = `${deviceId}_`;

if (!entityId.startsWith(entityPrefix)) {
return null;
}

const stateName = entityId.slice(entityPrefix.length);
if (!stateName) {
return null;
}

return {
configId: `${id.slice(0, -'.state'.length)}.config`,
deviceId,
deviceObjectId: `${userdataIdBase}.${deviceId}`,
stateName,
targetId: `${userdataIdBase}.${deviceId}.${stateName}`,
};
}

function getBinaryValue(status, config) {
if (typeof status === 'boolean') {
return status;
}

if (Object.prototype.hasOwnProperty.call(config, 'payload_on') && String(status) === String(config.payload_on)) {
return true;
}

if (Object.prototype.hasOwnProperty.call(config, 'payload_off') && String(status) === String(config.payload_off)) {
return false;
}

return null;
}

async function ensureTargetDevice(info, config) {
if (configuredDevices.has(info.deviceId)) {
return;
}

if (!pendingDeviceCreations.has(info.deviceId)) {
const deviceName = deviceNames[info.deviceId] || config.device?.name || info.deviceId;
const creation = extendObjectAsync(info.deviceObjectId, {
type: 'device',
common: {
name: deviceName,
},
native: {},
}).finally(() => pendingDeviceCreations.delete(info.deviceId));

pendingDeviceCreations.set(info.deviceId, creation);
}

await pendingDeviceCreations.get(info.deviceId);
configuredDevices.add(info.deviceId);
}

async function ensureTargetState(info, config) {
await ensureTargetDevice(info, config);

if (configuredStates.has(info.targetId)) {
return;
}

if (!pendingStateCreations.has(info.targetId)) {
const creation = createStateAsync(info.targetId, undefined, false, {
name: config.name || info.stateName,
type: 'boolean',
role: 'indicator',
read: true,
write: false,
}).finally(() => pendingStateCreations.delete(info.targetId));

pendingStateCreations.set(info.targetId, creation);
}

await pendingStateCreations.get(info.targetId);
configuredStates.add(info.targetId);
}

async function syncMqttState(id) {
const info = getMqttStateInfo(id);
if (!info) {
return;
}

const config = parseJsonState(info.configId);
if (!config) {
return;
}

await ensureTargetState(info, config);

const state = parseJsonState(id);
if (!state || !Object.prototype.hasOwnProperty.call(state, 'status')) {
return;
}

const value = getBinaryValue(state.status, config);
if (value === null) {
log(`Unbekannter Status "${state.status}" in ${id}`, 'warn');
return;
}

await setStateAsync(info.targetId, value, true);
}

async function handleMqttState(id) {
try {
await syncMqttState(id);
} catch (error) {
log(`Fehler beim Verarbeiten von ${id}: ${error.message}`, 'error');
}
}

on({ id: globToRegExp(mqttStateIdPattern), change: 'ne' }, (obj) => handleMqttState(obj.id));
on({ id: globToRegExp(mqttConfigIdPattern), change: 'ne' }, (obj) => handleMqttState(obj.id.replace(/\.config$/, '.state')));

$(mqttConfigIdPattern).each((id) => handleMqttState(id.replace(/\.config$/, '.state')));

Mit diesen States lassen sich anschließend weitere Automatisierungen, Visualisierungen und Benachrichtigungen in ioBroker erstellen.

Wandhalterung der Basisstation

Die Basisstation besitzt ab Werk keine Vorrichtung zur Wandmontage, sondern ist für das Aufstellen auf einer ebenen Fläche vorgesehen.

Wer über einen 3D-Drucker verfügt, kann mithilfe dieses Modells auf Printables eine passende Wandhalterung selbst anfertigen.