Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MQTT mit Namensausgabe #73

Closed
TVR-X opened this issue Dec 9, 2021 · 14 comments
Closed

MQTT mit Namensausgabe #73

TVR-X opened this issue Dec 9, 2021 · 14 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@TVR-X
Copy link

TVR-X commented Dec 9, 2021

Hallo,
ich überlege das Plugin zu nutzen um damit Daten in die InfluxDB schreiben zu lassen (über telegraf und das MQTT Plugin)
Dabie ist mir aufgefallen das per MQTT die Namen der Geräte nicht übertragen werden. Kann man das irgendwo aktivieren oder muss ich den weg über die rest API gehen und dann jedes Gerät einzeln abfragen da ich ja hier kein "gesamt" Ergebnis bekomme.

@mdzio mdzio added enhancement New feature or request question Further information is requested labels Dec 11, 2021
@mdzio
Copy link
Owner

mdzio commented Dec 11, 2021

Zurzeit werden die Namen der Geräte nicht über MQTT übertragen.
Soll denn der Name des Gerätes im MQTT-Topic auftauchen?
Oder soll er in der Payload enthalten sein?
Oder sollen alle Geräteeigenschaften in einem separaten Topic übertragen werden?

@TVR-X
Copy link
Author

TVR-X commented Dec 13, 2021

Im Topic steht ja aktuell die SN und eine ebene tiefer der Kanal, hier wärs natürlich übersichtlich aber bei Leuten die hier schon Daten auswerten dürfte das zu Problemen führen.

Wenn es als Value in jedem Kanal eingefügt wird hätte man zwar einen Datensatz der so nicht bei eq3 Dokumentiert ist aber ich denke damit würde man "kompatibel" zu bestehenden Systemen bleiben und im Falle der InfluxDB 2.x könnte man sich die Daten per telegraf holen lassen und alle infos die sich auf einer ebene Befinden lassen sich entsprechen sortieren das man immer wüsste welche Werte zu welchen Namen gehören

Wenn ich letzteres richtig verstanden hab gäbs dann direkt unter SN neben den Kanalnummern ein eigenes Topic, wäre in meinem Fall nicht so ideal da ich gerade bei den HMIPW-DRI32 den einzelnen Eingängen zum Beispiel Namen gegeben hab die man erst wieder eine ebene höher zuordnen müsste.

@sikousikou
Copy link

ich habe den CCU Jack installiert um per MQTT Daten zwischen raspberrymatic und Nodered + Influxdb zu tauschen anstatt die Nutzung von "node-red-contrib-ccu" es funktioniert prima aber ohne Namen und nur durch die Seriennummer die Geräte zu identifizieren und zuordnen ist sehr Mühsam, die Übertragung der Name anstatt die Seriennummer im topic wäre super

@AlexaMiller
Copy link

Bitte nicht anstelle von. Zusätzlich gerne, aber bitte bedenken - Gerätenamen können sich ändern, Verwendungszwecke können variieren - Seriennummern bleiben.

@sikousikou
Copy link

ja hast du recht zusätzlich oder konfigurierbar machen dass man wählen kann zwischen Name und Seriennummer

@sikousikou
Copy link

ich bin gerade beim umstellen von node-red-contrib-ccu auf CCU Jack bei meinem nodered und habe gemerkt dass man die Gerätenamen braucht nicht anstelle vom Seriennummer sondern anstelle vom Kanal nummer weil bei alle Geräte umbenennt man die Kanäle einzeln nach bedarf deswegen wäre ein MQTT Topic in der format: device/status/Seriennr./Kanalname:kanalnr./Parametername passend weil auch wenn mann die Kanalname ändert ist noch identifizierbar durch seriennummer und die kanalnummer im topic

@TVR-X
Copy link
Author

TVR-X commented Apr 22, 2022

So mein umbau steht ab Juni an, jetzt wollt ich mir mal nochmal meine Überlegungen beisteuern

device/status/Seriennr/"Titel":0/
device/status/Seriennr/"Titel":1/
usw.

wäre als option zum umschalten im Webinterface sicher gut aber für alle die ccu-jack schon einsetzen würde es bedeuten das se alles ändern müssen was hinten dran hängt. Bei der Option müssen dann alle Kanäle sauber benamt werden.

