Mal wieder virtuelle Geräte - jetzt aber richtig!

Gibt auch super zigbee Stick und schon kannst du alles direkt auswerten ohne eine zusätzliche Bridge.

Habe alle drei sticks zwave, zigbee und enocean. So kann jedes Gerät was homee nicht kennt angesprochen werden.

VG

1 „Gefällt mir“

Bin auch daran interessiert. Hab am Wochenende wenigstens geschafft meine Instar in HomeKit einzubinden :metal:
Im Homee fehlt mir jetzt noch.

Hat jemand eigentlich schon eine Alarmanlage in homee integriert? Falls ja, wie sehen dann die Schalter für scharf und unscharf schalten aus?

was meinst du mit alarmanlage integriert? ein „echtes“ System oder mit homee zusammengebaut?

  1. Hab ich bei mir umgesetzt:

verschiedene Alarmstufen…

  1. komplett und 2. äußere Hülle.

aktiviert wird per Abwesenheits Modus:

Schlafend = 2. äußere Hülle (Fenster und Tür Sensoren aktiv)
Abwesend/Urlaub = 1. komplett ( Fenster und Tür Sensoren aktiv, BWM innen aktiv)

Alarm über Sirene,Push und Licht

als physischen Schalter FT55 von homee. Beim Tür öffnen, kurze Zeitspanne zur deaktivierung. Später soll noch eine dezente LED signalisierung dazu kommen.

Meinte eher die Integration einer vorhandenen Alarmanlage. Ich habe eine Lupusec und will die über iobroker in Homee Integrieren.

So hier mein Weg um eine beliebige RTSP Kamera in homee einzubinden.
System ist hier der Raspberry 4 mit 4 GB Ram.
Grundlage ist ein Buster Image von Iobroker…
Hier wurde dann ein Apache Webserver eingerichtet.

sudo apt-get install apache2

Danach die IP deines Raspberry aufrufen um zu testen ob es läuft ( 123.456.789.00:80)
Wenn das gut durchgelaufen ist dann kommt ffmpeg dran.

sudo apt-get install ffmpeg

Die Installation von ffmpeg dauert eine Weile. ( Kaffee oder was anderes geht da immer )

Zum testen könnt Ihr jetzt mal ein Video eurer RTSP Cam streamen,

ffmpeg -rtsp_transport tcp -i rtsp://xxx.xxx.xxx.xx:554/onif1  -vcodec copy -hls_time 4 -hls_list_size 2 -hls_wrap 2 -start_number 1 -y /home/pi/test.m3u8

dann sollte der Bildschirm wie folgt aussehen.

Soweit so gut.
Jetzt haben wir also einen Stream der mit einem VLC player oder mit homee oder jedem mobilen Gerät abgerufen werden kann.
Da meine Kamera keinen Snapshot hat musste ich aus dem Livestream einen anlegen,
das ganze kommt jetzt in den Ordner /var/www/html/ des PI.
für den Snapshot habe ich eine /home/pi/test.sh Datei mit einem einfachen Aufruf des ffmpeg angelegt.

ffmpeg -loglevel fatal -i rtsp:xxx.xxx.xxx.xx:554/onif1 -vframes 1 -y -r 1 /var/www/html/test.png

Hierzu muss man dem Ordner vorher die passenden Rechte vergeben.

sudo chmod 777 -R /var/www/html

Nun könnt Ihr über einen beliebigen Browser das Bild öffnen. (ip:80/test.png)
Das ganze wird dann noch schön in ein Cronetab gepackt damit das Bild auch aktuell ist.
Dazu wird die Datei /etc/crontab geöffnet und folgende Zeile eingefügt.

*/5 *   * * *   root    /home/pi/test.sh

Sehr schön soweit.
Jetzt geht es an das vhih Gerät Netatmo Kamera,
dazu habe ich euch mal den Flow von mir exportiert.

