Mal wieder virtuelle Geräte - jetzt aber richtig!

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“

Vermutlich wird eine einfache Abfrage mit if (attribute.data) reichen.

Guckst du …

Danke. Ich merge das mit dem nächsten Update.

Zum Thema speichern der Zustände: Unter ioBroker und Docker ist das aktuell nicht ohne Workaround möglich. Ich habe das Wiki entsprechend ergänzt.
Einmal lässt sich der Docker-Container anders beenden, so dass Node-RED korrekt runterfährt. Die zweite Möglichkeit stammt von @medicus07: Ein Flow zur Beendigung von Node-RED. Diese Variante funktioniert auch unter ioBroker.

Details stehen im Wiki.

3 „Gefällt mir“

Hallo zusammen,

zur Zeit betreibe ich einen Xiaomi Mi Robot der ersten Generation. Dieser gibt langsam den Geist auf. Auch mein nächster Saugroboter soll über nodered und vhih mit homee sprechen können. Hat jemand Erfahrung mit den neuen Modellen? Kann ich z.B. einen Mi Robot 1S genau so einbinden?

1 „Gefällt mir“

Heute mal was neues… Rasensprenger in homee…

Gibt es eigentlcih nich Probleme mit node-red-contrib-huemagic 2.7.0? @DerSmily hatte davon berichtet.
Ich würde gern von 2.6.2 upgraden, bin mir aber nicht sicher ob die Idee gut ist…
Jemand Erfahrungen?
Danke Gruß Ralf

hab mal ne frage, vllt hat ja einer ne idee.
Folgendes Problem in node red:

habe zwei iobroker inputs (je ein plug der den verbraucht misst)

Plug1 misst den Verbrauch,
Plug2 eine Einspeisung,

bei dem Wert von Plug 2 wird das vorzeichen noch geändert, und dann gehen beide werte auf ein Join node
Dieser kombiniert alle msg.payload zu einem Array. und sendet nach 2 Nachrichtenteilen.

Soweit so gut.
Das Problem ist nur das die beiden Plugs nicht syncron aktualisieren, d.h. plug1 kann schon 2 mal einen neuen Wert schicken ohne das Plug2 üverhaupt was macht.

Dann kommt im Array statt 1xVerbrauch und 1x Produktion, z.b. 2 mal Verbrauch an.
Das darf natürlich nicht sein, denn dann summiert er nach dem Join 2x den verbrauch und nicht (Verbrauch - Produktion).

Jemand ne idee wie ich das ändern kann? z.b. die beider werte des iobroker inputs in einem zeitintervall immer syncron weitergeben?