Ich könnte auch sehr gut mit:
device/status/Seriennr/Titel <-device name
device/status/Seriennr/0/Titel <-chanel name
device/status/Seriennr/0/LOW_BAT <-rest wie gehabt
...
device/status/Seriennr/1/Tietel <-chanel name
device/status/Seriennr/1/Motion <-rest wie gehabt
...

leben, dann könnte mans als update einpflegen ohne das jemand in den nachfolgenden Programmen was ändern muss und evtl. die Option im Webinterface "verwende Kanalnamen zusätzlich zu Kanalnummern"

mit dem zusätzlichen name Topic macht man bestehende Installationen nicht kaputt und wenn man sich die Infos holt kann man z.B. in NodeRed jederzeit aus dem gelsenen Info Paket auslesen um was es sich handelt ohne das man die SN prüfen muss.
im Webinterface stünden ja schon die Titel drin
Screenshot 2022-04-22 104611

@mdzio
Copy link
Owner

mdzio commented Apr 24, 2022

Folgendes zu Deinen Überlegungen:

Titel als Bestandteil des Topics

device/status/<Seriennr.>/<Gerätetitel>:0/...

Wenn die Idee fortgeführt wird, müsste auch der Kanaltitel auftauchen. Die Seriennummer und den Kanalindex würde ich entfernen:

device/status/<Gerätetitel>/<Kanaltitel>/...

Der CCU-Jack dockt direkt an die Schnittstellenprozesse (BidCos-RF, usw.) der CCU an. Die Schnittstellenprozesse kennen keine Anzeigenamen/Titel, sie arbeiten mit Seriennummern und Kanalindizes. Dies wurde so für die MQTT-Topics und die Pfade der REST-API übernommen, damit die Abbildung einfach und eindeutig ist.

Die Anzeigenamen/Titel werden erst innerhalb eines anderen Prozesses der CCU (Logikschicht/ReGaHss) hinzugefügt. Falls also Titel in den MQTT-Topics oder den REST-API-Pfaden auftauchen sollen, wird das kompliziert und aufwändig zu implemetieren. Zudem müssen die Anzeigenamen erst aus der ReGaHss ausgelesen werden und stehen nicht sofort zur Verfügung. Vielleicht ist bereits aufgefallen, dass nach einem Neustart des CCU-Jacks es einige Zeit dauert, bis der Titel bei den Eigenschaften angezeigt wird.

Diese Variante würde ich also ersteinmal nicht umsetzen.

Titel und andere Informationen in einem separaten Topic

device/status/<Seriennr.>/Titel → Gerätetitel
device/status/<Seriennr.>/<Kanalindex>/Titel → Kanaltitel
device/status/<Seriennr.>/<Kanalindex>/<Parameter> → wie bisher

Da würde ich auch noch weiter gehen und alle vorhandenen Eigenschaften mit aufnehmen. Zudem würde ich das Topic noch etwas anpassen (z.B. meta für Metainformationen über den Datenpunkt):

device/meta/<Seriennr.> → Alle Geräteeigenschaften als JSON
device/meta/<Seriennr.>/<Kanalindex> → Alle Kanaleigenschaften als JSON
device/meta/<Seriennr.>/<Kanalindex>/<Parameter> → Alle Parametereigenschaften als JSON
sysvar/meta/<ISE-ID> → Alle Eigenschaften der Systemvariable als JSON

Wann aber auf den Topics device/status/<Seriennr.>/1/MOTION und device/meta/<Seriennr.>/1/MOTION gesendet wird, ist zeitlich unabhängig. Auf device/status könnten schon Wertänderungen gesendet worden sein, bevor auf device/meta das erste Mal Informationen gesendet worden sind.

Wäre das für Eure Anwendungsfälle hilfreich?

@ptweety
Copy link

ptweety commented Apr 24, 2022

Hallo @TVR-X,

wenn du das Ganze über NodeRed machen willst, dann kann ich dir einen function node bereitstellen, der MQTT-Nachrichten erwartet und einen fertigen payload zur Übergabe an die influxdb v2 zurück gibt. Im Inneren hat der einen Cache und ruft bei Bedarf die CCU-jack api ab, um weitere Infos nachzuladen.

Die Nachricht sieht dann so aus:

{
  "topic": "device/status/000EDxxxxxx/0/OPERATING_VOLTAGE",
  "measurement": "HmIP-STHO/OPERATING_VOLTAGE",
  "payload": [
    {
      "payload": 2.6
    },
    {
      "ccu": "ccu3-webui.local",
      "iface": "HmIP-RF",
      "device": "000EDxxxxxx",
      "deviceName": "HmIP-STHO Temperatursensor",
      "deviceType": "HmIP-STHO",
      "channel": "000EDxxxxxx:0",
      "channelName": "HmIP-STHO Temperatursensor:0",
      "channelType": "MAINTENANCE",
      "channelIndex": 0
    }
  ],
  "_msgid": "59f03dcbb973158c"
}

Und so kommen dann die Daten an:

Bildschirmfoto 2022-04-24 um 18 32 25

Der node selber ist hier:

[
    {
        "id": "2cd26313f07a36cf",
        "type": "function",
        "z": "c097cad3.926648",
        "name": "Jack Cache",
        "func": "const jackAPI = context.get('util').jackAPI;\nconst getLinks = context.get('util').getLinks;\n\n/**\n * status: indicate working state\n */\nnode.status({\n    fill: 'yellow',\n    shape: 'dot',\n    text: 'working',\n});\n\n/**\n * cache: add root, domains & vendor/config information\n */\nconst root = await jackAPI('/', 1000 * 60 * 60 * 24); // 1d\n\nif (root) {\n    await Promise.all(getLinks(root, 'domain').map(async (link) => {\n        const ret = await jackAPI(link.url, 1000 * 60 * 60 * 4); // 4h\n    }));\n}\n\nconst config = await jackAPI('/~vendor/config/~pv', 1000 * 60 * 60 * 4); // 4h\n\n/**\n * msg: split, analyze and add additional data (either from cache or api call)\n */\n\nlet message = {}\nlet ids = msg.topic.split('/');\n\nswitch (ids[0]) {\n    case 'device': {\n        // mqtt topic example: device/status/${device}/${channel}/${datapoint}\n        \n        const req = [\n            { url: `/device/${ids[2]}`,                     ttl: 1000 * 60 * 60 * 2 }, // 2h\n            { url: `/device/${ids[2]}/${ids[3]}`,           ttl: 1000 * 60 * 60 * 1 }, // 1h\n            { url: `/device/${ids[2]}/${ids[3]}/${ids[4]}`, ttl: 1000 * 60 * 10 }]; // 10min\n            \n        const [device, channel, datapoint] = await Promise.all(req.map(async (line) => {\n            return await jackAPI(line.url, line.ttl);\n        }));\n\n        setTimeout(() => {let self = node; self.status({})}, 1000);\n        return {\n            topic: msg.topic,\n            measurement: device.type.charAt(0).toUpperCase()\n                + device.type.charAt(1).toLowerCase()\n                + device.type.slice(2)\n                + '/' + datapoint.id,\n            payload: [{\n                payload: msg.payload.v\n            },{\n                ccu: config.v.CCU.Address,\n                iface: device.interfaceType,\n                device: device.address,\n                deviceName: device.title.charAt(0).toUpperCase()\n                    + device.title.charAt(1).toLowerCase()\n                    + device.title.slice(2),\n                deviceType: device.type.charAt(0).toUpperCase()\n                    + device.type.charAt(1).toLowerCase()\n                    + device.type.slice(2),\n                channel: channel.address,\n                channelName: channel.title,\n                channelType: channel.type,\n                channelIndex: channel.index,\n                room: channel['~links'].filter( it => it.rel == 'room').map(x => x.title)[0],\n                function: channel['~links'].filter( it => it.rel == 'function').map(x => x.title)[0],\n            }]\n        };\n        //break;\n    }\n    default: {\n        setTimeout(() => {let self = node; self.status({})}, 1000);\n        return msg;\n    }\n}\n\n/**\n * status: return to state\n */\nsetTimeout(() => {let self = node; self.status({})}, 1000);\n\n/**\n * msg: output\n */\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n\n//node.status({\n//    fill: 'blue',\n//    shape: 'dot',\n//    text: 'configuration required',\n//}); // delete me\n            \nnode.status({\n    fill: 'green',\n    shape: 'dot',\n    text: 'configured',\n});\n\n// Please enter your <ip>, <user> & <password> below\n\n// IMPORTANT: if you use https on port 2122\n// please change <proto> & <port> as well\n// Also, you must include propper <key> & <cert>\n// This might be self-signed ones from ccu-jack\n\nconst config = {\n    cache: 'jack-cache',\n    proto: 'http',\n    ip: 'ccu-jack.local',\n    port: 2121,\n    user: '...',\n    password: '...',\n    key: `-----BEGIN RSA PRIVATE KEY-----\n-----END RSA PRIVATE KEY-----`,\n    cert: `-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----`,\n}\n\n// no changes below this comment required\n\n//context.set('jack', config);\n\nlet cache = context.get(config.cache) || {};\n\nconst util = {\n    jackAPI: async (url, ttl) => {\n        const ts = Date.now();\n        if ( !cache[url] || cache[url].ts < (ts - ttl) ) {\n            try {\n                const { data } = await axios.get(url, {\n                    baseURL: `${config.proto}://${config.ip}:${config.port}`,\n                    auth: { username: config.user, password: config.password },\n                    httpsAgent: new https.Agent({ rejectUnauthorized: false, key: config.key, cert: config.cert }),\n                }).catch( (err) => { } ); // node.warn({url: url, error: err, ...msg})\n                cache[url] = data;\n                cache[url].ts = ts;\n                cache[url].ttl = ttl;\n                context.set(config.cache, cache);\n                cache = context.get(config.cache);\n            } catch(error) {}\n        }\n        return cache[url] || {};\n    },\n\n    getLinks: (obj, rel, ...args) => {\n        let ret = [];\n\n        obj['~links']\n        .sort((a, b) => { return a.href.localeCompare(b.href) })\n        .filter( link => link.href !== '..')\n        .filter( link => link.href !== '$MASTER')\n        .filter( link => rel !== null ? link.rel === rel : true)\n        .forEach( (link, ind, arr) => {\n            ret.push({\n                url: args.length > 0 ? '/' + args[0] + '/' + link.href : '/' + link.href,\n                request: args.length > 1 ? args[1] : link.href,\n            });\n        })\n        \n        return ret;\n    }\n}\n\ncontext.set('util', util);",
        "finalize": "",
        "libs": [
            {
                "var": "axios",
                "module": "axios"
            },
            {
                "var": "https",
                "module": "https"
            }
        ],
        "x": 790,
        "y": 240,
        "wires": [
            [
                "dd5858b7f61dba08",
                "2d752ee6.b38eda"
            ]
        ],
        "info": "## CCU-Jack Cache\n\nMit diesem Knoten werden eingehende MQTT-Nachrichten des [CCU-Jack](https://github.com/mdzio/ccu-jack) um weitere Informationen zu dem jeweiligen\n\n - Gerät\n - Kanal\n - Datenpunkt\n \nangereichert. Aus der ursprünglichen Nachricht `msg` wird das `msg.topic` zur Identifikation der benötigten Informationen herangezogen, zerlegt und in einzelne Abfragen an den CCU-Jack umgewandelt. Die Antworten werden für eine festgelegte Zeit zwischengespeichert und bei einer späteren Abfrage ggfs. genutzt.\n\n### Konfiguration\n\nIm **Start**-Tab kann die Konfiguration eingetragen werden:\n\n```\nconst config = {\n    cache: 'jack-cache',   // Name im globalen Kontext\n    proto: 'http',\n    ip: 'ccu-jack.local',\n    port: 2121,\n    user: 'user',\n    password: 'password',\n    key: `-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----`,\n    cert: `-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----`,\n}\n```\n\n\n### Details"
    }
]