[
    {
        "id": "565879f8.8bbd18",
        "type": "homeeDevice",
        "z": "ed531f2.24960e",
        "virtual-homee": "",
        "name": "Eingang",
        "nodeId": "1002",
        "profile": "3027",
        "icon": "",
        "attributes": "[{\"id\":2235,\"node_id\":1002,\"instance\":0,\"minimum\":0,\"maximum\":1,\"current_value\":1,\"target_value\":1,\"last_value\":0,\"unit\":\"n%2Fa\",\"step_value\":1,\"editable\":1,\"type\":179,\"state\":1,\"last_changed\":1571855493,\"changed_by\":1,\"changed_by_id\":0,\"based_on\":1,\"data\":\"{\\\"baseUrlOnline\\\":\\\"http://IPdesPI:80\\\",\\\"baseUrlLocal\\\":\\\"http://IPdesPI:80\\\",\\\"commands\\\":{\\\"image\\\":\\\"/test.png\\\",\\\"ping\\\":\\\"/command/ping\\\",\\\"video\\\":\\\"/test.m3u8\\\"}}\"}]",
        "x": 100,
        "y": 500,
        "wires": [
            [
                "14471ac1.de5305",
                "8582db99.009558"
            ]
        ]
    },
    {
        "id": "debc0b71.7ae098",
        "type": "exec",
        "z": "ed531f2.24960e",
        "command": "ffmpeg -rtsp_transport tcp -i rtsp://xxx.xxx.xxx.xx:554/onif1  -vcodec copy -hls_time 4 -hls_list_size 3 -hls_wrap 3 -start_number 1 -y /var/www/html/test.m3u8",
        "addpay": false,
        "append": "",
        "useSpawn": "true",
        "timer": "",
        "oldrc": false,
        "name": "Eingang Stream",
        "x": 860,
        "y": 480,
        "wires": [
            [],
            [],
            []
        ]
    },
    {
        "id": "f4a889f0.80bc58",
        "type": "change",
        "z": "ed531f2.24960e",
        "name": "",
        "rules": [
            {
                "t": "move",
                "p": "payload",
                "pt": "msg",
                "to": "kill",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 640,
        "y": 520,
        "wires": [
            [
                "debc0b71.7ae098"
            ]
        ]
    },
    {
        "id": "14471ac1.de5305",
        "type": "switch",
        "z": "ed531f2.24960e",
        "name": "Anschalten",
        "property": "payload.targetValue",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "1",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 250,
        "y": 480,
        "wires": [
            [
                "debc0b71.7ae098"
            ]
        ]
    },
    {
        "id": "8582db99.009558",
        "type": "switch",
        "z": "ed531f2.24960e",
        "name": "ausschalten",
        "property": "payload.targetValue",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "0",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 250,
        "y": 520,
        "wires": [
            [
                "45a245fe.a5f87c"
            ]
        ]
    },
    {
        "id": "45a245fe.a5f87c",
        "type": "change",
        "z": "ed531f2.24960e",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "SIGTERM",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 430,
        "y": 520,
        "wires": [
            [
                "f4a889f0.80bc58"
            ]
        ]
    }
]

Das war eigentlich schon alles um die Kamera in den homee zu bringen.
Leider wie gesagt aktuell nur lokal da mir keine Idee für den externen Zugang eingefallen ist und ich das auch nicht wirklich weiter verfolgt habe.
Bei dem ffmpeg könnt Ihr noch ein wenig mit der Zeitdauer und der Segmente arbeiten um das beste Ergebnis für euch raus zu holen.
Ich hoffe das ich nichts vergessen habe.
Wenn noch Fragen sind nur zu und ich schaue ob mir was einfällt dazu.
VG Micha

Edit:

Der Flow ist so aufgebaut das beim aktivieren der Kamera in homee erst der Stream angelegt wird, somit muss der Pi nicht die ganze Zeit die Daten umwandeln.

17 „Gefällt mir“

Wie immer tolle Beschreibung und tolles Projekt, danke @Micha! :+1:t2::pray:

Das Problem mit dem externen Zugang, müsstest du eigentlich ganz gut mit VPN-on-demand lösen können. So mache ich es auch mit einigen Dingen.

Du hast doch auch eine FRITZ!Box und iOS Geräte, oder?
Kann dir gerne morgen den Link zu einer gut verständlichen Anleitung schicken.

Das it sehr nett von dir , aber leider funktioniert das mit VPN nicht.
Alles getestet :see_no_evil:

Ok… es geht nur mit Hostnamen und nicht mit IPs.
Also wenn du eine Cam „Eingang“ hast, hätte die ja den Hostnamen Eingang.fritz.box.
Mein VPN-on-demand wird durch einen Aufruf *.fritz.box getriggert.

Ist die Frage, ob der Stream in ffmepg zwingend auf IP angewiesen ist.

Ich hab eine Kamera mit deinem Flow testweise in Homee eingebunden. Ich habe damit dann auch verstanden wie es funktioniert. Der Trick ist das data Attribute. Auf die Idee wäre ich so nicht gekommen ohne Beispiel :slight_smile:
Also vielen Dank fürs teilen.
Jetzt hab ich aktuell noch zwei Probleme…

  • In der Webapp (aufgerufen über https://my.hom.ee) kommt das Video nicht wegen „mixed content“. Ist klar, aber kein Problem, ich muss nur im Apache das https aktivieren.
  • das Video in der iOS App kommt, aber hört nach ein paar Sekunden wieder auf. Warum? Das variiert und ist nicht immer 4 Sekunden. Muss das so? Oder mache ich was falsch?

Was muss man dafür einrichten? Ok, man braucht dafür ein „Profil“. Das kann man mit dem Apple Configurator erstellen. Dafür braucht man aber ein Mac. Hm…

Edit: kann man auch von Hand erstellen.
https://www.loxwiki.eu/display/LOX/VPN+on-demand
Muss ich dringend mal testen. Die Idee gefällt mir sehr gut.

Das mit der Webapp ist mir garnicht aufgefallen da ich nur iOS dafür benutze.
Das mit dem Stream musst du mal anhand der Konfiguration des ffmpeg testen.
Starte mal den Stream über die Konsole und öffne dann die homee App ( vor her die Verbindung zum exec node trennen ).
Kommt es dann sauber an ?
Dadurch das der Stream in der Version erst gestartet wird scheint es hier ab und an zu dieser Verzögerung bzw. Zu den Abbruch zu kommen.

Ist halt noch nicht ganz so sauber.:see_no_evil:

Alles klar, danke. Reicht mir zu wissen, dass es als ununterbrochener Stream laufen sollte. Werde es dann wohl finden. Ich werde berichten.

Hallo,

ich bräuchte schon wieder eure Hilfe. Ich bin derweil noch am rumtesten.

In dem konkreten Fall habe ich von iobroker einen Xiaomi Temp-Sensor mittels Nodered an homee als virtuelles Gerät weitergereicht. Zusätzlich habe ich im iobroker auch den homee-Adapter, so dass das virtuelle Gerät dann auch dort auftaucht.
Das funktioniert auch alles wie es soll.

Jetzt ist mir aufgefallen, dass obwohl ich das virtuelle Gerät im homee gelöscht habe, es im homee auch weg ist, es immer noch im homee-adapter des iobrokers auftaucht. Dort jetzt durch die Spielereien sogar mehrfach. :smirk:

Bildschirmfoto 2020-05-05 um 18.05.34

Da der Flow in Nodered noch aktiv war, kamen im homee-iobroker-adapter sogar noch die Daten an. D.h. doch, dass das virtuelle Gerät noch irgendwie im homee ist, oder? Der Flow liefert mittlerweile keine neuen Daten, habe ihn stillgelegt. Neugestartet habe ich auch schon alles, die virtuellen Geräte sind allerdings immer noch im homee-iobroker-adapter, damit ggf. auch noch im homee selbst.

Ich habe sie jetzt noch nicht über den homee-iobroker-adapter gelöscht, nicht dass sie dann in irgendeiner Form im homee weiter existieren. :confused:

Habt ihr eine Idee oder konntet ein ähnliches Verhalten beobachten?

So, nun hab ich einen sauberen Stream laufen.

ffmpeg -fflags nobuffer -rtsp_transport tcp -i rtsp://username:password@192.168.xxx.xxx:554/12 -vsync 0 -copyts -vcodec copy -movflags frag_keyframe+empty_moov -an -hls_flags delete_segments+append_list -f segment -segment_list_flags live -segment_time 1 -segment_list_size 3 -segment_wrap 10 -segment_format mpegts -segment_list /var/www/html/eingang.m3u8 -segment_list_type m3u8 -segment_list_entry_prefix /eingang/ /var/www/html/eingang/%d.ts

Damit klappt es nun bei mir.
Einschränkung: Der Stream muss ca. 8 - 10 Sekunden laufen, bis er als „Live“ Stream erkannt wird.
Die *.ts-Dateien werden alle 10 Files überschrieben.
Um den externen Zugriff habe ich mich bisher nicht gekümmert… kommt noch :slight_smile:

4 „Gefällt mir“

Das Schwarmwissen ist nicht zu unterschätzen.
Freut mich das es bei dir läuft.
Meine Segmente werden auch immer überschrieben. :wink:

1 „Gefällt mir“

Sodelle, bin nicht der große Node-Red Entwickler.
Habe es trotzdem geschafft dynamisch zur Laufzeit das Data-Attribute zu ändern.
Zum Beispiel um eine Firmwareversion anzuzeigen

Attributconfig im VirtuellenDevice

    {
        "id": 346,
        "node_id": 340,
        "instance": 0,
        "minimum": 0,
        "maximum": 100,
        "current_value": 0,
        "target_value": 0,
        "last_value": 0,
        "unit": "text",
        "step_value": 1,
        "editable": 0,
        "type": 45,
        "state": 1,
        "last_changed": 1574494369,
        "changed_by": 1,
        "changed_by_id": 0,
        "based_on": 1,
        "data": "Software 1"
    }

Wird wie folgt gefüttert:

node.send({payload:{"attribute":{"id":346,"value":0,"data":String(msg.payload["valetudoVersion"])}}})

Erlaubt ist alles vom Typ STRING

Ergebniss:
image

Dazu musste lediglich geringfügig die homeeDevice.js angepasst werden:

const Device = require('../lib/device');

// eslint-disable-next-line
module.exports = function (RED) {
  function HomeeDeviceNode(config) {
    RED.nodes.createNode(this, config);
    const node = this;
    this.virtualHomeeNode = RED.nodes.getNode(config['virtual-homee']);
    this.icon = config.icon;
    this.name = config.name;
    this.nodeId = parseInt(config.nodeId, 10);
    this.profile = parseInt(config.profile, 10);
    this.storageConfigured = RED.settings.contextStorage && 'homeeStore' in RED.settings.contextStorage;

    if (this.nodeId === -1) throw new Error('The node id must not be -1');

    try {
      this.attributes = JSON.parse(config.attributes);
      if (!Array.isArray(this.attributes)) throw new Error('Attributes must be an array');

      if (this.attributes.filter((a) => a.node_id !== this.nodeId).length) {
        throw new Error('The node id of at least one attribute does not match the device node id');
      }

      if (this.storageConfigured) {
        node.context().get('attributes', 'homeeStore', (err, attributes) => {
          if (err || !Array.isArray(attributes)) {
            node.debug(`Can't load data from storage for device #${this.nodeId}, ${err}`);
            return;
          }

          attributes.forEach((storedAttribute) => {
            const attribute = this.attributes.find((a) => a.id === storedAttribute.id);
            attribute.current_value = storedAttribute.current_value;
            attribute.target_value = storedAttribute.target_value;
            this.virtualHomeeNode.api.send(JSON.stringify({ attribute }));
          });

          node.debug(`loaded data from storage for device #${this.nodeId}`);
        });
      }

      this.device = new Device(this.name, this.nodeId, this.profile, this.attributes, this.icon);
      this.status({ fill: 'green', shape: 'dot', text: this.device.statusString() });

      this.virtualHomeeNode.registerDevice(this.id, this.device, (err) => {
        if (err) throw Error(err);
      });
    } catch (e) {
      this.status({ fill: 'red', shape: 'dot', text: 'error' });
      this.error(e);
    }

    // new value from flow
    this.on('input', (msg) => {
      if (typeof msg.payload !== 'object') {
        node.warn('Only JSON-Objects are valid payloads. Ignoring message.');
        return;
      }

      if ('id' in msg.payload && 'value' in msg.payload) {
        node.warn(`using an object with id and value is deprecated.
          You'll find the new syntax in the README.`);
        this.updateAttribute(msg.payload.id, msg.payload.value);
        return;
      }

      Object.keys(msg.payload).forEach((key) => {
        switch (key) {
          case 'attribute':
            this.updateAttribute(msg.payload.attribute.id, msg.payload.attribute.value, msg.payload.attribute.data);
            break;
          case 'attributes':
            msg.payload.attributes.forEach((a) => this.updateAttribute(a.id, a.value, a.data));
            break;
          case 'state':
            this.updateNode(key, msg.payload[key]);
            break;
          default:
            node.warn('Invalid message. Please check the Readme/Wiki. Ignoring message');
        }
      });
    });

    this.on('close', (done) => {
      if (!this.storageConfigured) {
        done();
        return;
      }

      node.context().set('attributes', this.attributes, 'homeeStore', (err) => {
        if (err) node.debug(`Can't store data for device #${this.nodeId}, ${err}`);

        node.debug(`stored data for device #${this.nodeId}`);
        node.status({ fill: 'red', shape: 'dot', text: this.device.statusString() });
        done();
      });
    });

    /**
     * update node
     * @param  {string} key
     * @param  {mixed} value
     * @return {void}
     */
    this.updateNode = (key, value) => {
      this.device[key] = value;
      if (key === 'state') this.device.state_changed = Math.floor(Date.now() / 1000);
      this.virtualHomeeNode.api.send(JSON.stringify({ node: this.device }));
      node.debug(`updated ${key} of node #${this.device.id} to value ${value}`);
    };

    /**
     * update attribute
     * @param  {int} id        the attribute id
     * @param  {int|float} value  new value
     * @param  {string} data    new data
     * @return {void}
     */
    this.updateAttribute = (id, value, data) => {
      if (typeof id !== 'number' || typeof value !== 'number') {
        node.warn('id and value must be numeric. ignoring message.');
        return;
      }

      const attribute = this.attributes.find((a) => a.id === id);
      const unixTimestamp = Math.round(Date.now() / 1000);

      if (!attribute) {
        node.warn(`Can't find attribute with id ${id}`);
        return;
      }

      node.debug(`updating attribute #${id} to value: ${value}`);

      if (value < attribute.minimum || value > attribute.maximum) {
        node.warn(`can't update attribute. The provided value must be
            between ${attribute.minimum} and ${attribute.maximum}`);
        return;
      }

      if (attribute.target_value === value && attribute.last_changed + 2 > unixTimestamp) {
        node.debug(`Attribute #${id} was updated within the last two seconds.`);
      }

      // first update target value only
      attribute.target_value = value;
      this.virtualHomeeNode.api.send(JSON.stringify({ attribute }));

      // next update current_value and data
      attribute.last_value = attribute.current_value;
      attribute.current_value = value;
      attribute.data = data;
      attribute.last_changed = unixTimestamp;
      this.virtualHomeeNode.api.send(JSON.stringify({ attribute }));
      this.status({ fill: 'green', shape: 'dot', text: this.device.statusString() });
    };
  }

  RED.nodes.registerType('homeeDevice', HomeeDeviceNode);
};

Ich kenne jetzt nur das Attribut Softwareversion, um dieses mit einem freien Text zu belegen, eventuell gibt es ja weitere.

Grüße Matthias

PS: Hier ein besseres Beispiel, dass String akzeptiert wird:
image

2 „Gefällt mir“

Evtl. Magst du mit deinen Änderungen einen Pull-Request bei @stfnhmplr machen?

Danke erstmal für deinen Input. Yo, so ist das im Prinzip möglich. Habe gerade einen Blick in den PR geworfen. Allerdings ist es so, wie du es gelöst hast keine optionale Geschichte mehr.

Kurze Erklärung: Wenn der Data Key nun nicht übergeben wird, ist die Variable undefined. Damit wird das dann auch so übertragen was die bisherigen gespeicherten Werte überschreibt. Finde ich nicht so gut. Meiner Meinung nach wäre es besser, das ganze als optionale Änderung zu ermöglichen. So muss der Data Key nicht bei jeder Attributänderung übergeben werden.

Auch wird die Änderung zur Laufzeit nicht in die Ursprungskonfiguration des Nodes weggeschrieben. Bei einem Neustart wird also der alte Wert aus der Konfiguration wieder verwendet. Da weiß ich auf anhieb aber auch nicht, ob das überhaupt möglich ist.
Über die optionale Speicherfunktion wäre das machbar, die müsste aber dann auch noch um den Data-Key ergänzt werden.

Möchtest du den PR selbst anpassen oder soll ich?

Das ganze behebt noch nicht das Problem, wenn das Data Attribut vom physischen homee geändert wird. Das passiert z.B. bei der Anlage von Farbfavoriten. Hier ist leider keine Übernahme der geänderten Werte möglich.

Muss zugeben, das ganze war eher quick and dirty. Ich schau mir mal an wie ich data sauber abfange bevor ich data mit NULL/Undefined oder dergleichen überschreibe. Das ganze in persistenten Speicher zu übergeben ist ja mit deiner Vorarbeit nicht mehr das große Ding.

Ich werde den PR überarbeiten.

Grüße Matthias

PS: Musste mich bisher nie mit Github rumschlagen … eher mit der Versionsverwaltung in nem SAP ERP

1 „Gefällt mir“