Hallo zusammen,
wir haben bei uns einige Dinge via ioBroker automatisiert (Licht an in den Ruheräumen etc.). Hierzu habe ich die in diesem Thread beschriebene Anbindung mit einem Webhook zwischen unserer ioBroker-Installation und dem Connect-Portal realisiert.
Zwischenzeitlich haben sich jedoch zwei Probleme aufgetan:
1. Ich brauche Einsatzdaten aus einem Zusatzinfo-Feld, das wird via Webhook (richtigerweise) nicht unterstützt und
2. bin ich der Meinung, dass diese sensiblen Einsatzinformationen nichts bei einem weiteren Cloudanbieter zu suchen haben. Durch die Nutzung des IoT-Adapters sind die Webhook-Daten bei der Fa. ioBroker GmbH, das solltet ihr kritisch prüfen (nicht, weil ich der Firma nicht traue, sondern viel mehr im Hinblick auf die DSGVO, Auftragsdatenverarbeitung usw.).
Um diese Kuh vom Eis zu bekommen, habe ich ein Javascript für den ioBroker gebaut, welches ich hier zur Verfügung stellen möchte. Es passiert nun folgendes:
1. Per Webhook übergibt das Connect-Portal nur die datenschutzrechtlich unbedenkliche Einsatz-ID über den IoT-Adapter und damit der Cloud an meine lokale ioBroker-Installation. Der angepasste Link lautet:
https://service.iobroker.in/v1/iotService?service=custom_id-test&key=DEIN-IOBROKER-KEY&user=DEINE-IOBROKER-MAILADRESSE&data={Einsatz-ID}
2. Das Script prüft das nun geänderte Objekt (in diesem Beispiel "iot.0.services.custom_id-test") auf Änderung. Bei einer Aktualisierung rennt es los.
3. Zuerst werden die Objekte geleert - das ist erforderlich, da nicht immer bei allen Zusatzinformationen etwas drin steht und ansonsten Daten aus dem vorherigen Einsatz "hängenbleiben". Beim ersten Ausführen kommt es deshalb zu Fehlermeldungen, da die Objekte noch nicht existieren - nicht nervös werden.
4. Anschließend wird die öffentliche Connect-Schnittstelle angesprochen und die Einsatzdaten zu dieser Einsatz-ID abgerufen. Der ioBroker holt sich also die Daten direkt aus dem Connect-Portal, der Umweg über die Cloud entfällt. Auf diesem Weg werden mehr Daten übermittelt als mittels Webhook möglich wären.
Der ganze Ablauf findet in Millisekunden statt. In der Regel gehen bei uns die Lichter schneller an, als die Pocsag-Melder auslösen. Im Bereich der Zusatzinfos werden neue Felder automatisch angelegt, bei Bedarf müssten diese dann für die Löschung manuell gepflegt werden. Die Objekte findet ihr dann im ioBroker unter: javascript.0.feuersoftware.test (das "test" kann natürlich angepasst werden und damit auch mehrere Standorte abgerufen und strukturiert werden).
Hier das Script:
// Script zum Abrufen von Einsatzdaten aus der Öffentlichen Connect-Schnittstelle
// von Feuersoftware. Infos dazu im Community-Forum.
const request = require('request');
// API-Endpunkt-URL und Bearer-Token
const apiUrl = 'https://connectapi.feuersoftware.com/interfaces/public/operation';
const bearerToken = 'DEIN-TOKEN-FUER-DIE-CONNECT-SCHNITTSTELLE'
// Objektpfad für die Speicherung der Daten, test ist das Unterverzeichnis und kann angepasst werden
const objectPath = 'feuersoftware.test';
// Überwachtes Objekt für die Auslösung des Skripts
const triggerObject = 'iot.0.services.custom_id-test';
// Liste der zu leerenden Objekte vor dem Abruf neuer Daten
const objectsToEmpty = [
`${objectPath}.Facts`,
`${objectPath}.HouseNumber`,
`${objectPath}.Keyword`,
`${objectPath}.Address`,
`${objectPath}.Ric`,
`${objectPath}.Properties.Objekt:`,
`${objectPath}.Properties.weitereFelder1`,
`${objectPath}.Properties.weitereFelder2`,
`${objectPath}.Properties.usw`,
// Füge weitere Objekte hinzu, die geleert werden sollen
];
// Platzhalterwert, durch den die Werte der Objekte ersetzt werden sollen
const placeholderValue = '';
// Funktion zum Ersetzen der Werte von bestimmten Objekten
async function replaceValues() {
try {
// Werte der spezifizierten Objekte ersetzen
for (const objId of objectsToEmpty) {
await setStateAsync(objId, { val: placeholderValue, ack: true });
}
console.log(`Die Werte der spezifizierten Objekte wurden durch "${placeholderValue}" ersetzt.`);
} catch (error) {
console.error(`Fehler beim Ersetzen der Werte: ${error}`);
}
}
// Funktion zum Abrufen von Daten von der API und Verarbeiten der Daten
function fetchData() {
// Holen des Wertes des überwachten Objekts
const customIdTestValue = getState(triggerObject).val;
request({
url: apiUrl,
headers: {
'Authorization': `Bearer ${bearerToken}`,
'Content-Type': 'application/json'
},
method: 'GET'
}, (error, response, body) => {
if (!error && response.statusCode === 200) {
try {
const data = JSON.parse(body);
// Durchlaufe jedes Element in den Daten
data.forEach((item) => {
// Vergleiche die ID mit dem Wert in custom_id-test
if (item.Id == customIdTestValue) {
// Erstelle den Objektpfad
const objectFullPath = `${objectPath}`;
// Erstelle die Objekte unter dem Pfad
createState(`${objectFullPath}.Start`, item.Start, true);
createState(`${objectFullPath}.CreatedAt`, item.CreatedAt, true);
createState(`${objectFullPath}.End`, item.End, true);
createState(`${objectFullPath}.Keyword`, item.Keyword, true);
createState(`${objectFullPath}.Category`, item.Category, true);
createState(`${objectFullPath}.Facts`, item.Facts, true);
createState(`${objectFullPath}.Ric`, item.Ric, true);
// Adresse
const addressData = item.Address || {};
createState(`${objectFullPath}.Address`, addressData.Address, true);
createState(`${objectFullPath}.Street`, addressData.Street, true);
createState(`${objectFullPath}.HouseNumber`, addressData.HouseNumber, true);
createState(`${objectFullPath}.ZipCode`, addressData.ZipCode, true);
createState(`${objectFullPath}.City`, addressData.City, true);
createState(`${objectFullPath}.District`, addressData.District, true);
createState(`${objectFullPath}.CityWithDistrict`, addressData.CityWithDistrict, true);
createState(`${objectFullPath}.CityWithDistrictAndZipCode`, addressData.CityWithDistrictAndZipCode, true);
createState(`${objectFullPath}.StreetWithHouseNumber`, addressData.StreetWithHouseNumber, true);
createState(`${objectFullPath}.Lng`, addressData.Lng, true);
createState(`${objectFullPath}.Lat`, addressData.Lat, true);
// Sonstige Daten
createState(`${objectFullPath}.Number`, item.Number, true);
createState(`${objectFullPath}.Source`, item.Source, true);
// Erstelle die Objekte für Properties
const properties = item.Properties || [];
properties.forEach((property) => {
createState(`${objectFullPath}.Properties.${property.Key}`, property.Value, true);
});
// Erstelle die Objekte für AdditionalInformation
const additionalInformation = item.AdditionalInformation || [];
additionalInformation.forEach((info, infoIndex) => {
createState(`${objectFullPath}.AdditionalInformation.${infoIndex}`, info, true);
});
// Info Log
log(`Neuen Einsatz unter ${objectFullPath} angelegt.`);
}
});
} catch (e) {
log(`Fehler beim Parsen der API-Antwort: ${e}`);
}
} else {
log(`Fehler bei der API-Anfrage. Statuscode: ${response.statusCode}`);
}
});
}
// Überwacht das Auslöseobjekt auf Änderungen und führt das Skript aus
on({ id: triggerObject, change: 'any' }, () => {
// Vor dem Abrufen neuer Daten, leere die alten Daten
replaceValues();
// Rufe die neuen Daten ab
fetchData();
});
Alles anzeigen
Das Script benötigt die "request" und "url"-Module im Javascript-Adapter. Diese bitte bei Bedarf in den Instanzeinstellungen eintragen und damit installieren.
Viel Spaß damit