@ptweety
Copy link

ptweety commented Apr 24, 2022

Hallo @mdzio,

eine allgemein verwendbare Lösung kann man hier wahrscheinlich nicht erreichen. Die Gründe dafür hast du schon gelistet:

  • Die Nachrichten kommen nicht in garantierter Reihenfolge an
  • Die relevanten Informationen sind je nach Nutzer unterschiedlich

Darum muss ein Konsument in jedem Fall

  • entweder die Verarbeitung der Nachrichten verzögern
  • oder bei Erhalt die fehlenden Informationen nachlesen

Das ist aber immer mit einer Art von Zwischenspeicher verbunden, welcher eine gewisse Programmierung erfordert. Und diese Arbeit kannst du dem Konsumenten m.E. nicht abnehmen.

@TVR-X
Copy link
Author

TVR-X commented Apr 25, 2022

@mdzio bei
device/meta/<Seriennr.> → Alle Geräteeigenschaften als JSON
hätte ich zwar im NodeRed alles was ich brauch müsste es aber für die DB wieder aufbereiten

Aber das Problem mit dem Timing hab ich nicht ganz Verstanden, wenn die Gerätenamen beim starten(z.B.) einmal im broker "eingetragen" ist kann ichs doch von telegraf immer auslesen wenn der refresh der Daten kommen soll oder sehe ich das falsch?

