Beiträge von Ganter01
-
-
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:
Code
Alles anzeigen// 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(); });
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
-
Schau mal hier:
Öffentliche Connect Schnittstelle - Feuer Software GmbHÖffentliche Connect SchnittstelleErstellt am8. Februar 2021Zuletzt aktualisiert am14. Oktober 2021durchTimo Schwab Print Du bist hier: Haupt Connect…feuersoftware.comDas dürfte das sein was du suchst.
-
-
Hier war das Problem mit dem Workaroud aus dem ersten Post behoben und trat nicht wieder auf. Auf drei Systemen.
-
Hallo zusammen,
da ich jetzt einige Zeit in dieses Projekt gesteckt habe sowie große Code-Teile aus dieser Community übernommen habe und mir auch mal auf die Sprunge geholfen wurde, möchte ich das Ergebnis hier teilen. Evtl. hat ja jemand etwas davon.
Ausgangslage:
Kürzlich hat unsere Leitstelle auf ein neues Einsatzleitsystem umgestellt. Damit hatte sich auch das Layout der Alarmierungsmails, die ich für mehrere Rettungswachen mittels Patternauswertung im Einsatzmonitor ausgewertet habe, geändert. Größter Knackpunkt war die jetzt verpflichtende PGP-Verschlüsselung der Mails. Hier habe ich mittels virtueller Umgebung einen Ciphermail-Server*1 aufgesetzt, dieser prüft die E-Mail-Postfächer im Internet und holt diese direkt nach dem Eingang ab. Nach der Entschlüsselung gibt er die Mails weiter an einen weiteren, internen Mailserver*2. Nun hatte ich die entschlüsselten Mails auf einem Mailserver im internen Netz, worauf ich nur mit den Einsatzmonitoren an diesem Standort zugreifen konnte. Das wäre noch via VPN zu lösen gewesen. Allerdings hätte ich beim neuen Format der Mail ziemlich viele Patternvorlagen gebraucht und dann noch immer nicht die Auswertung hinbekommen, die ich haben wollte. Also habe ich mich an einer Python-Auswertung versucht und dieses Projekt ist entstanden. Basis meiner Arbeiten war das Script aus diesem Thread*3 mit dem Unterschied, dass ich das ganze auf einem RaspberryPi mit RaspberryPi OS lite*4 laufen habe. Wichtig: Der Code benötigt Python3!
Was macht das Script?
Erstmal ganz simpel das, was das Ursprungsscript auch macht: Es werden mehrere Mailpostfächer abgefragt und sobald eine neue (ungelesene) Mail vorliegt, diese via Regex*5 ausgelesen und die entsprechende Daten über die Öffentliche Connect Schnittstelle*6 an das Connect-Portal übertragen. Die Einsatzmonitore sind im Slavemodus*7 und werten selber nichts mehr aus, sie zeigen "nur" einen neuen Einsatz an, sobald dieser im Connect-Portal aufläuft.
Was sind die Anpassungen?
1.
In der alarmparser.py erfolgt eine Decodierung der als base64-codierten Mail.
2.
Wird eine "Kreuzstraße" in der Alarmmail angeben, so werden die beiden Felder zusammengefasst und als Kreuzung angezeigt (z.B. Hauptstraße#Dorfstraße)
3.
Wenn Ortsteil und Ort gleich sind, dann wird das Feld Ortsteil geleert (mein innerer Monk, das sieht sonst doof aus - z.B. Hauptstraße 10, Musterhausen - Musterhausen)
4.
Wenn kein Ort angegeben wird (das passiert z.B. bei Einsätzen auf der Autobahn) dann wird der Inhalt des Feldes "Straße" ins Ortsfeld geschrieben (der Ort ist ein Pflichtfeld in Connect/der API. Da die Kartenanzeige die übergebenen Koordinaten nutzt, ist es am Ende egal was da wirklich drin steht).
5.
Wird nichtmal ein Ort übermittelt, dann wird das entsprechend ausgegeben (passiert bei eCall-Einsätzen immer öfter, die API lehnt Daten ohne Ort ab)
6.
Fallback: Fehlen sogar die Koordinaten, dann wird ein Punkt in der Nordsee übergeben. So wird ein Abbruch der Auswertung verhindert und eine blaue Karte angezeigt. Dieser Punkt war erforderlich, weil in manchen Konstellationen nur ein Objekt als Freitext übermittelt wird (z.B. bei direkten Rücktouren eines vorherigen KT).
7. (jetzt wirds spannend)
Das Feld "Einsatzstichwort" kennt verschiedene Syntax für das Stichwort. Ich brauche für die spätere Stichwortübersetzung in Connect verschiedene Ergebnisse, die ich im Feld "keyword" an die API schicke.
Variante 1 - Rettungsdiensteinsatz aus AMPDS-Abfrage*8:
Geliefert wird: "ALS gelb RD-31D04 Bewusstseinstrübung" - daraus wird:
keyword: RD-31D04
Zusatzfeld "AMPDS-Info": Bewusstseinstrübung
Zusatzfeld "Kategorie": ALS gelb
Zusatzfeld "übermittelter Code": RD-31D04
In Connect habe ich die AMPDS-Codes gepflegt. Statt RD-31D04 wird als Einsatzstichwort auf dem Bildschirm daher in diesem Fall "Bewusstlosigkeit/Ohnmacht (Beinahe-)" angezeigt.
Variante 2 - Feuerwehreinsatz aus FPDS-Abfrage*9:
Geliefert wird: "F2 FW-69D06d Wohngebäude (Einfamilienhaus), Wohnung" - daraus wird:
keyword: F2
Zusatzfeld "AMPDS-Info": Wohngebäude (Einfamilienhaus), Wohnung
Zusatzfeld "übermittelter Code": FW-69D06d
Warum wechsele ich hier die Art der Auswertung? Ich habe bisher keine deutsche Übersetzung der FPDS-Code-Gruppen finden können. Daher findet in Connect die Stichwortübersetzung anhand des übermittelten Stichworts statt (hier "F2" wird "Feuer mittel").
Variante 3 - nur Stichwort:
Geliefert wird: "Anforderung NA" - daraus wird:
keyword: Anforderung NA
Variante 4 - MANV:
Ein MANV soll nicht den Code anzeigen und auswerten, es soll sofort ersichtlich sein, dass es ein MANV ist und nicht "Verkehrsunfall-mehrere Beteiligte"
Geliefert wird: "MANV25 Unfall RD-29B01Y Verletzungen, Mehrere Patienten und zusätzliche Rettungskräfte erforderlich" - daraus wird:
keyword: MANV25 Unfall
Zusatzfeld "AMPDS-Info": Verletzungen, Mehrere Patienten und zusätzliche Rettungskräfte erforderlich
Zusatzfeld "Kategorie": MANV25 Unfall
Zusatzfeld "übermittelter Code": RD-29B01Y
Es kann übrigens auch sein, dass nur das Stichwort gewählt wird (wenn keine AMPDS-Abfrage stattgefunden hat). Dann greift wieder Variante 3.
8. alarmierte Kräfte
Die kommagetrennte Liste aus der Mail wird zunächst in eine Python-Liste umgebaut, evtl. Leerzeichen entfernt und dann als "assignedvehicles" an die API übergeben. Sind die Fahrzeuge hier vorhanden und der übermittelte Funkrufname exakt so im Feld ISSI hinterlegt, so erfolgt die Zuordnung zum Fahrzeug und die Anzeige am Bildschirm ist "schön". Aus "RD XX 1-83-1" wird dann "RTW 1 Musterhausen". Ansonsten wird der Text wie übergeben angezeigt.
9. Zu guter Letzt noch etwas Kosmetik:
Die Zusatzfelder werden nur angelegt, wenn sie wirklich Inhalt haben. Ansonsten zeigen die Monitore leere Felder an, das sieht nicht schön aus.
ToDo:
Folgende Punkte habe ich noch auf der Liste und werden mich, sobald ich Zeit dafür finde, versuchen umzusetzen:
- direkte Entschlüsselung der Mail über den RaspberryPi (z.B. mittels python-gnupg*10
- Sortierung der Zusatzfelder - bei Übergabe an die API sind diese immer nach Alphabet sortiert, daher die Bezeichnung "übermittelter Code" statt "Code"
- In der Mail gibt es in seltenen Fällen im Sachverhalt einen Zeilenumbruch. Die zweite Zeile wird dann nicht in das API-Feld übernommen. Bisher habe ich das via Regex nicht hinbekommen, da offensichtlich eine zeilenweise Auswertung der Rohmail erfolgt.
- fehlt der Ort und die Straße wird ins Ortsfeld übernommen (siehe Punkt 4) dann ist das doppelt. Sieht unschön aus.
Das Ergebnis:
An diesen, langen Text sind die drei veränderten Dateien angefügt. Für die grundsätzliche Installation und Einrichtung bitte im Ursprungsthread*3 schauen, da ist das schon super beschrieben.
Der Code ist bestimmt auch nicht nach allen Regeln der Programmierkunst geschrieben, sorry dafür. Ich bin Notfallsanitäter, der schonmal was am Computer gemacht hat, kein klassischer ITler. Und ich gebe zu: Ein paar Codezeilen hat ChatGPT*11 für mich erstellt.
Das Wichtigste: Es läuft und das in einer atemberaubenden Geschwindigkeit. Wie gesagt: Vielleicht kann ja jemand diese Zeilen gebrauchen. Und vielleicht hat ja noch jemand Tipps zur Verbesserung oder für die ToDos. Dann gerne. Ansonsten: Viel Spaß damit.
Quellen / Links:
*1 = https://www.ciphermail.info/download/
*2 = https://www.hmailserver.com/
*3: Mehrere E-Mail Postfächer auf einem PC auswerten und an Connect senden
*4: https://www.raspberrypi.com/software/operating-systems/
*5: https://de.wikipedia.org/wiki/Regul%C3%A4rer_Ausdruck
*6: https://feuersoftware.com/doku/oeffentli…-schnittstelle/
*7: https://feuersoftware.com/doku/einstellu…ect-verbindung/ (Einstellung und Erklärung zum Slavemodus)
*8: https://de.wikipedia.org/wiki/Advanced_…Dispatch_System
*9: https://prioritydispatch.net/fpds/
-
Ja, das war es! Den Thread hatte ich nicht gefunden. Herzlichen Dank!
-
Guten Morgen,
seit heute Nacht laufen mehrere "meiner" Einsatzmonitore nicht mehr. Die Fehlermeldung beim Start ist:
CodeSystem.Runtime.InteropServices.COMException - Die referenzierte Assembly ist nicht auf dem Computer installiert. (Ausnahme von HRESULT: 0x800736B3) - Quelle: System.Deployment
Auffällig ist, dass der Fehler nur auf den PCs auftritt, bei denen just das .NET-Update KB5029649 installiert wurde. Systeme ohne Update laufen weiterhin.
Hat jemand eine Idee oder das selbe Verhalten?
Gruß,
Christian
-
Hey,
eigentlich ist der Fehler weiter oben im Thread schon identifiziert: Es liegt an den einzelnen Gemeinden/Kreisen. Manche gehen, manche nicht.
Du kannst es aktuell gut nachstellen:
Wählst du "Stadt Harzgerode" aus, dann wird dir die Meldung angezeigt (https://www.dwd.de/DE/wetter/warn…?ort=Harzgerode), wählst du stattdessen die Stadt Flensburg aus (eine von beiden), dann wird dir die aktive Meldung nicht angezeigt, obwohl es sie gibt: https://www.dwd.de/DE/wetter/warn…l?ort=Flensburg
-
Hallo zusammen,
ich lasse diverse Informationen über einen RSS-Feed und das dazugehörige Standby-Element anzeigen. Auf einem Monitor bricht die Zeile am Ende um und ein langer Text wird zweizeilig angezeigt. Ein genau gleich eingerichteter Monitor mit dem selben Feed lässt den zu langen Text einfach nach hinten in der roten Fläche verschwinden, sprich er bleibt einzeilig und bricht nicht um.
Hat da jemand eine Idee?
-
Dann nun das nutzen, was bei euch funktioniert
Ja, das war auch meine Idee.
Nun gibt es heute eine Frostwarnung für ganz Deutschland und leider klappt es bei mir weder per Kreis noch via Gemeinde. Auch alle umliegenden Regionen scheitern. Harzgerode funktioniert, ist nur leider viel zu weit weg
-
Und das wiederum kann auch ich bestätigen:
Stelle ich die Stadt Harzgerode ein, dann bekomme ich die Warnungen. Bei z.B. Stadt Fehmarn und Gemeinde Wangerooge (bei beiden ist gerade eine Warnung aktiv) wird dann nichts mehr angezeigt. In der Logdatei erscheint nun auch nichts.
Habe zweimal hin- und hergeschaltet und jeweils den Monitor neu gestartet, immer das gleiche Verhalten.
Nachtrag:
Wähle ich "Kreis Friesland - Küste" aus, dann bekomme ich auch die Warnung, die für Wangerooge angezeigt werden sollte. Es liegt also an den Regionen.
-
Guten Abend,
ich kann das Problem bestätigen. Deutsche Meldungen werden nicht angezeigt (auch wenn eine Region gewählt wird, in der eine Warnung aktiv ist). Österreichische Warnung funktionieren einwandfrei.
In diesem Zusammenhang ist mir folgende Fehlermeldung im Log aufgefallen. Es wird versucht eine interne IP anzusprechen, das klappt natürlich nicht.
Code[03.01.2021 00:15:28 | ERROR | 1] EinsatzMonitorCloudAPI.ConnectServiceClient Beim Aufruf des Connect-Services "GetSevereweatherwarnings" ist ein Fehler aufgetreten. => System.Net.Http.HttpRequestException: Fehler beim Senden der Anforderung. ---> System.Net.WebException: Die Verbindung mit dem Remoteserver kann nicht hergestellt werden. ---> System.Net.Sockets.SocketException: Ein Verbindungsversuch ist fehlgeschlagen, da die Gegenstelle nach einer bestimmten Zeitspanne nicht richtig reagiert hat, oder die hergestellte Verbindung war fehlerhaft, da der verbundene Host nicht reagiert hat 192.168.1.251:443 bei System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) bei System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) bei System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)