@ptweety Erstmal Vielen Dank fürs teilen der Funktion
Hab die jetzt mal getestet, die macht schon ziemlich genau das was ich will, die Ursprüngliche Idee wäre aber gewesen in NodeRed nur zu verarbeiten was ich da brauch und es logging über telegraf direkt aus der CCU (über ccu-jack) zu holen ohne den umweg übers NodeRed.

Es gibt zwar ein paar Sachen die ich noch nicht 100% nachvollziehen konnte (da fehlt mir leider die Programmierkenntniss um zu verstehen wie) aber vor allem der Grund davon erschließt sich mir nicht ganz:
deviceName: device.title.charAt(0).toUpperCase() + device.title.charAt(1).toLowerCase() + device.title.slice(2),
und
deviceType: device.type.charAt(0).toUpperCase() + device.type.charAt(1).toLowerCase() + device.type.slice(2),
hab bei mir alles in UpperCase und war dann ein wenig verwirrt warum immer der zweite Buchstabe klein raus kam^^

@sikousikou
Copy link

@ptweety
Vielen Dank für die NodeRed Funktion, das ist genau was ich gesucht habe

@ptweety
Copy link

ptweety commented Apr 25, 2022

@ptweety Erstmal Vielen Dank fürs teilen der Funktion Hab die jetzt mal getestet, die macht schon ziemlich genau das was ich will, die Ursprüngliche Idee wäre aber gewesen in NodeRed nur zu verarbeiten was ich da brauch und es logging über telegraf direkt aus der CCU (über ccu-jack) zu holen ohne den umweg übers NodeRed.

Dann wäre die von @mdzio vorgeschlagene Lösung sicher ein guter Weg, denn damit hättest du die Möglichkeit jedweden payload und auch die Meta-Daten in die InfluxDB zu laden. Innerhalb von queries kannst du dann die Ergebnisse zusammen führen.
Letztlich brauchst du dann aber eine weitere Komponente (nämlich Telegraf). Mir wäre da NodeRed deutlich lieber, weil die Flexibilität damit höher ist.

Es gibt zwar ein paar Sachen die ich noch nicht 100% nachvollziehen konnte (da fehlt mir leider die Programmierkenntniss um zu verstehen wie) aber vor allem der Grund davon erschließt sich mir nicht ganz: deviceName: device.title.charAt(0).toUpperCase() + device.title.charAt(1).toLowerCase() + device.title.slice(2), und deviceType: device.type.charAt(0).toUpperCase() + device.type.charAt(1).toLowerCase() + device.type.slice(2), hab bei mir alles in UpperCase und war dann ein wenig verwirrt warum immer der zweite Buchstabe klein raus kam^^

Ach ja, einige HmIP-Geräte haben den Typ HmIP-... und andere eben HMIP-.... Das hat mir halt nicht gefallen und damit habe ich das normalisiert. Vorwiegend so rum, weil die meisten meiner Geräte eben das kleine m drin haben.

@mdzio
Copy link
Owner

mdzio commented Apr 25, 2022

Aber das Problem mit dem Timing hab ich nicht ganz Verstanden, wenn die Gerätenamen beim starten(z.B.) einmal im broker "eingetragen" ist kann ichs doch von telegraf immer auslesen wenn der refresh der Daten kommen soll oder sehe ich das falsch?

Das Senden der Gerätenamen zum MQTT-Broker erfolgt aber unabhängig vom Senden der Wertänderungen und kann je nach Projektgröße einige Zeit dauern. Also Wertänderungen können beim Client eintreffen bevor der Gerätename bekannt ist.

Anzeigenamen werden aus dargelegten Gründen ersteinmal nicht mit ins Topic oder der Payload aufgenommen. Eine Idee wäre noch, im Rahmen einer zukünftigen Skript-Funktionalität im CCU-Jack, dass sich Anwender den CCU-Jack selber erweitern können.

Die Metainformationen sollten über ein separates Topic publiziert werden (siehe dazu #90).

Dann schließe ich ersteinmal den Eintrag. Vielen Dank für die Diskussion.

@mdzio mdzio closed this as completed Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants