Skip to content
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo
  1. ioBroker Community Home
  2. Deutsch
  3. Tester
  4. Test Withings v0.0.x

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.2k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    1.9k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.2k

Test Withings v0.0.x

Geplant Angeheftet Gesperrt Verschoben Tester
540 Beiträge 51 Kommentatoren 127.0k Aufrufe 45 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • N Nidoello

    Hat inzwischen jemand herausgefunden, wie man das Verarbeiten von aufstehen und Bett gehen mit dem Sleep Analizer in ioBroker hinbekommt?

    R Offline
    R Offline
    robson
    schrieb am zuletzt editiert von
    #513

    @nidoello
    Deine Schlafsession bzw. die Daten werden im IOB erst morgens nach dem Aufstehen aktualisiert. Wenn dir das genügt, hast du deine Daten dort zur weiteren Bearbeitung parat liegen.
    Wenn du, wie ich und einige andere, die Daten aber direkt nach jedem "Ins Bett gehen" und "Aufstehen"-Event haben möchtest, dann geht dies nicht mit der aktuellen Withings API.

    N 1 Antwort Letzte Antwort
    0
    • R robson

      @nidoello
      Deine Schlafsession bzw. die Daten werden im IOB erst morgens nach dem Aufstehen aktualisiert. Wenn dir das genügt, hast du deine Daten dort zur weiteren Bearbeitung parat liegen.
      Wenn du, wie ich und einige andere, die Daten aber direkt nach jedem "Ins Bett gehen" und "Aufstehen"-Event haben möchtest, dann geht dies nicht mit der aktuellen Withings API.

      N Online
      N Online
      Nidoello
      schrieb am zuletzt editiert von
      #514

      @robson Genau das ist der Punkt.

      Für die Automatisierung mit direkt folgender Aktion ausgelöst durch einen dieser beiden Trigger, da bleibt leider nur der Umweg über IFTTT.
      Darüber geht's weiterhin mit immer unterschiedlich ein paar Sekunden Verzögerung.

      1 Antwort Letzte Antwort
      0
      • T tombox

        Hi ich habe ein neuen Adapter für Withings geschrieben

        Integriert:
        Getmeas
        Getactivity
        ListHeart
        GetSummarySleep
        getHeart
        getSleep

        Fehlt:
        Getintradayactivity

        hr Heart Rate.

        rr Respiration Rate.

        snoring Total snoring time

        Geräte Information werden nur bei Adapterstart abgerufen

        Zum Installieren:
        https://github.com/TA2k/ioBroker.withings

        Unter Adapter Experten Modus aktivieren:
        9309457a-cad0-4ff4-946f-28df05d32801-image.png

        Unter Adapter das Github Icon klicken:
        ef5f973f-4a70-43be-bf9a-460726a69d1d-image.png
        Benutzerdefiniert auswählen und die Url einfügen.
        8328414c-da64-41d4-b524-5a75a25cb683-image.png

        Dann unter Adapter den Withings Adapter suchen und ganz rechts auf das Plus klicken.
        f59f304a-7769-463f-84d2-e36b461a87bd-image.png

        Loginablauf:
        Die Withings Email und Passwort eingeben
        https://account.withings.com/partner/dashboard_oauth2
        Eine App registrieren
        Name: ioBroker
        Beliebiges Bild
        Description: ioBroker
        Email: Yourmail
        Company: ioBroker
        Callback URI: http://localhost
        Eingeschränkter Modus: Ja

        Client id und Client Secret eintragen

        C Offline
        C Offline
        chris299
        schrieb am zuletzt editiert von
        #515

        @tombox vielen Dank für den Adapter.
        Eine technische Frage aus reiner Neugier: wie kommt der Adapter denn um die user-interaktive Freigabe drumrum?

        nach https://developer.withings.com/api-reference/#tag/oauth2/operation/oauth2-authorize
        leitet das GET den User in einen interaktiven Flow um, an dessen ende man einen Code für den Request nach einem Token bekommt....
        Wie kürzt der Adapter das ab?

        T 1 Antwort Letzte Antwort
        0
        • C chris299

          @tombox vielen Dank für den Adapter.
          Eine technische Frage aus reiner Neugier: wie kommt der Adapter denn um die user-interaktive Freigabe drumrum?

          nach https://developer.withings.com/api-reference/#tag/oauth2/operation/oauth2-authorize
          leitet das GET den User in einen interaktiven Flow um, an dessen ende man einen Code für den Request nach einem Token bekommt....
          Wie kürzt der Adapter das ab?

          T Offline
          T Offline
          tombox
          schrieb am zuletzt editiert von
          #516

          @chris299 der Flow wird programmatisch simuliert

          1 Antwort Letzte Antwort
          0
          • P prorun

            @pamf

            Ich habe das gleiche Problem (Werte kommen immer in anderem Unterordner an) und manchmal sogar noch schlimmer (Werte der letzten Messung verteilen sich auf verschiedene Unterordner)

            Ich nutze die Körperwaage "Body Cardio" von Withings.

            So wie ich das verstanden habe ist es so vorgesehen, dass die neusten Messwerte immer unter
            withings.0.########.measures.measuregrps01
            ankommen sollen und ältere Werte in die Folgeordner
            withings.0.########.measures.measuregrps02
            withings.0.########.measures.measuregrps03
            withings.0.########.measures.measuregrps04
            usw.
            nachgeschoben werden was quasi eine absteigende Sortierung darstellen soll.

            Leider funktioniert das so gar nicht. Mal kommen die aktuellen Werte unter .measuregrps01 an, am nächsten Tag unter .measuregrps03 und danach unter .measuregrps02 ohne jedwede erkennbare Systematik - was zwar über ein Skript umständlich abgefangen wird - aber manchmal verteilen sich die aktuellen Werte sogar auf verschiedene Unterordner.

            Ich versuche es am Beispiel meiner heutigen Messung (27.03.) aufzuzeigen (benutzt wurde die Waage heute nur ein einziges Mal):

            Die Messwerte wurden auf folgende Ordner verteilt.
            withings.0.########.measures.measuregrps01
            withings.0.########.measures.measuregrps02
            withings.0.########.measures.measuregrps03
            Folgend werde ich diese drei Ordner nur mit 01, 02 und 03 bezeichnen.
            In allen drei Ordnern wurden die Datenpunkte .created .date und .modified die sich jeweils darin befinden auf Datum und Uhrzeit der heutigen Messung gesetzt.
            Die aktuell gemessene Pulswellengeschwindigkeit ist im Ordner 01 gelandet
            Die aktuell gemessene Herzfrequenz ist im Ordner 02 gelandet
            Alle anderen aktuell gemessenen Messwerte sind im Ordner 03 gelandet

            Hier noch mal die vollständige Auflistung für die drei Ordner (Das Datum bezieht sich jeweils auf lastchange):


            withings.0.########.measures.measuregrps01
            26.03.2024 - Weight (kg)
            25.03.2024 - Heart Pulse (bpm) - only for BPM and scale devices
            26.03.2024 - Fat Free Mass (kg)
            26.03.2024 - Fat Ratio (%)
            26.03.2024 - Muscle Mass (kg)
            26.03.2024 - Hydration (kg)
            26.03.2024 - Fat Mass Weight (kg)
            26.03.2024 - Bone Mass (kg)
            27.03.2024 - Pulse Wave Velocity (m/s)

            withings.0.########.measures.measuregrps02
            25.03.2024 - Weight (kg)
            27.03.2024 - Heart Pulse (bpm) - only for BPM and scale devices
            25.03.2024 - Fat Free Mass (kg)
            25.03.2024 - Fat Ratio (%)
            25.03.2024 - Muscle Mass (kg)
            25.03.2024 - Hydration (kg)
            25.03.2024 - Fat Mass Weight (kg)
            25.03.2024 - Bone Mass (kg)
            nicht vorhanden - Pulse Wave Velocity (m/s)

            withings.0.########.measures.measuregrps03
            27.03.2024 - Weight (kg)
            24.03.2024 - Heart Pulse (bpm) - only for BPM and scale devices
            27.03.2024 - Fat Free Mass (kg)
            27.03.2024 - Fat Ratio (%)
            27.03.2024 - Muscle Mass (kg)
            27.03.2024 - Hydration (kg)
            27.03.2024 - Fat Mass Weight (kg)
            27.03.2024 - Bone Mass (kg)
            nicht vorhanden - Pulse Wave Velocity (m/s)

            Ich benutze den Adapter in der Version 0.0.13

            Ich habe dazu ein issue auf Github geöffnet. Wenn hier jemand dabei ist, der dazu etwas beitragen kann dann immer raus damit. Ich würde mich über jede Unterstützung sehr freuen.

            A Abwesend
            A Abwesend
            ammawel
            schrieb am zuletzt editiert von
            #517

            Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

            @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
            Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
            Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

            Für jede Hilfe bin ich dankbar.

            M Marc BergM 3 Antworten Letzte Antwort
            0
            • A ammawel

              Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

              @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
              Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
              Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

              Für jede Hilfe bin ich dankbar.

              M Online
              M Online
              mading
              schrieb am zuletzt editiert von
              #518

              @ammawel sagte in Test Withings v0.0.x:

              Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

              @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
              Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
              Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

              Für jede Hilfe bin ich dankbar.

              Soweit ich mich erinnere, ist der erste aufgeführte Messwert, der neuste.

              Marc BergM 1 Antwort Letzte Antwort
              0
              • A ammawel

                Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
                Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
                Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

                Für jede Hilfe bin ich dankbar.

                Marc BergM Offline
                Marc BergM Offline
                Marc Berg
                Most Active
                schrieb am zuletzt editiert von
                #519

                @ammawel sagte in Test Withings v0.0.x:

                Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

                Mein Workaround:

                • immer nur den letzten Tag abfragen
                  7678dcbd-f07c-4a54-8592-4268d060436a-grafik.png

                • maximal einmal am Tag auf die Waage steigen

                Damit wird nur measuregrps01 angelegt. Über die History Funktion kann man dann sauber die Werte wegschreiben.

                NUC10I3+Ubuntu+Docker+ioBroker+influxDB2+Node Red+RabbitMQ+Grafana

                Pi-hole, Traefik, Checkmk, Conbee II+Zigbee2MQTT, ESPSomfy-RTS, LoRaWAN, Arduino, KiCad

                Benutzt das Voting im Beitrag, wenn er euch geholfen hat.

                A 1 Antwort Letzte Antwort
                0
                • M mading

                  @ammawel sagte in Test Withings v0.0.x:

                  Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                  @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
                  Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
                  Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

                  Für jede Hilfe bin ich dankbar.

                  Soweit ich mich erinnere, ist der erste aufgeführte Messwert, der neuste.

                  Marc BergM Offline
                  Marc BergM Offline
                  Marc Berg
                  Most Active
                  schrieb am zuletzt editiert von Marc Berg
                  #520

                  @mading sagte in Test Withings v0.0.x:

                  Soweit ich mich erinnere, ist der erste aufgeführte Messwert, der neuste.

                  Das ist leider nicht (immer) so, sonst wäre das alles kein Problem.

                  NUC10I3+Ubuntu+Docker+ioBroker+influxDB2+Node Red+RabbitMQ+Grafana

                  Pi-hole, Traefik, Checkmk, Conbee II+Zigbee2MQTT, ESPSomfy-RTS, LoRaWAN, Arduino, KiCad

                  Benutzt das Voting im Beitrag, wenn er euch geholfen hat.

                  1 Antwort Letzte Antwort
                  0
                  • Marc BergM Marc Berg

                    @ammawel sagte in Test Withings v0.0.x:

                    Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

                    Mein Workaround:

                    • immer nur den letzten Tag abfragen
                      7678dcbd-f07c-4a54-8592-4268d060436a-grafik.png

                    • maximal einmal am Tag auf die Waage steigen

                    Damit wird nur measuregrps01 angelegt. Über die History Funktion kann man dann sauber die Werte wegschreiben.

                    A Abwesend
                    A Abwesend
                    ammawel
                    schrieb am zuletzt editiert von
                    #521

                    @marc-berg sagte in Test Withings v0.0.x:

                    • maximal einmal am Tag auf die Waage steigen

                    🤣 🤣 🤣 "Ich lasse mir doch von meiner Waage nicht vorschreiben... "
                    Das Problem sollte dann die Waage mit meinem Doc ausdiskutieren...

                    Das muss auch anders gehen, ich hatte schon den Tipp fhem wieder zu installieren oder zu HomeAssistant zu wechseln.
                    Danke für alle Tipps. Falls ich eine Lösung finde, werde ich sie hier mitteilen - vermutlich wird sie nicht so unterhaltsam.
                    VG Achim

                    Marc BergM 1 Antwort Letzte Antwort
                    0
                    • A ammawel

                      @marc-berg sagte in Test Withings v0.0.x:

                      • maximal einmal am Tag auf die Waage steigen

                      🤣 🤣 🤣 "Ich lasse mir doch von meiner Waage nicht vorschreiben... "
                      Das Problem sollte dann die Waage mit meinem Doc ausdiskutieren...

                      Das muss auch anders gehen, ich hatte schon den Tipp fhem wieder zu installieren oder zu HomeAssistant zu wechseln.
                      Danke für alle Tipps. Falls ich eine Lösung finde, werde ich sie hier mitteilen - vermutlich wird sie nicht so unterhaltsam.
                      VG Achim

                      Marc BergM Offline
                      Marc BergM Offline
                      Marc Berg
                      Most Active
                      schrieb am zuletzt editiert von Marc Berg
                      #522

                      @ammawel sagte in Test Withings v0.0.x:

                      fhem wieder zu installieren oder zu HomeAssistant zu wechseln.

                      Warte mal noch ... 🙂

                      Ich habe mir den Code mal angeschaut, es gab schon mal den vorsichtigen Versuch zu sortieren. Dieser Teil wurde aber wieder auskommentiert.

                      Darum habe ich jetzt eine Sortierung eingebaut (primär nach Datum/Zeit der Messung), sekundär nach Erstell-/bzw. Änderungsdatum.

                      Wenn du mutig bist, kannst du mal von Github testen:

                      https://github.com/Marc-Berg/ioBroker.withings

                      Wenn das funktioniert, kann ich einen PR einreichen, der Programmierer ist ja noch aktiv.

                      EDIT: da ich nur ca. einmal im Quartal auf die Waage steige, habe ich zu wenige Messwerte, um selbst zu testen.

                      NUC10I3+Ubuntu+Docker+ioBroker+influxDB2+Node Red+RabbitMQ+Grafana

                      Pi-hole, Traefik, Checkmk, Conbee II+Zigbee2MQTT, ESPSomfy-RTS, LoRaWAN, Arduino, KiCad

                      Benutzt das Voting im Beitrag, wenn er euch geholfen hat.

                      A 1 Antwort Letzte Antwort
                      1
                      • Marc BergM Marc Berg

                        @ammawel sagte in Test Withings v0.0.x:

                        fhem wieder zu installieren oder zu HomeAssistant zu wechseln.

                        Warte mal noch ... 🙂

                        Ich habe mir den Code mal angeschaut, es gab schon mal den vorsichtigen Versuch zu sortieren. Dieser Teil wurde aber wieder auskommentiert.

                        Darum habe ich jetzt eine Sortierung eingebaut (primär nach Datum/Zeit der Messung), sekundär nach Erstell-/bzw. Änderungsdatum.

                        Wenn du mutig bist, kannst du mal von Github testen:

                        https://github.com/Marc-Berg/ioBroker.withings

                        Wenn das funktioniert, kann ich einen PR einreichen, der Programmierer ist ja noch aktiv.

                        EDIT: da ich nur ca. einmal im Quartal auf die Waage steige, habe ich zu wenige Messwerte, um selbst zu testen.

                        A Abwesend
                        A Abwesend
                        ammawel
                        schrieb am zuletzt editiert von
                        #523

                        @marc-berg
                        Das hört sich ja super an! Da warte ich sehr gerne und freue mich auf das Testen.
                        Schon vorab ganz herzlichen Dank für Deine Mühen!

                        1 Antwort Letzte Antwort
                        1
                        • A ammawel

                          Ich habe mit der Waage Body-Smart und Adapterversion 0.0.16 / 0.0.13 auch das Problem der total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                          @prorun hat dazu im März 2024 ein issue auf github erstellt - bis heute leider ohne jede Reaktion.
                          Da auch auf andere issues keine Reaktion erfolgt, befürchte ich, dass der Adapter tot ist. Dann sollte er auch aus dem Repository genommen werden.
                          Oder hat jemand einen workaround, um stabil aktuelle Messwerte zu erhalten?

                          Für jede Hilfe bin ich dankbar.

                          Marc BergM Offline
                          Marc BergM Offline
                          Marc Berg
                          Most Active
                          schrieb am zuletzt editiert von Marc Berg
                          #524

                          @ammawel sagte in Test Withings v0.0.x:

                          total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                          Hast du noch ein paar Beispiele als Screenshots oder Export des Objektbaums, gern ohne die Messwerte? Mit meinen wenigen Daten kann ich es noch nicht nachstellen. Und erfasst du auch andere Daten als nur mit der Waage?

                          NUC10I3+Ubuntu+Docker+ioBroker+influxDB2+Node Red+RabbitMQ+Grafana

                          Pi-hole, Traefik, Checkmk, Conbee II+Zigbee2MQTT, ESPSomfy-RTS, LoRaWAN, Arduino, KiCad

                          Benutzt das Voting im Beitrag, wenn er euch geholfen hat.

                          A 2 Antworten Letzte Antwort
                          0
                          • Marc BergM Marc Berg

                            @ammawel sagte in Test Withings v0.0.x:

                            total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                            Hast du noch ein paar Beispiele als Screenshots oder Export des Objektbaums, gern ohne die Messwerte? Mit meinen wenigen Daten kann ich es noch nicht nachstellen. Und erfasst du auch andere Daten als nur mit der Waage?

                            A Abwesend
                            A Abwesend
                            ammawel
                            schrieb am zuletzt editiert von
                            #525

                            @marc-berg
                            Mit vielen Messwerten kann ich leider nicht dienen.
                            Die Waage ist neu, ich bin noch in der Testphase, habe gestern nach Frust alles zurückgesetzt und wahrscheinlich auch ein anderes Ziel.

                            Es geht nicht um den Verlauf der weihnachtlichen Plätzchen-Diät.
                            Vielmehr muss ich das Gewicht morgens und abends für einige Zeit dokumentieren. Zur Zeit erfolgt das mit alter Waage, Zettel mit Bleistift und manuellem Übertrag in eine Excel-Tabelle.
                            Mein Ziel ist es, "date" als Trigger zu verwenden und dann den aktuellen Gewichtswert bei Wägung in einem definierten Zeitfenster in die passende Spalte einer Zeile einer csv-Datei zu schreiben. Pro Tag eine Zeile mit zwei Werten und Zeit, automatisch gepflegt.

                            Und da tauchten gestern schon bei mehr als einer Wägung die Probleme auf.
                            Meistens enthielt grps01 wie im Forum beschrieben die aktuellen Werte, manchmal waren die aktuellen Werte aber auch in grps02 - auch das wurde hier im Forum schon als aktuelle Gruppe beschrieben. Und hin und wieder lief alles durcheinander, da war das Datum in grps02 aktuell, das Gewicht dazu in grps01 oder umgekehrt oder auch in grps03. Die Anzahl der abzurufenden Tage steht im Adapter auf 1, da ich nur den aktuellen Wert benötige.

                            Bei normaler Nutzung fällt es vielleicht gar nicht auf, wenn ein Gewicht zwischendurch vom Vortag ist, bei der Überwachung mit Dokumentation geht das aber nicht.

                            Ich danke Dir nochmal und werde nachher weiter testen. Bis jetzt sieht es sehr gut aus!

                            1 Antwort Letzte Antwort
                            0
                            • Marc BergM Marc Berg

                              @ammawel sagte in Test Withings v0.0.x:

                              total chaotisch in verschiedenen Unterordnern vom Typ measuregrpsXX abgelegten Werte. Die Datenpunkte "date" passen oft nicht zu den in dem jeweiligen Unterordner abgelegten Werte und die Eigenschaft "letzte Änderung" der Messwerte ist oft in allen Ordnern identisch.

                              Hast du noch ein paar Beispiele als Screenshots oder Export des Objektbaums, gern ohne die Messwerte? Mit meinen wenigen Daten kann ich es noch nicht nachstellen. Und erfasst du auch andere Daten als nur mit der Waage?

                              A Abwesend
                              A Abwesend
                              ammawel
                              schrieb am zuletzt editiert von
                              #526

                              @marc-berg
                              Guten Morgen,
                              ich habe gestern noch weiter getestet - die Werte sind ja nicht in der Waage gespeichert sondern auf den Withings-Servern in Frankreich.

                              Zusätzlich zu den wenigen vorhandenen "echten" Werten kann man in der Smartphone-App auch manuelle Messungen hinzufügen. Solche manuellen Eingaben habe ich ans Ende der tatsächlichen Messungen gehängt, an den Anfang und irgendwo zwischendrin willkürlich eingeschoben.

                              Das Ergebnis: Perfekt - in allen Fällen waren die Messungen im Adapter (Anzahl der abzufragenden Tage anpassen 😀 ) nach der nächsten Synchronisation in der richtigen Reihenfolge; Datum und Messwert passten in allen Fälen immer zu einander. Ich konnte keinen Fehler finden.

                              Marc BergM 1 Antwort Letzte Antwort
                              0
                              • A ammawel

                                @marc-berg
                                Guten Morgen,
                                ich habe gestern noch weiter getestet - die Werte sind ja nicht in der Waage gespeichert sondern auf den Withings-Servern in Frankreich.

                                Zusätzlich zu den wenigen vorhandenen "echten" Werten kann man in der Smartphone-App auch manuelle Messungen hinzufügen. Solche manuellen Eingaben habe ich ans Ende der tatsächlichen Messungen gehängt, an den Anfang und irgendwo zwischendrin willkürlich eingeschoben.

                                Das Ergebnis: Perfekt - in allen Fällen waren die Messungen im Adapter (Anzahl der abzufragenden Tage anpassen 😀 ) nach der nächsten Synchronisation in der richtigen Reihenfolge; Datum und Messwert passten in allen Fälen immer zu einander. Ich konnte keinen Fehler finden.

                                Marc BergM Offline
                                Marc BergM Offline
                                Marc Berg
                                Most Active
                                schrieb am zuletzt editiert von
                                #527

                                @ammawel sagte in Test Withings v0.0.x:

                                Ich konnte keinen Fehler finden.

                                PR erstellt: https://github.com/TA2k/ioBroker.withings/pull/44

                                NUC10I3+Ubuntu+Docker+ioBroker+influxDB2+Node Red+RabbitMQ+Grafana

                                Pi-hole, Traefik, Checkmk, Conbee II+Zigbee2MQTT, ESPSomfy-RTS, LoRaWAN, Arduino, KiCad

                                Benutzt das Voting im Beitrag, wenn er euch geholfen hat.

                                1 Antwort Letzte Antwort
                                0
                                • T Online
                                  T Online
                                  Tino 0
                                  schrieb am zuletzt editiert von
                                  #528

                                  Hallo,
                                  ich habe mal chatGPT gefragt ob er nicht die letzten Messwerte in einem eigenen Datenpunkt speichern kann. Dabei ist das rausgekommen:

                                  Screenshot 2025-11-17 230600.png

                                  Es wurden 2 Blöcke hinzugefügt und eine Zeile geändert. Wer es Testen möchte hier ist die main.js

                                  "use strict";
                                  
                                  /*
                                  * Created with @iobroker/create-adapter v2.0.1
                                  */
                                  
                                  // The adapter-core module gives you access to the core ioBroker functions
                                  // you need to create an adapter
                                  const utils = require("@iobroker/adapter-core");
                                  const axios = require("axios");
                                  const qs = require("qs");
                                  const Json2iob = require("./lib/json2iob");
                                  const tough = require("tough-cookie");
                                  const { HttpsCookieAgent } = require("http-cookie-agent/http");
                                  
                                  class Withings extends utils.Adapter {
                                   /**
                                    * @param {Partial<utils.AdapterOptions>} [options={}]
                                    */
                                   constructor(options) {
                                     super({
                                       ...options,
                                       name: "withings",
                                     });
                                     this.on("ready", this.onReady.bind(this));
                                     this.on("stateChange", this.onStateChange.bind(this));
                                     this.on("unload", this.onUnload.bind(this));
                                     this.deviceArray = [];
                                     this.json2iob = new Json2iob(this);
                                   }
                                  
                                   /**
                                    * Is called when databases are connected and adapter received configuration.
                                    */
                                   async onReady() {
                                     // Reset the connection indicator during startup
                                     this.setState("info.connection", false, true);
                                     if (this.config.interval < 0.5) {
                                       this.log.info("Set interval to minimum 0.5");
                                       this.config.interval = 0.5;
                                     }
                                     if (!this.config.lastDays || this.config.lastDays < 1) {
                                       this.config.lastDays = 1;
                                     }
                                     if (!this.config.lastHours || this.config.lastHours < 1) {
                                       this.config.lastHours = 1;
                                     }
                                     if (!this.config.username || !this.config.password || !this.config.clientid || !this.config.clientsecret) {
                                       this.log.error("Please set username and password in the instance settings");
                                       return;
                                     }
                                     this.userAgent = "ioBroker v0.0.1";
                                     this.cookieJar = new tough.CookieJar();
                                     this.requestClient = axios.create({
                                       jar: this.cookieJar,
                                       withCredentials: true,
                                       httpsAgent: new HttpsCookieAgent({ cookies: { jar: this.cookieJar } }),
                                     });
                                  
                                     this.updateInterval = null;
                                     this.reLoginTimeout = null;
                                     this.refreshTokenTimeout = null;
                                     this.session = [];
                                     await this.cleanOldVersion();
                                     this.subscribeStates("*");
                                  
                                     await this.login();
                                  
                                     if (this.session.length > 0) {
                                       await this.getDeviceList();
                                       await this.updateDevices();
                                       this.updateInterval = setInterval(async () => {
                                         await this.updateDevices();
                                       }, this.config.interval * 60 * 1000);
                                       this.refreshTokenInterval = setInterval(() => {
                                         this.refreshToken();
                                       }, this.session[0].expires_in * 1000);
                                     }
                                   }
                                   async login() {
                                     let loginHtml = await this.requestClient({
                                       method: "get",
                                       url:
                                         "https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=" +
                                         this.config.clientid +
                                         "&state=h4fhjnc2daoc3m&scope=user.activity,user.metrics,user.info&redirect_uri=http://localhost",
                                       headers: {
                                         Accept: "*/*",
                                         "User-Agent": this.userAgent,
                                       },
                                       jar: this.cookieJar,
                                       withCredentials: true,
                                     })
                                       .then((res) => {
                                         this.log.debug(JSON.stringify(res.data));
                                         this.log.debug(res.request.path);
                                         return res.data;
                                       })
                                       .catch((error) => {
                                         this.log.error(error);
                                         if (error.response) {
                                           this.log.error(JSON.stringify(error.response.data));
                                         }
                                       });
                                     let form = this.extractHidden(loginHtml);
                                     form.email = this.config.username;
                                     loginHtml = await this.requestClient({
                                       method: "post",
                                       url:
                                         "https://account.withings.com/new_workflow/login?r=https://account.withings.com/oauth2_user/account_login?response_type=code&client_id=" +
                                         this.config.clientid +
                                         "&state=h4fhjnc2daoc3m&scope=user.activity%2Cuser.metrics%2Cuser.info&redirect_uri=http%3A%2F%2Flocalhost&b=authorize2",
                                       headers: {
                                         Accept:
                                           "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                         "Accept-Language": "de",
                                         "Content-Type": "application/x-www-form-urlencoded",
                                       },
                                       jar: this.cookieJar,
                                       withCredentials: true,
                                       data: qs.stringify(form),
                                     })
                                       .then(async (res) => {
                                         return res.data;
                                       })
                                       .catch((error) => {
                                         this.log.error(error);
                                         if (error.response) {
                                           this.log.error(JSON.stringify(error.response.data));
                                         }
                                         return;
                                       });
                                  
                                     form = this.extractHidden(loginHtml);
                                     form.password = this.config.password;
                                  
                                     const resultArray = await this.requestClient({
                                       method: "post",
                                       url:
                                         "https://account.withings.com/new_workflow/password_check?r=https%3A%2F%2Faccount.withings.com%2Foauth2_user%2Faccount_login%3Fresponse_type%3Dcode%26client_id%3D" +
                                         this.config.clientid +
                                         "%26state%3Dh4fhjnc2daoc3m%26scope%3Duser.activity%252Cuser.metrics%252Cuser.info%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%26b%3Dauthorize2",
                                       headers: {
                                         Accept:
                                           "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                         "Accept-Language": "de",
                                         "Content-Type": "application/x-www-form-urlencoded",
                                       },
                                       jar: this.cookieJar,
                                       withCredentials: true,
                                       data: qs.stringify(form),
                                     })
                                       .then(async (res) => {
                                         this.log.debug(JSON.stringify(res.data));
                                         if (res.data.errors) {
                                           this.log.error(JSON.stringify(res.data));
                                           return;
                                         }
                                         if (res.data.indexOf("user_selection") !== -1) {
                                           let urlArray = res.data.split("response_type=code");
                                           urlArray.shift();
                                           urlArray = urlArray.map((url) => {
                                             //eslint-disable-next-line no-useless-escape
                                             return url.split('"')[0].replace(/\&amp;/g, "&");
                                           });
                                           let nameArray = res.data.split("selecteduser=");
                                           nameArray.shift();
                                           nameArray = nameArray = nameArray.map((name) => {
                                             const key = name.split('"')[0];
                                             const value = name.split('group-item">')[1].split("<")[0];
                                             return { id: key, name: value };
                                           });
                                           for (const element of nameArray) {
                                             await this.setObjectNotExistsAsync(element.id, {
                                               type: "device",
                                               common: {
                                                 name: element.name,
                                               },
                                               native: {},
                                             });
                                           }
                                           const responseArray = [];
                                           for (const url of urlArray) {
                                             await this.requestClient({
                                               method: "get",
                                               url: "https://account.withings.com/oauth2_user/account_login?response_type=code" + url,
                                               headers: {
                                                 Accept:
                                                   "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                                 "Accept-Language": "de",
                                               },
                                               jar: this.cookieJar,
                                               withCredentials: true,
                                             })
                                               .then((res) => {
                                                 this.log.debug(JSON.stringify(res.data));
                                                 this.log.debug(res.request.path);
                                                 responseArray.push(res);
                                                 return;
                                               })
                                               .catch((error) => {
                                                 this.log.error(error);
                                                 if (error.response) {
                                                   this.log.error(JSON.stringify(error.response.data));
                                                 }
                                                 return;
                                               });
                                           }
                                           return responseArray;
                                         } else {
                                           return [res];
                                         }
                                       })
                                       .catch((error) => {
                                         if (error.response && error.response.status === 302) {
                                           return [];
                                         }
                                         this.log.error(error);
                                         if (error.response) {
                                           this.log.error(JSON.stringify(error.response.data));
                                         }
                                         return [];
                                       });
                                  
                                     for (const result of resultArray) {
                                       if (!result) {
                                         return;
                                       }
                                       form = this.extractHidden(result.data);
                                       form.authorized = "1";
                                       const code = await this.requestClient({
                                         method: "post",
                                         url: "https://account.withings.com" + result.request.path,
                                         headers: {
                                           Accept:
                                             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                           "Accept-Language": "de",
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         data: qs.stringify(form),
                                         maxRedirects: 0,
                                       })
                                         .then((res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           this.log.debug(res.request.path);
                                           this.log.debug(res.headers.location);
                                           if (res.headers.location) {
                                             return res.headers.location.split("code=")[1];
                                           }
                                           this.log.warn("Please check username and password");
                                           return;
                                         })
                                         .catch((error) => {
                                           if (error.response && error.response.status === 302) {
                                             this.log.debug(JSON.stringify(error.response.headers));
                                             if (error.response.headers.location.indexOf("code=") === -1) {
                                               this.log.debug(JSON.stringify(error.response.headers));
                                               this.log.error("No code found");
                                               return null;
                                             }
                                             return error.response.headers.location.split("code=")[1].split("&")[0];
                                           }
                                  
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                         });
                                  
                                       await this.requestClient({
                                         method: "post",
                                         url: "https://wbsapi.withings.net/v2/oauth2",
                                         headers: {
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         data: qs.stringify({
                                           action: "requesttoken",
                                           grant_type: "authorization_code",
                                           client_id: this.config.clientid,
                                           client_secret: this.config.clientsecret,
                                           code: code,
                                           redirect_uri: "http://localhost",
                                         }),
                                       })
                                         .then(async (res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           if (res.data.error) {
                                             this.log.error(JSON.stringify(res.data));
                                             return;
                                           }
                                           this.session.push(res.data.body);
                                           await this.setObjectNotExistsAsync(res.data.body.userid, {
                                             type: "device",
                                             common: {
                                               name: "Hauptnutzer",
                                             },
                                             native: {},
                                           });
                                           this.setState("info.connection", true, true);
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                         });
                                     }
                                   }
                                   async getDeviceList() {
                                     for (const session of this.session) {
                                       await this.requestClient({
                                         method: "post",
                                         url: "https://wbsapi.withings.net/v2/user?action=getdevice",
                                         headers: {
                                           Authorization: "Bearer " + session.access_token,
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                       })
                                         .then(async (res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           if (!res.data.body.devices) {
                                             return;
                                           }
                                           for (const device of res.data.body.devices) {
                                             const id = device.deviceid;
                                             if (this.deviceArray.indexOf(id) === -1) {
                                               this.deviceArray.push(id);
                                             }
                                             const name = device.model;
                                  
                                             await this.setObjectNotExistsAsync(id, {
                                               type: "device",
                                               common: {
                                                 name: name,
                                               },
                                               native: {},
                                             });
                                             await this.setObjectNotExistsAsync(id + ".remote", {
                                               type: "channel",
                                               common: {
                                                 name: "Remote Controls",
                                               },
                                               native: {},
                                             });
                                  
                                             const remoteArray = [{ command: "Refresh", name: "True = Refresh" }];
                                             remoteArray.forEach((remote) => {
                                               this.setObjectNotExists(id + ".remote." + remote.command, {
                                                 type: "state",
                                                 common: {
                                                   name: remote.name || "",
                                                   type: remote.type || "boolean",
                                                   role: remote.role || "boolean",
                                                   write: true,
                                                   read: true,
                                                 },
                                                 native: {},
                                               });
                                             });
                                             this.json2iob.parse(id, device);
                                           }
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           error.response && this.log.error(JSON.stringify(error.response.data));
                                         });
                                     }
                                   }
                                  
                                   /**
                                    * Schreibe pro Measure-Type den letzten (neuesten) Messwert als State unter userid.lastMeasures.<type>
                                    * measuregrps wird idealerweise sortiert übergeben (neueste zuerst), aber wir schützen uns trotzdem.
                                    */
                                   async writeLastMeasures(userid, measuregrps, descriptions) {
                                     if (!Array.isArray(measuregrps)) return;
                                  
                                     // Channel anlegen
                                     await this.setObjectNotExistsAsync(userid + ".lastMeasures", {
                                       type: "channel",
                                       common: {
                                         name: "Letzte Messwerte",
                                       },
                                       native: {},
                                     });
                                  
                                     const seen = new Set();
                                  
                                     // Falls measuregrps nicht sortiert sind, ist das kein Problem – wir nehmen den ersten Vorkommnis je type,
                                     // weil im UpdateDevices die measuregrps bereits sortiert werden (neueste zuerst).
                                     for (const grp of measuregrps) {
                                       if (!grp || !Array.isArray(grp.measures)) continue;
                                       for (const m of grp.measures) {
                                         const t = m.type;
                                         if (seen.has(t)) continue; // schon den letzten Wert für diesen Typ geschrieben
                                         const val = m.value * Math.pow(10, m.unit);
                                         const stateId = `${userid}.lastMeasures.${t}`;
                                  
                                         await this.setObjectNotExistsAsync(stateId, {
                                           type: "state",
                                           common: {
                                             name: descriptions && descriptions[t] ? descriptions[t] : `Measure ${t}`,
                                             type: "number",
                                             role: "value",
                                             read: true,
                                             write: false,
                                           },
                                           native: {
                                             type: t,
                                             unit: m.unit,
                                           },
                                         });
                                  
                                         await this.setStateAsync(stateId, { val: val, ack: true });
                                         seen.add(t);
                                       }
                                     }
                                   }
                                  
                                   async updateDevices() {
                                     for (const session of this.session) {
                                       const userid = session.userid;
                                       const date = new Date().toISOString().split("T")[0];
                                       const startTimestampday = new Date().setDate(new Date().getDate() - this.config.lastDays);
                                       const startDateFormattedday = new Date(startTimestampday).toISOString().split("T")[0];
                                       const limitSeconds = this.config.lastDays * 24 * 60 * 60;
                                  
                                       const statusArray = [
                                         {
                                           path: "measures",
                                           url: "https://wbsapi.withings.net/measure",
                                           desc: "Measurements",
                                           data: {
                                             action: "getmeas",
                                             meastypes: "1,4,5,6,8,9,10,11,12,54,71,73,76,77,88,91,123,135,136,137,138,139,170",
                                  
                                             startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                             enddate: Math.round(Date.now() / 1000),
                                           },
                                           forceIndex: false,
                                           preferedArrayName: "type",
                                         },
                                         {
                                           path: "activity",
                                           url: "https://wbsapi.withings.net/v2/measure",
                                           desc: "Activity",
                                           data: {
                                             action: "getactivity",
                                             data_fields:
                                               "steps,distance,elevation,soft,moderate,intense,active,calories,totalcalories,hr_average,hr_min,hr_max,hr_zone_0,hr_zone_1,hr_zone_2,hr_zone_3",
                                             startdateymd: startDateFormattedday,
                                             enddateymd: date,
                                           },
                                           forceIndex: true,
                                         },
                                         {
                                           path: "heartList",
                                           url: "https://wbsapi.withings.net/v2/heart",
                                           desc: "List of ECG recordings",
                                           data: {
                                             action: "list",
                                             startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                             enddate: Math.round(Date.now() / 1000),
                                           },
                                           forceIndex: true,
                                         },
                                         {
                                           path: "sleepSummary",
                                           url: "https://wbsapi.withings.net/v2/sleep",
                                           desc: "Basic information about a night",
                                           data: {
                                             action: "getsummary",
                                             startdateymd: startDateFormattedday,
                                             enddateymd: date,
                                             data_fields:
                                               "breathing_disturbances_intensity,deepsleepduration,durationtosleep,durationtowakeup,hr_average,hr_max,hr_min,lightsleepduration,remsleepduration,rr_average,rr_max,rr_min,sleep_score,snoring,snoringepisodecount,wakeupcount,wakeupduration,nb_rem_episodes,sleep_efficiency,sleep_latency,total_sleep_time,total_timeinbed,wakeup_latency,waso,apnea_hypopnea_index,asleepduration,night_events,out_of_bed_count",
                                           },
                                           forceIndex: true,
                                         },
                                         {
                                           path: "sleep",
                                           url: "https://wbsapi.withings.net/v2/sleep",
                                           desc: "Sleep measures for the night ",
                                           data: {
                                             action: "get",
                                             startdate: Math.round(Date.now() / 1000) - this.config.lastHours * 60 * 60,
                                             enddate: Math.round(Date.now() / 1000),
                                             data_fields: "hr,rr,snoring",
                                           },
                                           forceIndex: true,
                                         },
                                       ];
                                       const headers = {
                                         authorization: "Bearer " + session.access_token,
                                         "user-agent": this.userAgent,
                                       };
                                       for (const element of statusArray) {
                                         await this.requestClient({
                                           method: "post",
                                           url: element.url,
                                           headers: headers,
                                           data: qs.stringify(element.data),
                                         })
                                           .then(async (res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (!res.data) {
                                               return;
                                             }
                                             const data = res.data.body;
                                             if (Array.isArray(data?.measuregrps)) {
                                               data.measuregrps.sort((a, b) => {
                                                 const numeric = (obj, key) => {
                                                   const value = obj && obj[key];
                                                   return typeof value === "number" ? value : Number(value) || 0;
                                                 };
                                                 const compareChain = ["date", "created", "modified", "grpid"];
                                                 for (const field of compareChain) {
                                                   const diff = numeric(b, field) - numeric(a, field);
                                                   if (diff !== 0) {
                                                     return diff;
                                                   }
                                                 }
                                                 return 0;
                                               });
                                             }
                                             if (data.activities) {
                                               data.activities.sort((a, b) => a.date.localeCompare(b.date));
                                             }
                                             if (element.path === "sleepSummary" || element.path === "sleep") {
                                               if (data.series && data.series.sort) {
                                                 data.series.sort((a, b) => b.startdate - a.startdate);
                                               }
                                             }
                                             if (element.path === "sleep" && data.series) {
                                               data.series.map((element) => {
                                                 for (const key in element) {
                                                   if (typeof element[key] === "object") {
                                                     const newArray = [];
                                                     for (const timestamp in element[key]) {
                                                       newArray.push({ timestamp: timestamp, value: element[key][timestamp] });
                                                     }
                                                     newArray.sort((a, b) => b.timestamp - a.timestamp);
                                                     element[key] = newArray;
                                                   }
                                                 }
                                               });
                                             }
                                             // if (data.measuregrps) {
                                             //     data.measuregrps.sort((a, b) => a.date - b.date);
                                             // }
                                             const descriptions = {
                                               1: "Weight (kg)",
                                               4: "Height (meter)",
                                               5: "Fat Free Mass (kg)",
                                               6: "Fat Ratio (%)",
                                               8: "Fat Mass Weight (kg)",
                                               9: "Diastolic Blood Pressure (mmHg)",
                                               10: "Systolic Blood Pressure (mmHg)",
                                               11: "Heart Pulse (bpm) - only for BPM and scale devices",
                                               12: "Temperature (celsius)",
                                               54: "SP02 (%)",
                                               71: "Body Temperature (celsius)",
                                               73: "Skin Temperature (celsius)",
                                               76: "Muscle Mass (kg)",
                                               77: "Hydration (kg)",
                                               88: "Bone Mass (kg)",
                                               91: "Pulse Wave Velocity (m/s)",
                                               123: "VO2 max is a numerical measurement of your body’s ability to consume oxygen (ml/min/kg).",
                                               135: "QRS interval duration based on ECG signal",
                                               136: "PR interval duration based on ECG signal",
                                               137: "QT interval duration based on ECG signal",
                                               138: "Corrected QT interval duration based on ECG signal",
                                               139: "Atrial fibrillation result from PPG",
                                               170: "Visceral Fat (without unity)",
                                             };
                                  
                                             // === NEU: Schreibe die letzten Messwerte pro Type in userid.lastMeasures.<type>
                                             if (element.path === "measures" && Array.isArray(data?.measuregrps)) {
                                               try {
                                                 await this.writeLastMeasures(userid, data.measuregrps, descriptions);
                                               } catch (e) {
                                                 this.log.error("writeLastMeasures failed: " + e);
                                               }
                                             }
                                             // === ENDE NEU
                                  
                                             this.json2iob.parse(userid + "." + element.path, data, {
                                               forceIndex: element.forceIndex,
                                               preferedArrayName: element.preferedArrayName,
                                               channelName: element.desc,
                                               descriptions: descriptions,
                                             });
                                           })
                                           .catch((error) => {
                                             if (error.response) {
                                               if (error.response.status === 401) {
                                                 error.response && this.log.debug(JSON.stringify(error.response.data));
                                                 this.log.info(element.path + " receive 401 error. Refresh Token in 60 seconds");
                                                 this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                                 this.refreshTokenTimeout = setTimeout(() => {
                                                   this.refreshToken();
                                                 }, 1000 * 60);
                                  
                                                 return;
                                               }
                                             }
                                             this.log.error(element.url);
                                             this.log.error(error);
                                             error.response && this.log.error(JSON.stringify(error.response.data));
                                           });
                                       }
                                     }
                                   }
                                   async refreshToken() {
                                     if (this.session.length === 0) {
                                       this.log.error("No session found relogin");
                                       await this.login();
                                       return;
                                     }
                                  
                                     for (const session of this.session) {
                                       await this.requestClient({
                                         method: "post",
                                         url: "https://wbsapi.withings.net/v2/oauth2",
                                         headers: {
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         data: qs.stringify({
                                           action: "requesttoken",
                                           grant_type: "refresh_token",
                                           client_id: this.config.clientid,
                                           client_secret: this.config.clientsecret,
                                           refresh_token: session.refresh_token,
                                         }),
                                       })
                                         .then((res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           if (res.data.body && res.data.body.access_token) {
                                             const index = this.session.indexOf(session);
                                             this.session[index] = res.data.body;
                                             this.setState("info.connection", true, true);
                                           }
                                         })
                                         .catch((error) => {
                                           this.log.error("refresh token failed");
                                           this.log.error(error);
                                           error.response && this.log.error(JSON.stringify(error.response.data));
                                           this.log.error("Start relogin in 1min");
                                           this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                           this.reLoginTimeout = setTimeout(() => {
                                             this.login();
                                           }, 1000 * 60 * 1);
                                         });
                                     }
                                   }
                                   async cleanOldVersion() {
                                     const cleanOldVersion = await this.getObjectAsync("oldVersionCleanedv2");
                                     if (!cleanOldVersion) {
                                       this.log.info("Please wait a few minutes.... clean old version");
                                       await this.delForeignObjectAsync(this.name + "." + this.instance, { recursive: true });
                                       await this.setObjectNotExistsAsync("oldVersionCleanedv2", {
                                         type: "state",
                                         common: {
                                           type: "boolean",
                                           role: "boolean",
                                           write: false,
                                           read: true,
                                         },
                                         native: {},
                                       });
                                  
                                       this.log.info("Done with cleaning, restart adapter");
                                       this.restart();
                                     }
                                   }
                                   extractHidden(body) {
                                     const returnObject = {};
                                     if (!body) {
                                       this.log.warn("No body found");
                                     }
                                     let matches;
                                     if (body.matchAll) {
                                       matches = body.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g);
                                     } else {
                                       this.log.warn(
                                         "The adapter needs in the future NodeJS v12. https://forum.iobroker.net/topic/22867/how-to-node-js-f%C3%BCr-iobroker-richtig-updaten",
                                       );
                                       matches = this.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g, body);
                                     }
                                     for (const match of matches) {
                                       returnObject[match[1]] = match[2];
                                     }
                                     return returnObject;
                                   }
                                   matchAll(re, str) {
                                     let match;
                                     const matches = [];
                                  
                                     while ((match = re.exec(str))) {
                                       // add all matched groups
                                       matches.push(match);
                                     }
                                  
                                     return matches;
                                   }
                                   /**
                                    * Is called when adapter shuts down - callback has to be called under any circumstances!
                                    * @param {() => void} callback
                                    */
                                   onUnload(callback) {
                                     try {
                                       this.setState("info.connection", false, true);
                                       this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                       this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                       this.updateInterval && clearInterval(this.updateInterval);
                                       this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
                                       callback();
                                     } catch (e) {
                                       this.log.error(e);
                                       callback();
                                     }
                                   }
                                  
                                   /**
                                    * Is called if a subscribed state changes
                                    * @param {string} id
                                    * @param {ioBroker.State | null | undefined} state
                                    */
                                   async onStateChange(id, state) {
                                     if (state) {
                                       if (!state.ack) {
                                         // const deviceId = id.split(".")[2];
                                         const command = id.split(".")[4];
                                         if (id.split(".")[3] !== "remote") {
                                           return;
                                         }
                                  
                                         if (command === "Refresh") {
                                           this.updateDevices();
                                         }
                                       }
                                     }
                                   }
                                  }
                                  
                                  if (require.main !== module) {
                                   // Export the constructor in compact mode
                                   /**
                                    * @param {Partial<utils.AdapterOptions>} [options={}]
                                    */
                                   module.exports = (options) => new Withings(options);
                                  } else {
                                   // otherwise start the instance directly
                                   new Withings();
                                  }
                                  
                                  

                                  Gruß Tino

                                  T 1 Antwort Letzte Antwort
                                  0
                                  • T Tino 0

                                    Hallo,
                                    ich habe mal chatGPT gefragt ob er nicht die letzten Messwerte in einem eigenen Datenpunkt speichern kann. Dabei ist das rausgekommen:

                                    Screenshot 2025-11-17 230600.png

                                    Es wurden 2 Blöcke hinzugefügt und eine Zeile geändert. Wer es Testen möchte hier ist die main.js

                                    "use strict";
                                    
                                    /*
                                    * Created with @iobroker/create-adapter v2.0.1
                                    */
                                    
                                    // The adapter-core module gives you access to the core ioBroker functions
                                    // you need to create an adapter
                                    const utils = require("@iobroker/adapter-core");
                                    const axios = require("axios");
                                    const qs = require("qs");
                                    const Json2iob = require("./lib/json2iob");
                                    const tough = require("tough-cookie");
                                    const { HttpsCookieAgent } = require("http-cookie-agent/http");
                                    
                                    class Withings extends utils.Adapter {
                                     /**
                                      * @param {Partial<utils.AdapterOptions>} [options={}]
                                      */
                                     constructor(options) {
                                       super({
                                         ...options,
                                         name: "withings",
                                       });
                                       this.on("ready", this.onReady.bind(this));
                                       this.on("stateChange", this.onStateChange.bind(this));
                                       this.on("unload", this.onUnload.bind(this));
                                       this.deviceArray = [];
                                       this.json2iob = new Json2iob(this);
                                     }
                                    
                                     /**
                                      * Is called when databases are connected and adapter received configuration.
                                      */
                                     async onReady() {
                                       // Reset the connection indicator during startup
                                       this.setState("info.connection", false, true);
                                       if (this.config.interval < 0.5) {
                                         this.log.info("Set interval to minimum 0.5");
                                         this.config.interval = 0.5;
                                       }
                                       if (!this.config.lastDays || this.config.lastDays < 1) {
                                         this.config.lastDays = 1;
                                       }
                                       if (!this.config.lastHours || this.config.lastHours < 1) {
                                         this.config.lastHours = 1;
                                       }
                                       if (!this.config.username || !this.config.password || !this.config.clientid || !this.config.clientsecret) {
                                         this.log.error("Please set username and password in the instance settings");
                                         return;
                                       }
                                       this.userAgent = "ioBroker v0.0.1";
                                       this.cookieJar = new tough.CookieJar();
                                       this.requestClient = axios.create({
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         httpsAgent: new HttpsCookieAgent({ cookies: { jar: this.cookieJar } }),
                                       });
                                    
                                       this.updateInterval = null;
                                       this.reLoginTimeout = null;
                                       this.refreshTokenTimeout = null;
                                       this.session = [];
                                       await this.cleanOldVersion();
                                       this.subscribeStates("*");
                                    
                                       await this.login();
                                    
                                       if (this.session.length > 0) {
                                         await this.getDeviceList();
                                         await this.updateDevices();
                                         this.updateInterval = setInterval(async () => {
                                           await this.updateDevices();
                                         }, this.config.interval * 60 * 1000);
                                         this.refreshTokenInterval = setInterval(() => {
                                           this.refreshToken();
                                         }, this.session[0].expires_in * 1000);
                                       }
                                     }
                                     async login() {
                                       let loginHtml = await this.requestClient({
                                         method: "get",
                                         url:
                                           "https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=" +
                                           this.config.clientid +
                                           "&state=h4fhjnc2daoc3m&scope=user.activity,user.metrics,user.info&redirect_uri=http://localhost",
                                         headers: {
                                           Accept: "*/*",
                                           "User-Agent": this.userAgent,
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                       })
                                         .then((res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           this.log.debug(res.request.path);
                                           return res.data;
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                         });
                                       let form = this.extractHidden(loginHtml);
                                       form.email = this.config.username;
                                       loginHtml = await this.requestClient({
                                         method: "post",
                                         url:
                                           "https://account.withings.com/new_workflow/login?r=https://account.withings.com/oauth2_user/account_login?response_type=code&client_id=" +
                                           this.config.clientid +
                                           "&state=h4fhjnc2daoc3m&scope=user.activity%2Cuser.metrics%2Cuser.info&redirect_uri=http%3A%2F%2Flocalhost&b=authorize2",
                                         headers: {
                                           Accept:
                                             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                           "Accept-Language": "de",
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         data: qs.stringify(form),
                                       })
                                         .then(async (res) => {
                                           return res.data;
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                           return;
                                         });
                                    
                                       form = this.extractHidden(loginHtml);
                                       form.password = this.config.password;
                                    
                                       const resultArray = await this.requestClient({
                                         method: "post",
                                         url:
                                           "https://account.withings.com/new_workflow/password_check?r=https%3A%2F%2Faccount.withings.com%2Foauth2_user%2Faccount_login%3Fresponse_type%3Dcode%26client_id%3D" +
                                           this.config.clientid +
                                           "%26state%3Dh4fhjnc2daoc3m%26scope%3Duser.activity%252Cuser.metrics%252Cuser.info%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%26b%3Dauthorize2",
                                         headers: {
                                           Accept:
                                             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                           "Accept-Language": "de",
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         data: qs.stringify(form),
                                       })
                                         .then(async (res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           if (res.data.errors) {
                                             this.log.error(JSON.stringify(res.data));
                                             return;
                                           }
                                           if (res.data.indexOf("user_selection") !== -1) {
                                             let urlArray = res.data.split("response_type=code");
                                             urlArray.shift();
                                             urlArray = urlArray.map((url) => {
                                               //eslint-disable-next-line no-useless-escape
                                               return url.split('"')[0].replace(/\&amp;/g, "&");
                                             });
                                             let nameArray = res.data.split("selecteduser=");
                                             nameArray.shift();
                                             nameArray = nameArray = nameArray.map((name) => {
                                               const key = name.split('"')[0];
                                               const value = name.split('group-item">')[1].split("<")[0];
                                               return { id: key, name: value };
                                             });
                                             for (const element of nameArray) {
                                               await this.setObjectNotExistsAsync(element.id, {
                                                 type: "device",
                                                 common: {
                                                   name: element.name,
                                                 },
                                                 native: {},
                                               });
                                             }
                                             const responseArray = [];
                                             for (const url of urlArray) {
                                               await this.requestClient({
                                                 method: "get",
                                                 url: "https://account.withings.com/oauth2_user/account_login?response_type=code" + url,
                                                 headers: {
                                                   Accept:
                                                     "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                                   "Accept-Language": "de",
                                                 },
                                                 jar: this.cookieJar,
                                                 withCredentials: true,
                                               })
                                                 .then((res) => {
                                                   this.log.debug(JSON.stringify(res.data));
                                                   this.log.debug(res.request.path);
                                                   responseArray.push(res);
                                                   return;
                                                 })
                                                 .catch((error) => {
                                                   this.log.error(error);
                                                   if (error.response) {
                                                     this.log.error(JSON.stringify(error.response.data));
                                                   }
                                                   return;
                                                 });
                                             }
                                             return responseArray;
                                           } else {
                                             return [res];
                                           }
                                         })
                                         .catch((error) => {
                                           if (error.response && error.response.status === 302) {
                                             return [];
                                           }
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                           return [];
                                         });
                                    
                                       for (const result of resultArray) {
                                         if (!result) {
                                           return;
                                         }
                                         form = this.extractHidden(result.data);
                                         form.authorized = "1";
                                         const code = await this.requestClient({
                                           method: "post",
                                           url: "https://account.withings.com" + result.request.path,
                                           headers: {
                                             Accept:
                                               "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                             "Accept-Language": "de",
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           jar: this.cookieJar,
                                           withCredentials: true,
                                           data: qs.stringify(form),
                                           maxRedirects: 0,
                                         })
                                           .then((res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             this.log.debug(res.request.path);
                                             this.log.debug(res.headers.location);
                                             if (res.headers.location) {
                                               return res.headers.location.split("code=")[1];
                                             }
                                             this.log.warn("Please check username and password");
                                             return;
                                           })
                                           .catch((error) => {
                                             if (error.response && error.response.status === 302) {
                                               this.log.debug(JSON.stringify(error.response.headers));
                                               if (error.response.headers.location.indexOf("code=") === -1) {
                                                 this.log.debug(JSON.stringify(error.response.headers));
                                                 this.log.error("No code found");
                                                 return null;
                                               }
                                               return error.response.headers.location.split("code=")[1].split("&")[0];
                                             }
                                    
                                             this.log.error(error);
                                             if (error.response) {
                                               this.log.error(JSON.stringify(error.response.data));
                                             }
                                           });
                                    
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/oauth2",
                                           headers: {
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           data: qs.stringify({
                                             action: "requesttoken",
                                             grant_type: "authorization_code",
                                             client_id: this.config.clientid,
                                             client_secret: this.config.clientsecret,
                                             code: code,
                                             redirect_uri: "http://localhost",
                                           }),
                                         })
                                           .then(async (res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (res.data.error) {
                                               this.log.error(JSON.stringify(res.data));
                                               return;
                                             }
                                             this.session.push(res.data.body);
                                             await this.setObjectNotExistsAsync(res.data.body.userid, {
                                               type: "device",
                                               common: {
                                                 name: "Hauptnutzer",
                                               },
                                               native: {},
                                             });
                                             this.setState("info.connection", true, true);
                                           })
                                           .catch((error) => {
                                             this.log.error(error);
                                             if (error.response) {
                                               this.log.error(JSON.stringify(error.response.data));
                                             }
                                           });
                                       }
                                     }
                                     async getDeviceList() {
                                       for (const session of this.session) {
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/user?action=getdevice",
                                           headers: {
                                             Authorization: "Bearer " + session.access_token,
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                         })
                                           .then(async (res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (!res.data.body.devices) {
                                               return;
                                             }
                                             for (const device of res.data.body.devices) {
                                               const id = device.deviceid;
                                               if (this.deviceArray.indexOf(id) === -1) {
                                                 this.deviceArray.push(id);
                                               }
                                               const name = device.model;
                                    
                                               await this.setObjectNotExistsAsync(id, {
                                                 type: "device",
                                                 common: {
                                                   name: name,
                                                 },
                                                 native: {},
                                               });
                                               await this.setObjectNotExistsAsync(id + ".remote", {
                                                 type: "channel",
                                                 common: {
                                                   name: "Remote Controls",
                                                 },
                                                 native: {},
                                               });
                                    
                                               const remoteArray = [{ command: "Refresh", name: "True = Refresh" }];
                                               remoteArray.forEach((remote) => {
                                                 this.setObjectNotExists(id + ".remote." + remote.command, {
                                                   type: "state",
                                                   common: {
                                                     name: remote.name || "",
                                                     type: remote.type || "boolean",
                                                     role: remote.role || "boolean",
                                                     write: true,
                                                     read: true,
                                                   },
                                                   native: {},
                                                 });
                                               });
                                               this.json2iob.parse(id, device);
                                             }
                                           })
                                           .catch((error) => {
                                             this.log.error(error);
                                             error.response && this.log.error(JSON.stringify(error.response.data));
                                           });
                                       }
                                     }
                                    
                                     /**
                                      * Schreibe pro Measure-Type den letzten (neuesten) Messwert als State unter userid.lastMeasures.<type>
                                      * measuregrps wird idealerweise sortiert übergeben (neueste zuerst), aber wir schützen uns trotzdem.
                                      */
                                     async writeLastMeasures(userid, measuregrps, descriptions) {
                                       if (!Array.isArray(measuregrps)) return;
                                    
                                       // Channel anlegen
                                       await this.setObjectNotExistsAsync(userid + ".lastMeasures", {
                                         type: "channel",
                                         common: {
                                           name: "Letzte Messwerte",
                                         },
                                         native: {},
                                       });
                                    
                                       const seen = new Set();
                                    
                                       // Falls measuregrps nicht sortiert sind, ist das kein Problem – wir nehmen den ersten Vorkommnis je type,
                                       // weil im UpdateDevices die measuregrps bereits sortiert werden (neueste zuerst).
                                       for (const grp of measuregrps) {
                                         if (!grp || !Array.isArray(grp.measures)) continue;
                                         for (const m of grp.measures) {
                                           const t = m.type;
                                           if (seen.has(t)) continue; // schon den letzten Wert für diesen Typ geschrieben
                                           const val = m.value * Math.pow(10, m.unit);
                                           const stateId = `${userid}.lastMeasures.${t}`;
                                    
                                           await this.setObjectNotExistsAsync(stateId, {
                                             type: "state",
                                             common: {
                                               name: descriptions && descriptions[t] ? descriptions[t] : `Measure ${t}`,
                                               type: "number",
                                               role: "value",
                                               read: true,
                                               write: false,
                                             },
                                             native: {
                                               type: t,
                                               unit: m.unit,
                                             },
                                           });
                                    
                                           await this.setStateAsync(stateId, { val: val, ack: true });
                                           seen.add(t);
                                         }
                                       }
                                     }
                                    
                                     async updateDevices() {
                                       for (const session of this.session) {
                                         const userid = session.userid;
                                         const date = new Date().toISOString().split("T")[0];
                                         const startTimestampday = new Date().setDate(new Date().getDate() - this.config.lastDays);
                                         const startDateFormattedday = new Date(startTimestampday).toISOString().split("T")[0];
                                         const limitSeconds = this.config.lastDays * 24 * 60 * 60;
                                    
                                         const statusArray = [
                                           {
                                             path: "measures",
                                             url: "https://wbsapi.withings.net/measure",
                                             desc: "Measurements",
                                             data: {
                                               action: "getmeas",
                                               meastypes: "1,4,5,6,8,9,10,11,12,54,71,73,76,77,88,91,123,135,136,137,138,139,170",
                                    
                                               startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                               enddate: Math.round(Date.now() / 1000),
                                             },
                                             forceIndex: false,
                                             preferedArrayName: "type",
                                           },
                                           {
                                             path: "activity",
                                             url: "https://wbsapi.withings.net/v2/measure",
                                             desc: "Activity",
                                             data: {
                                               action: "getactivity",
                                               data_fields:
                                                 "steps,distance,elevation,soft,moderate,intense,active,calories,totalcalories,hr_average,hr_min,hr_max,hr_zone_0,hr_zone_1,hr_zone_2,hr_zone_3",
                                               startdateymd: startDateFormattedday,
                                               enddateymd: date,
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "heartList",
                                             url: "https://wbsapi.withings.net/v2/heart",
                                             desc: "List of ECG recordings",
                                             data: {
                                               action: "list",
                                               startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                               enddate: Math.round(Date.now() / 1000),
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "sleepSummary",
                                             url: "https://wbsapi.withings.net/v2/sleep",
                                             desc: "Basic information about a night",
                                             data: {
                                               action: "getsummary",
                                               startdateymd: startDateFormattedday,
                                               enddateymd: date,
                                               data_fields:
                                                 "breathing_disturbances_intensity,deepsleepduration,durationtosleep,durationtowakeup,hr_average,hr_max,hr_min,lightsleepduration,remsleepduration,rr_average,rr_max,rr_min,sleep_score,snoring,snoringepisodecount,wakeupcount,wakeupduration,nb_rem_episodes,sleep_efficiency,sleep_latency,total_sleep_time,total_timeinbed,wakeup_latency,waso,apnea_hypopnea_index,asleepduration,night_events,out_of_bed_count",
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "sleep",
                                             url: "https://wbsapi.withings.net/v2/sleep",
                                             desc: "Sleep measures for the night ",
                                             data: {
                                               action: "get",
                                               startdate: Math.round(Date.now() / 1000) - this.config.lastHours * 60 * 60,
                                               enddate: Math.round(Date.now() / 1000),
                                               data_fields: "hr,rr,snoring",
                                             },
                                             forceIndex: true,
                                           },
                                         ];
                                         const headers = {
                                           authorization: "Bearer " + session.access_token,
                                           "user-agent": this.userAgent,
                                         };
                                         for (const element of statusArray) {
                                           await this.requestClient({
                                             method: "post",
                                             url: element.url,
                                             headers: headers,
                                             data: qs.stringify(element.data),
                                           })
                                             .then(async (res) => {
                                               this.log.debug(JSON.stringify(res.data));
                                               if (!res.data) {
                                                 return;
                                               }
                                               const data = res.data.body;
                                               if (Array.isArray(data?.measuregrps)) {
                                                 data.measuregrps.sort((a, b) => {
                                                   const numeric = (obj, key) => {
                                                     const value = obj && obj[key];
                                                     return typeof value === "number" ? value : Number(value) || 0;
                                                   };
                                                   const compareChain = ["date", "created", "modified", "grpid"];
                                                   for (const field of compareChain) {
                                                     const diff = numeric(b, field) - numeric(a, field);
                                                     if (diff !== 0) {
                                                       return diff;
                                                     }
                                                   }
                                                   return 0;
                                                 });
                                               }
                                               if (data.activities) {
                                                 data.activities.sort((a, b) => a.date.localeCompare(b.date));
                                               }
                                               if (element.path === "sleepSummary" || element.path === "sleep") {
                                                 if (data.series && data.series.sort) {
                                                   data.series.sort((a, b) => b.startdate - a.startdate);
                                                 }
                                               }
                                               if (element.path === "sleep" && data.series) {
                                                 data.series.map((element) => {
                                                   for (const key in element) {
                                                     if (typeof element[key] === "object") {
                                                       const newArray = [];
                                                       for (const timestamp in element[key]) {
                                                         newArray.push({ timestamp: timestamp, value: element[key][timestamp] });
                                                       }
                                                       newArray.sort((a, b) => b.timestamp - a.timestamp);
                                                       element[key] = newArray;
                                                     }
                                                   }
                                                 });
                                               }
                                               // if (data.measuregrps) {
                                               //     data.measuregrps.sort((a, b) => a.date - b.date);
                                               // }
                                               const descriptions = {
                                                 1: "Weight (kg)",
                                                 4: "Height (meter)",
                                                 5: "Fat Free Mass (kg)",
                                                 6: "Fat Ratio (%)",
                                                 8: "Fat Mass Weight (kg)",
                                                 9: "Diastolic Blood Pressure (mmHg)",
                                                 10: "Systolic Blood Pressure (mmHg)",
                                                 11: "Heart Pulse (bpm) - only for BPM and scale devices",
                                                 12: "Temperature (celsius)",
                                                 54: "SP02 (%)",
                                                 71: "Body Temperature (celsius)",
                                                 73: "Skin Temperature (celsius)",
                                                 76: "Muscle Mass (kg)",
                                                 77: "Hydration (kg)",
                                                 88: "Bone Mass (kg)",
                                                 91: "Pulse Wave Velocity (m/s)",
                                                 123: "VO2 max is a numerical measurement of your body’s ability to consume oxygen (ml/min/kg).",
                                                 135: "QRS interval duration based on ECG signal",
                                                 136: "PR interval duration based on ECG signal",
                                                 137: "QT interval duration based on ECG signal",
                                                 138: "Corrected QT interval duration based on ECG signal",
                                                 139: "Atrial fibrillation result from PPG",
                                                 170: "Visceral Fat (without unity)",
                                               };
                                    
                                               // === NEU: Schreibe die letzten Messwerte pro Type in userid.lastMeasures.<type>
                                               if (element.path === "measures" && Array.isArray(data?.measuregrps)) {
                                                 try {
                                                   await this.writeLastMeasures(userid, data.measuregrps, descriptions);
                                                 } catch (e) {
                                                   this.log.error("writeLastMeasures failed: " + e);
                                                 }
                                               }
                                               // === ENDE NEU
                                    
                                               this.json2iob.parse(userid + "." + element.path, data, {
                                                 forceIndex: element.forceIndex,
                                                 preferedArrayName: element.preferedArrayName,
                                                 channelName: element.desc,
                                                 descriptions: descriptions,
                                               });
                                             })
                                             .catch((error) => {
                                               if (error.response) {
                                                 if (error.response.status === 401) {
                                                   error.response && this.log.debug(JSON.stringify(error.response.data));
                                                   this.log.info(element.path + " receive 401 error. Refresh Token in 60 seconds");
                                                   this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                                   this.refreshTokenTimeout = setTimeout(() => {
                                                     this.refreshToken();
                                                   }, 1000 * 60);
                                    
                                                   return;
                                                 }
                                               }
                                               this.log.error(element.url);
                                               this.log.error(error);
                                               error.response && this.log.error(JSON.stringify(error.response.data));
                                             });
                                         }
                                       }
                                     }
                                     async refreshToken() {
                                       if (this.session.length === 0) {
                                         this.log.error("No session found relogin");
                                         await this.login();
                                         return;
                                       }
                                    
                                       for (const session of this.session) {
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/oauth2",
                                           headers: {
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           data: qs.stringify({
                                             action: "requesttoken",
                                             grant_type: "refresh_token",
                                             client_id: this.config.clientid,
                                             client_secret: this.config.clientsecret,
                                             refresh_token: session.refresh_token,
                                           }),
                                         })
                                           .then((res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (res.data.body && res.data.body.access_token) {
                                               const index = this.session.indexOf(session);
                                               this.session[index] = res.data.body;
                                               this.setState("info.connection", true, true);
                                             }
                                           })
                                           .catch((error) => {
                                             this.log.error("refresh token failed");
                                             this.log.error(error);
                                             error.response && this.log.error(JSON.stringify(error.response.data));
                                             this.log.error("Start relogin in 1min");
                                             this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                             this.reLoginTimeout = setTimeout(() => {
                                               this.login();
                                             }, 1000 * 60 * 1);
                                           });
                                       }
                                     }
                                     async cleanOldVersion() {
                                       const cleanOldVersion = await this.getObjectAsync("oldVersionCleanedv2");
                                       if (!cleanOldVersion) {
                                         this.log.info("Please wait a few minutes.... clean old version");
                                         await this.delForeignObjectAsync(this.name + "." + this.instance, { recursive: true });
                                         await this.setObjectNotExistsAsync("oldVersionCleanedv2", {
                                           type: "state",
                                           common: {
                                             type: "boolean",
                                             role: "boolean",
                                             write: false,
                                             read: true,
                                           },
                                           native: {},
                                         });
                                    
                                         this.log.info("Done with cleaning, restart adapter");
                                         this.restart();
                                       }
                                     }
                                     extractHidden(body) {
                                       const returnObject = {};
                                       if (!body) {
                                         this.log.warn("No body found");
                                       }
                                       let matches;
                                       if (body.matchAll) {
                                         matches = body.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g);
                                       } else {
                                         this.log.warn(
                                           "The adapter needs in the future NodeJS v12. https://forum.iobroker.net/topic/22867/how-to-node-js-f%C3%BCr-iobroker-richtig-updaten",
                                         );
                                         matches = this.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g, body);
                                       }
                                       for (const match of matches) {
                                         returnObject[match[1]] = match[2];
                                       }
                                       return returnObject;
                                     }
                                     matchAll(re, str) {
                                       let match;
                                       const matches = [];
                                    
                                       while ((match = re.exec(str))) {
                                         // add all matched groups
                                         matches.push(match);
                                       }
                                    
                                       return matches;
                                     }
                                     /**
                                      * Is called when adapter shuts down - callback has to be called under any circumstances!
                                      * @param {() => void} callback
                                      */
                                     onUnload(callback) {
                                       try {
                                         this.setState("info.connection", false, true);
                                         this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                         this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                         this.updateInterval && clearInterval(this.updateInterval);
                                         this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
                                         callback();
                                       } catch (e) {
                                         this.log.error(e);
                                         callback();
                                       }
                                     }
                                    
                                     /**
                                      * Is called if a subscribed state changes
                                      * @param {string} id
                                      * @param {ioBroker.State | null | undefined} state
                                      */
                                     async onStateChange(id, state) {
                                       if (state) {
                                         if (!state.ack) {
                                           // const deviceId = id.split(".")[2];
                                           const command = id.split(".")[4];
                                           if (id.split(".")[3] !== "remote") {
                                             return;
                                           }
                                    
                                           if (command === "Refresh") {
                                             this.updateDevices();
                                           }
                                         }
                                       }
                                     }
                                    }
                                    
                                    if (require.main !== module) {
                                     // Export the constructor in compact mode
                                     /**
                                      * @param {Partial<utils.AdapterOptions>} [options={}]
                                      */
                                     module.exports = (options) => new Withings(options);
                                    } else {
                                     // otherwise start the instance directly
                                     new Withings();
                                    }
                                    
                                    

                                    Gruß Tino

                                    T Online
                                    T Online
                                    Tino 0
                                    schrieb zuletzt editiert von Tino 0
                                    #529

                                    hier ist noch eine Version mit lastActivity und lastSleep

                                    "use strict";
                                    
                                    /*
                                    * Created with @iobroker/create-adapter v2.0.1
                                    */
                                    
                                    // The adapter-core module gives you access to the core ioBroker functions
                                    // you need to create an adapter
                                    const utils = require("@iobroker/adapter-core");
                                    const axios = require("axios");
                                    const qs = require("qs");
                                    const Json2iob = require("./lib/json2iob");
                                    const tough = require("tough-cookie");
                                    const { HttpsCookieAgent } = require("http-cookie-agent/http");
                                    
                                    class Withings extends utils.Adapter {
                                     /**
                                      * @param {Partial<utils.AdapterOptions>} [options={}]
                                      */
                                     constructor(options) {
                                       super({
                                         ...options,
                                         name: "withings",
                                       });
                                       this.on("ready", this.onReady.bind(this));
                                       this.on("stateChange", this.onStateChange.bind(this));
                                       this.on("unload", this.onUnload.bind(this));
                                       this.deviceArray = [];
                                       this.json2iob = new Json2iob(this);
                                     }
                                    
                                     /**
                                      * Is called when databases are connected and adapter received configuration.
                                      */
                                     async onReady() {
                                       // Reset the connection indicator during startup
                                       this.setState("info.connection", false, true);
                                       if (this.config.interval < 0.5) {
                                         this.log.info("Set interval to minimum 0.5");
                                         this.config.interval = 0.5;
                                       }
                                       if (!this.config.lastDays || this.config.lastDays < 1) {
                                         this.config.lastDays = 1;
                                       }
                                       if (!this.config.lastHours || this.config.lastHours < 1) {
                                         this.config.lastHours = 1;
                                       }
                                       if (!this.config.username || !this.config.password || !this.config.clientid || !this.config.clientsecret) {
                                         this.log.error("Please set username and password in the instance settings");
                                         return;
                                       }
                                       this.userAgent = "ioBroker v0.0.1";
                                       this.cookieJar = new tough.CookieJar();
                                       this.requestClient = axios.create({
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         httpsAgent: new HttpsCookieAgent({ cookies: { jar: this.cookieJar } }),
                                       });
                                    
                                       this.updateInterval = null;
                                       this.reLoginTimeout = null;
                                       this.refreshTokenTimeout = null;
                                       this.session = [];
                                       await this.cleanOldVersion();
                                       this.subscribeStates("*");
                                    
                                       await this.login();
                                    
                                       if (this.session.length > 0) {
                                         await this.getDeviceList();
                                         await this.updateDevices();
                                         this.updateInterval = setInterval(async () => {
                                           await this.updateDevices();
                                         }, this.config.interval * 60 * 1000);
                                         this.refreshTokenInterval = setInterval(() => {
                                           this.refreshToken();
                                         }, this.session[0].expires_in * 1000);
                                       }
                                     }
                                     async login() {
                                       let loginHtml = await this.requestClient({
                                         method: "get",
                                         url:
                                           "https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=" +
                                           this.config.clientid +
                                           "&state=h4fhjnc2daoc3m&scope=user.activity,user.metrics,user.info&redirect_uri=http://localhost",
                                         headers: {
                                           Accept: "*/*",
                                           "User-Agent": this.userAgent,
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                       })
                                         .then((res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           this.log.debug(res.request.path);
                                           return res.data;
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                         });
                                       let form = this.extractHidden(loginHtml);
                                       form.email = this.config.username;
                                       loginHtml = await this.requestClient({
                                         method: "post",
                                         url:
                                           "https://account.withings.com/new_workflow/login?r=https://account.withings.com/oauth2_user/account_login?response_type=code&client_id=" +
                                           this.config.clientid +
                                           "&state=h4fhjnc2daoc3m&scope=user.activity%2Cuser.metrics%2Cuser.info&redirect_uri=http%3A%2F%2Flocalhost&b=authorize2",
                                         headers: {
                                           Accept:
                                             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                           "Accept-Language": "de",
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         data: qs.stringify(form),
                                       })
                                         .then(async (res) => {
                                           return res.data;
                                         })
                                         .catch((error) => {
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                           return;
                                         });
                                    
                                       form = this.extractHidden(loginHtml);
                                       form.password = this.config.password;
                                    
                                       const resultArray = await this.requestClient({
                                         method: "post",
                                         url:
                                           "https://account.withings.com/new_workflow/password_check?r=https%3A%2F%2Faccount.withings.com%2Foauth2_user%2Faccount_login%3Fresponse_type%3Dcode%26client_id%3D" +
                                           this.config.clientid +
                                           "%26state%3Dh4fhjnc2daoc3m%26scope%3Duser.activity%252Cuser.metrics%252Cuser.info%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%26b%3Dauthorize2",
                                         headers: {
                                           Accept:
                                             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                           "Accept-Language": "de",
                                           "Content-Type": "application/x-www-form-urlencoded",
                                         },
                                         jar: this.cookieJar,
                                         withCredentials: true,
                                         data: qs.stringify(form),
                                       })
                                         .then(async (res) => {
                                           this.log.debug(JSON.stringify(res.data));
                                           if (res.data.errors) {
                                             this.log.error(JSON.stringify(res.data));
                                             return;
                                           }
                                           if (res.data.indexOf("user_selection") !== -1) {
                                             let urlArray = res.data.split("response_type=code");
                                             urlArray.shift();
                                             urlArray = urlArray.map((url) => {
                                               //eslint-disable-next-line no-useless-escape
                                               return url.split('"')[0].replace(/\&amp;/g, "&");
                                             });
                                             let nameArray = res.data.split("selecteduser=");
                                             nameArray.shift();
                                             nameArray = nameArray = nameArray.map((name) => {
                                               const key = name.split('"')[0];
                                               const value = name.split('group-item">')[1].split("<")[0];
                                               return { id: key, name: value };
                                             });
                                             for (const element of nameArray) {
                                               await this.setObjectNotExistsAsync(element.id, {
                                                 type: "device",
                                                 common: {
                                                   name: element.name,
                                                 },
                                                 native: {},
                                               });
                                             }
                                             const responseArray = [];
                                             for (const url of urlArray) {
                                               await this.requestClient({
                                                 method: "get",
                                                 url: "https://account.withings.com/oauth2_user/account_login?response_type=code" + url,
                                                 headers: {
                                                   Accept:
                                                     "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                                   "Accept-Language": "de",
                                                 },
                                                 jar: this.cookieJar,
                                                 withCredentials: true,
                                               })
                                                 .then((res) => {
                                                   this.log.debug(JSON.stringify(res.data));
                                                   this.log.debug(res.request.path);
                                                   responseArray.push(res);
                                                   return;
                                                 })
                                                 .catch((error) => {
                                                   this.log.error(error);
                                                   if (error.response) {
                                                     this.log.error(JSON.stringify(error.response.data));
                                                   }
                                                   return;
                                                 });
                                             }
                                             return responseArray;
                                           } else {
                                             return [res];
                                           }
                                         })
                                         .catch((error) => {
                                           if (error.response && error.response.status === 302) {
                                             return [];
                                           }
                                           this.log.error(error);
                                           if (error.response) {
                                             this.log.error(JSON.stringify(error.response.data));
                                           }
                                           return [];
                                         });
                                    
                                       for (const result of resultArray) {
                                         if (!result) {
                                           return;
                                         }
                                         form = this.extractHidden(result.data);
                                         form.authorized = "1";
                                         const code = await this.requestClient({
                                           method: "post",
                                           url: "https://account.withings.com" + result.request.path,
                                           headers: {
                                             Accept:
                                               "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                                             "Accept-Language": "de",
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           jar: this.cookieJar,
                                           withCredentials: true,
                                           data: qs.stringify(form),
                                           maxRedirects: 0,
                                         })
                                           .then((res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             this.log.debug(res.request.path);
                                             this.log.debug(res.headers.location);
                                             if (res.headers.location) {
                                               return res.headers.location.split("code=")[1];
                                             }
                                             this.log.warn("Please check username and password");
                                             return;
                                           })
                                           .catch((error) => {
                                             if (error.response && error.response.status === 302) {
                                               this.log.debug(JSON.stringify(error.response.headers));
                                               if (error.response.headers.location.indexOf("code=") === -1) {
                                                 this.log.debug(JSON.stringify(error.response.headers));
                                                 this.log.error("No code found");
                                                 return null;
                                               }
                                               return error.response.headers.location.split("code=")[1].split("&")[0];
                                             }
                                    
                                             this.log.error(error);
                                             if (error.response) {
                                               this.log.error(JSON.stringify(error.response.data));
                                             }
                                           });
                                    
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/oauth2",
                                           headers: {
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           data: qs.stringify({
                                             action: "requesttoken",
                                             grant_type: "authorization_code",
                                             client_id: this.config.clientid,
                                             client_secret: this.config.clientsecret,
                                             code: code,
                                             redirect_uri: "http://localhost",
                                           }),
                                         })
                                           .then(async (res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (res.data.error) {
                                               this.log.error(JSON.stringify(res.data));
                                               return;
                                             }
                                             this.session.push(res.data.body);
                                             await this.setObjectNotExistsAsync(res.data.body.userid, {
                                               type: "device",
                                               common: {
                                                 name: "Hauptnutzer",
                                               },
                                               native: {},
                                             });
                                             this.setState("info.connection", true, true);
                                           })
                                           .catch((error) => {
                                             this.log.error(error);
                                             if (error.response) {
                                               this.log.error(JSON.stringify(error.response.data));
                                             }
                                           });
                                       }
                                     }
                                     async getDeviceList() {
                                       for (const session of this.session) {
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/user?action=getdevice",
                                           headers: {
                                             Authorization: "Bearer " + session.access_token,
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                         })
                                           .then(async (res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (!res.data.body.devices) {
                                               return;
                                             }
                                             for (const device of res.data.body.devices) {
                                               const id = device.deviceid;
                                               if (this.deviceArray.indexOf(id) === -1) {
                                                 this.deviceArray.push(id);
                                               }
                                               const name = device.model;
                                    
                                               await this.setObjectNotExistsAsync(id, {
                                                 type: "device",
                                                 common: {
                                                   name: name,
                                                 },
                                                 native: {},
                                               });
                                               await this.setObjectNotExistsAsync(id + ".remote", {
                                                 type: "channel",
                                                 common: {
                                                   name: "Remote Controls",
                                                 },
                                                 native: {},
                                               });
                                    
                                               const remoteArray = [{ command: "Refresh", name: "True = Refresh" }];
                                               remoteArray.forEach((remote) => {
                                                 this.setObjectNotExists(id + ".remote." + remote.command, {
                                                   type: "state",
                                                   common: {
                                                     name: remote.name || "",
                                                     type: remote.type || "boolean",
                                                     role: remote.role || "boolean",
                                                     write: true,
                                                     read: true,
                                                   },
                                                   native: {},
                                                 });
                                               });
                                               this.json2iob.parse(id, device);
                                             }
                                           })
                                           .catch((error) => {
                                             this.log.error(error);
                                             error.response && this.log.error(JSON.stringify(error.response.data));
                                           });
                                       }
                                     }
                                    
                                     // helper: unix seconds -> ISO string (UTC)
                                     toIso(val) {
                                       if (val === null || val === undefined) return null;
                                       const n = Number(val);
                                       if (Number.isNaN(n)) return null;
                                       return new Date(n * 1000).toLocaleString("de-DE").replace(",", "");
                                     }
                                    
                                     /**
                                      * Schreibe pro Measure-Type den letzten (neuesten) Messwert als State unter userid.lastMeasures.<type>
                                      * measuregrps wird idealerweise sortiert übergeben (neueste zuerst), aber wir schützen uns trotzdem.
                                      */
                                       /**
                                        * Schreibe pro Measure-Type:
                                        *   <type>             = letzter Wert
                                        *   <type>_update      = lokale Zeit (de-DE) ohne Komma
                                        *   <type>_timestamp   = Rohwert der Messung (Unix Sekunden)
                                        */
                                       async writeLastMeasures(userid, measuregrps, descriptions) {
                                       	if (!Array.isArray(measuregrps) || measuregrps.length === 0) return;
                                    
                                       	await this.setObjectNotExistsAsync(`${userid}.lastMeasures`, {
                                       		type: "channel",
                                       		common: { name: "Letzte Messwerte" },
                                       		native: {},
                                       	});
                                    
                                       	const seen = new Set();
                                    
                                       	for (const grp of measuregrps) {
                                       		if (!grp?.measures) continue;
                                    
                                       		const tsRaw = Number(grp.date) || null; // Sekunden
                                       		const tsLocal = tsRaw
                                       			? new Date(tsRaw * 1000).toLocaleString("de-DE").replace(",", "")
                                       			: null;
                                    
                                       		for (const m of grp.measures) {
                                       			const t = m.type;
                                       			if (seen.has(t)) continue;
                                    
                                       			const val = m.value * Math.pow(10, m.unit);
                                    
                                       			const base = `${userid}.lastMeasures.${t}`;
                                       			const upd  = `${userid}.lastMeasures.${t}_update`;
                                       			const raw  = `${userid}.lastMeasures.${t}_timestamp`;
                                    
                                       			// --- Wert ---
                                       			await this.setObjectNotExistsAsync(base, {
                                       				type: "state",
                                       				common: {
                                       					name: descriptions?.[t] || `Measure ${t}`,
                                       					type: "number",
                                       					role: "value",
                                       					read: true,
                                       					write: false,
                                       				},
                                       				native: { type: t, unit: m.unit },
                                       			});
                                       			await this.setStateAsync(base, { val: val, ack: true });
                                    
                                       			// --- Lokale Zeit ---
                                       			await this.setObjectNotExistsAsync(upd, {
                                       				type: "state",
                                       				common: {
                                       					name: `Updatezeit Measure ${t}`,
                                       					type: "string",
                                       					role: "value.datetime",
                                       					read: true,
                                       					write: false,
                                       				},
                                       				native: {},
                                       			});
                                       			await this.setStateAsync(upd, { val: tsLocal, ack: true });
                                    
                                       			// --- Raw Timestamp ---
                                       			await this.setObjectNotExistsAsync(raw, {
                                       				type: "state",
                                       				common: {
                                       					name: `Timestamp Measure ${t}`,
                                       					type: "number",
                                       					role: "value",
                                       					read: true,
                                       					write: false,
                                       				},
                                       				native: {},
                                       			});
                                       			await this.setStateAsync(raw, { val: tsRaw, ack: true });
                                    
                                       			seen.add(t);
                                       		}
                                       	}
                                       }
                                    
                                    
                                     /**
                                      * Schreibe die letzte Aktivität als States unter userid.lastActivity.<key>
                                      * data ist die body-Antwort mit data.activities (Array)
                                      * Konvertiert: modified -> ISO
                                      */
                                     async writeLastActivity(userid, data) {
                                       try {
                                         if (!data || !Array.isArray(data.activities) || data.activities.length === 0) return;
                                    
                                         // Wir haben in updateDevices() data.activities.sort(...) – dort wird sortiert. Hier nehmen wir das erste Element (neueste)
                                         const activity = data.activities[0];
                                         if (!activity) return;
                                    
                                         // Channel anlegen
                                         await this.setObjectNotExistsAsync(userid + ".lastActivity", {
                                           type: "channel",
                                           common: {
                                             name: "Letzte Aktivität",
                                           },
                                           native: {},
                                         });
                                    
                                         // Schreibe alle Felder der Aktivität als einzelne States
                                         for (const key of Object.keys(activity)) {
                                           const rawValue = activity[key];
                                           let storeValue = rawValue;
                                    
                                           // convert modified from unix seconds to ISO
                                           // if (key === "modified") {
                                           //  const iso = this.toIso(rawValue);
                                           //  if (iso !== null) storeValue = iso;
                                           //}
                                    
                                           const stateId = `${userid}.lastActivity.${key}`;
                                    
                                           // Datentyp bestimmen
                                           const common = {
                                             name: `Letzte Aktivität - ${key}`,
                                             type: typeof storeValue === "number" ? "number" : "string",
                                             role: "value",
                                             read: true,
                                             write: false,
                                           };
                                    
                                           await this.setObjectNotExistsAsync(stateId, {
                                             type: "state",
                                             common,
                                             native: {},
                                           });
                                    
                                           // Falls Objekt oder Array, als JSON-String speichern, damit nichts verloren geht
                                           const outValue = typeof storeValue === "object" ? JSON.stringify(storeValue) : storeValue;
                                           await this.setStateAsync(stateId, { val: outValue, ack: true });
                                         }
                                       } catch (e) {
                                         this.log.error("writeLastActivity failed: " + e);
                                       }
                                     }
                                    
                                     /**
                                      * Schreibe die letzte Sleep Summary als States unter userid.lastSleep.<key>
                                      * data ist die body-Antwort, erwartet data.series (Array)
                                      * Konvertiert: created, startdate, enddate, modified -> ISO
                                      */
                                     async writeLastSleepSummary(userid, data) {
                                       try {
                                         if (!data || !Array.isArray(data.series) || data.series.length === 0) return;
                                    
                                         // In updateDevices() haben wir data.series.sort(...) (neueste zuerst), also nehmen wir das erste Element
                                         const series = data.series[0] || data.series[data.series.length - 1];
                                         if (!series) return;
                                    
                                         // Channel anlegen
                                         await this.setObjectNotExistsAsync(userid + ".lastSleep", {
                                           type: "channel",
                                           common: {
                                             name: "Letzte Sleep Summary",
                                           },
                                           native: {},
                                         });
                                    
                                         // Oberflächenwerte (alles außer 'data')
                                         for (const key of Object.keys(series)) {
                                           if (key === "data") continue;
                                           const rawValue = series[key];
                                           let storeValue = rawValue;
                                    
                                         //  if (["created", "startdate", "enddate", "modified"].includes(key)) {
                                         //    const iso = this.toIso(rawValue);
                                         //    if (iso !== null) storeValue = iso;
                                         //  }
                                    
                                           const stateId = `${userid}.lastSleep.${key}`;
                                    
                                           const common = {
                                             name: `Letzte Sleep - ${key}`,
                                             type: typeof storeValue === "number" ? "number" : "string",
                                             role: "value",
                                             read: true,
                                             write: false,
                                           };
                                    
                                           await this.setObjectNotExistsAsync(stateId, {
                                             type: "state",
                                             common,
                                             native: {},
                                           });
                                    
                                           const outValue = typeof storeValue === "object" ? JSON.stringify(storeValue) : storeValue;
                                           await this.setStateAsync(stateId, { val: outValue, ack: true });
                                         }
                                    
                                         // Falls es ein data-Objekt gibt (detaillierte Werte), diese ebenfalls ablegen
                                         if (series.data && typeof series.data === "object") {
                                           for (const key of Object.keys(series.data)) {
                                             const value = series.data[key];
                                             const stateId = `${userid}.lastSleep.${key}`;
                                    
                                             const common = {
                                               name: `Letzte Sleep - ${key}`,
                                               type: typeof value === "number" ? "number" : "string",
                                               role: "value",
                                               read: true,
                                               write: false,
                                             };
                                    
                                             await this.setObjectNotExistsAsync(stateId, {
                                               type: "state",
                                               common,
                                               native: {},
                                             });
                                    
                                             const outValue = typeof value === "object" ? JSON.stringify(value) : value;
                                             await this.setStateAsync(stateId, { val: outValue, ack: true });
                                           }
                                         }
                                       } catch (e) {
                                         this.log.error("writeLastSleepSummary failed: " + e);
                                       }
                                     }
                                    
                                     async updateDevices() {
                                       for (const session of this.session) {
                                         const userid = session.userid;
                                         const date = new Date().toISOString().split("T")[0];
                                         const startTimestampday = new Date().setDate(new Date().getDate() - this.config.lastDays);
                                         const startDateFormattedday = new Date(startTimestampday).toISOString().split("T")[0];
                                         const limitSeconds = this.config.lastDays * 24 * 60 * 60;
                                    
                                         const statusArray = [
                                           {
                                             path: "measures",
                                             url: "https://wbsapi.withings.net/measure",
                                             desc: "Measurements",
                                             data: {
                                               action: "getmeas",
                                               meastypes: "1,4,5,6,8,9,10,11,12,54,71,73,76,77,88,91,123,135,136,137,138,139,170",
                                    
                                               startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                               enddate: Math.round(Date.now() / 1000),
                                             },
                                             forceIndex: false,
                                             preferedArrayName: "type",
                                           },
                                           {
                                             path: "activity",
                                             url: "https://wbsapi.withings.net/v2/measure",
                                             desc: "Activity",
                                             data: {
                                               action: "getactivity",
                                               data_fields:
                                                 "steps,distance,elevation,soft,moderate,intense,active,calories,totalcalories,hr_average,hr_min,hr_max,hr_zone_0,hr_zone_1,hr_zone_2,hr_zone_3",
                                               startdateymd: startDateFormattedday,
                                               enddateymd: date,
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "heartList",
                                             url: "https://wbsapi.withings.net/v2/heart",
                                             desc: "List of ECG recordings",
                                             data: {
                                               action: "list",
                                               startdate: Math.round(Date.now() / 1000) - limitSeconds,
                                               enddate: Math.round(Date.now() / 1000),
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "sleepSummary",
                                             url: "https://wbsapi.withings.net/v2/sleep",
                                             desc: "Basic information about a night",
                                             data: {
                                               action: "getsummary",
                                               startdateymd: startDateFormattedday,
                                               enddateymd: date,
                                               data_fields:
                                                 "breathing_disturbances_intensity,deepsleepduration,durationtosleep,durationtowakeup,hr_average,hr_max,hr_min,lightsleepduration,remsleepduration,rr_average,rr_max,rr_min,sleep_score,snoring,snoringepisodecount,wakeupcount,wakeupduration,nb_rem_episodes,sleep_efficiency,sleep_latency,total_sleep_time,total_timeinbed,wakeup_latency,waso,apnea_hypopnea_index,asleepduration,night_events,out_of_bed_count",
                                             },
                                             forceIndex: true,
                                           },
                                           {
                                             path: "sleep",
                                             url: "https://wbsapi.withings.net/v2/sleep",
                                             desc: "Sleep measures for the night ",
                                             data: {
                                               action: "get",
                                               startdate: Math.round(Date.now() / 1000) - this.config.lastHours * 60 * 60,
                                               enddate: Math.round(Date.now() / 1000),
                                               data_fields: "hr,rr,snoring",
                                             },
                                             forceIndex: true,
                                           },
                                         ];
                                         const headers = {
                                           authorization: "Bearer " + session.access_token,
                                           "user-agent": this.userAgent,
                                         };
                                         for (const element of statusArray) {
                                           await this.requestClient({
                                             method: "post",
                                             url: element.url,
                                             headers: headers,
                                             data: qs.stringify(element.data),
                                           })
                                             .then(async (res) => {
                                               this.log.debug(JSON.stringify(res.data));
                                               if (!res.data) {
                                                 return;
                                               }
                                               const data = res.data.body;
                                               if (Array.isArray(data?.measuregrps)) {
                                                 data.measuregrps.sort((a, b) => {
                                                   const numeric = (obj, key) => {
                                                     const value = obj && obj[key];
                                                     return typeof value === "number" ? value : Number(value) || 0;
                                                   };
                                                   const compareChain = ["date", "created", "modified", "grpid"];
                                                   for (const field of compareChain) {
                                                     const diff = numeric(b, field) - numeric(a, field);
                                                     if (diff !== 0) {
                                                       return diff;
                                                     }
                                                   }
                                                   return 0;
                                                 });
                                               }
                                               if (data.activities) {
                                                 // sort activities by modified timestamp (newest first)
                                                 data.activities.sort((a, b) => (b.modified || 0) - (a.modified || 0));
                                               }
                                               if (element.path === "sleepSummary" || element.path === "sleep") {
                                                 if (data.series && data.series.sort) {
                                                   data.series.sort((a, b) => b.startdate - a.startdate);
                                                 }
                                               }
                                               if (element.path === "sleep" && data.series) {
                                                 data.series.map((element) => {
                                                   for (const key in element) {
                                                     if (typeof element[key] === "object") {
                                                       const newArray = [];
                                                       for (const timestamp in element[key]) {
                                                         newArray.push({ timestamp: timestamp, value: element[key][timestamp] });
                                                       }
                                                       newArray.sort((a, b) => b.timestamp - a.timestamp);
                                                       element[key] = newArray;
                                                     }
                                                   }
                                                 });
                                               }
                                               // if (data.measuregrps) {
                                               //     data.measuregrps.sort((a, b) => a.date - b.date);
                                               // }
                                               const descriptions = {
                                                 1: "Weight (kg)",
                                                 4: "Height (meter)",
                                                 5: "Fat Free Mass (kg)",
                                                 6: "Fat Ratio (%)",
                                                 8: "Fat Mass Weight (kg)",
                                                 9: "Diastolic Blood Pressure (mmHg)",
                                                 10: "Systolic Blood Pressure (mmHg)",
                                                 11: "Heart Pulse (bpm) - only for BPM and scale devices",
                                                 12: "Temperature (celsius)",
                                                 54: "SP02 (%)",
                                                 71: "Body Temperature (celsius)",
                                                 73: "Skin Temperature (celsius)",
                                                 76: "Muscle Mass (kg)",
                                                 77: "Hydration (kg)",
                                                 88: "Bone Mass (kg)",
                                                 91: "Pulse Wave Velocity (m/s)",
                                                 123: "VO2 max is a numerical measurement of your body’s ability to consume oxygen (ml/min/kg).",
                                                 135: "QRS interval duration based on ECG signal",
                                                 136: "PR interval duration based on ECG signal",
                                                 137: "QT interval duration based on ECG signal",
                                                 138: "Corrected QT interval duration based on ECG signal",
                                                 139: "Atrial fibrillation result from PPG",
                                                 170: "Visceral Fat (without unity)",
                                               };
                                    
                                               // === NEU: Schreibe die letzten Messwerte pro Type in userid.lastMeasures.<type>
                                               if (element.path === "measures" && Array.isArray(data?.measuregrps)) {
                                                 try {
                                                   await this.writeLastMeasures(userid, data.measuregrps, descriptions);
                                                 } catch (e) {
                                                   this.log.error("writeLastMeasures failed: " + e);
                                                 }
                                               }
                                               // === NEU: Schreibe die letzte Activity in userid.lastActivity.<key>
                                               if (element.path === "activity" && data) {
                                                 try {
                                                   await this.writeLastActivity(userid, data);
                                                 } catch (e) {
                                                   this.log.error("writeLastActivity failed: " + e);
                                                 }
                                               }
                                               // === NEU: Schreibe die letzte Sleep Summary in userid.lastSleep.<key>
                                               if (element.path === "sleepSummary" && data) {
                                                 try {
                                                   await this.writeLastSleepSummary(userid, data);
                                                 } catch (e) {
                                                   this.log.error("writeLastSleepSummary failed: " + e);
                                                 }
                                               }
                                               // === ENDE NEU
                                    
                                               this.json2iob.parse(userid + "." + element.path, data, {
                                                 forceIndex: element.forceIndex,
                                                 preferedArrayName: element.preferedArrayName,
                                                 channelName: element.desc,
                                                 descriptions: descriptions,
                                               });
                                             })
                                             .catch((error) => {
                                               if (error.response) {
                                                 if (error.response.status === 401) {
                                                   error.response && this.log.debug(JSON.stringify(error.response.data));
                                                   this.log.info(element.path + " receive 401 error. Refresh Token in 60 seconds");
                                                   this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                                   this.refreshTokenTimeout = setTimeout(() => {
                                                     this.refreshToken();
                                                   }, 1000 * 60);
                                    
                                                   return;
                                                 }
                                               }
                                               this.log.error(element.url);
                                               this.log.error(error);
                                               error.response && this.log.error(JSON.stringify(error.response.data));
                                             });
                                         }
                                       }
                                     }
                                     async refreshToken() {
                                       if (this.session.length === 0) {
                                         this.log.error("No session found relogin");
                                         await this.login();
                                         return;
                                       }
                                    
                                       for (const session of this.session) {
                                         await this.requestClient({
                                           method: "post",
                                           url: "https://wbsapi.withings.net/v2/oauth2",
                                           headers: {
                                             "Content-Type": "application/x-www-form-urlencoded",
                                           },
                                           data: qs.stringify({
                                             action: "requesttoken",
                                             grant_type: "refresh_token",
                                             client_id: this.config.clientid,
                                             client_secret: this.config.clientsecret,
                                             refresh_token: session.refresh_token,
                                           }),
                                         })
                                           .then((res) => {
                                             this.log.debug(JSON.stringify(res.data));
                                             if (res.data.body && res.data.body.access_token) {
                                               const index = this.session.indexOf(session);
                                               this.session[index] = res.data.body;
                                               this.setState("info.connection", true, true);
                                             }
                                           })
                                           .catch((error) => {
                                             this.log.error("refresh token failed");
                                             this.log.error(error);
                                             error.response && this.log.error(JSON.stringify(error.response.data));
                                             this.log.error("Start relogin in 1min");
                                             this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                             this.reLoginTimeout = setTimeout(() => {
                                               this.login();
                                             }, 1000 * 60 * 1);
                                           });
                                       }
                                     }
                                     async cleanOldVersion() {
                                       const cleanOldVersion = await this.getObjectAsync("oldVersionCleanedv2");
                                       if (!cleanOldVersion) {
                                         this.log.info("Please wait a few minutes.... clean old version");
                                         await this.delForeignObjectAsync(this.name + "." + this.instance, { recursive: true });
                                         await this.setObjectNotExistsAsync("oldVersionCleanedv2", {
                                           type: "state",
                                           common: {
                                             type: "boolean",
                                             role: "boolean",
                                             write: false,
                                             read: true,
                                           },
                                           native: {},
                                         });
                                    
                                         this.log.info("Done with cleaning, restart adapter");
                                         this.restart();
                                       }
                                     }
                                     extractHidden(body) {
                                       const returnObject = {};
                                       if (!body) {
                                         this.log.warn("No body found");
                                       }
                                       let matches;
                                       if (body.matchAll) {
                                         matches = body.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g);
                                       } else {
                                         this.log.warn(
                                           "The adapter needs in the future NodeJS v12. https://forum.iobroker.net/topic/22867/how-to-node-js-f%C3%BCr-iobroker-richtig-updaten",
                                         );
                                         matches = this.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g, body);
                                       }
                                       for (const match of matches) {
                                         returnObject[match[1]] = match[2];
                                       }
                                       return returnObject;
                                     }
                                     matchAll(re, str) {
                                       let match;
                                       const matches = [];
                                    
                                       while ((match = re.exec(str))) {
                                         // add all matched groups
                                         matches.push(match);
                                       }
                                    
                                       return matches;
                                     }
                                     /**
                                      * Is called when adapter shuts down - callback has to be called under any circumstances!
                                      * @param {() => void} callback
                                      */
                                     onUnload(callback) {
                                       try {
                                         this.setState("info.connection", false, true);
                                         this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
                                         this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
                                         this.updateInterval && clearInterval(this.updateInterval);
                                         this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
                                         callback();
                                       } catch (e) {
                                         this.log.error(e);
                                         callback();
                                       }
                                     }
                                    
                                     /**
                                      * Is called if a subscribed state changes
                                      * @param {string} id
                                      * @param {ioBroker.State | null | undefined} state
                                      */
                                     async onStateChange(id, state) {
                                       if (state) {
                                         if (!state.ack) {
                                           // const deviceId = id.split(".")[2];
                                           const command = id.split(".")[4];
                                           if (id.split(".")[3] !== "remote") {
                                             return;
                                           }
                                    
                                           if (command === "Refresh") {
                                             this.updateDevices();
                                           }
                                         }
                                       }
                                     }
                                    }
                                    
                                    if (require.main !== module) {
                                     // Export the constructor in compact mode
                                     /**
                                      * @param {Partial<utils.AdapterOptions>} [options={}]
                                      */
                                     module.exports = (options) => new Withings(options);
                                    } else {
                                     // otherwise start the instance directly
                                     new Withings();
                                    }
                                    
                                    

                                    PS: es war noch ein Fehler in der Reihenfolge bei lastActivity drin, ich habe die main.js aktualisiert.

                                    PS2: es ist jetzt bei lastMeasures zusätzlich das update Datum mit drin

                                    PS3: jetzt mit deutschem Datumsformat

                                    PS4: jetzt mit update + timestamp

                                    Gruß Tino

                                    1 Antwort Letzte Antwort
                                    0
                                    • A Abwesend
                                      A Abwesend
                                      ammawel
                                      schrieb zuletzt editiert von
                                      #530

                                      @Tino-0
                                      Hallo,
                                      mindestens ebenso spannend wie das Ergebnis finde ich den Weg, eine Korrektur durch ChatGPT machen zu lassen.

                                      Bis vor ein paar Tagen dachte ich, dass nach der Anpassung durch Marc Berg die aktuellen Werte zuverlässig im Ordner measuregrps01 liegen, aber nach hinzufügen der Herzfrequenz zu den Messungen der Body-Smart-Waage fing das Chaos wieder an: aktuelle Herzfrequenz in 01 und aktuelles Gewicht in 02, dann mal beides in 01 u.s.w.. Das Entfernen der Herzfrequenzmessung brachte wieder Ordnung ins Chaos.

                                      Zwei Fragen zu Deiner Lösung:

                                      • Basiert Deine Modifikation auf der bereits geänderten main.js vom github-Verzeichnis des ursprünglichen Ersteller TA2K (https://github.com/TA2k/ioBroker.withings)? In dieser Version wurde die Sortierung von Marc Berg ja bereits eingepflegt.
                                      • Wirst Du auch ein pull-request erstellen, sodass Deine Änderungen eingepflegt werden können und hoffentlich eine stabil funktionierende Version des Adapters entsteht?

                                      Danke für Deine Mühen,
                                      vG, Achim

                                      1 Antwort Letzte Antwort
                                      0
                                      • A Abwesend
                                        A Abwesend
                                        ammawel
                                        schrieb zuletzt editiert von
                                        #531

                                        @Tino-0
                                        Problem: Was nimmt man als Trigger? Eine Aktualisierung der Werte in lastMeasures tritt bei jedem refresh des eingestellten Updateintervalls auf, eine Änderung der Werte scheidet auch aus...
                                        Bisher habe ich auf eine Änderung von date in measuregrp01 getriggert - so 100%ig verlässlich ist das aber auch nicht mehr nach den Erfahrungen mit der Herzfrequenz (s.o.).

                                        T 1 Antwort Letzte Antwort
                                        0
                                        • A ammawel

                                          @Tino-0
                                          Problem: Was nimmt man als Trigger? Eine Aktualisierung der Werte in lastMeasures tritt bei jedem refresh des eingestellten Updateintervalls auf, eine Änderung der Werte scheidet auch aus...
                                          Bisher habe ich auf eine Änderung von date in measuregrp01 getriggert - so 100%ig verlässlich ist das aber auch nicht mehr nach den Erfahrungen mit der Herzfrequenz (s.o.).

                                          T Online
                                          T Online
                                          Tino 0
                                          schrieb zuletzt editiert von
                                          #532

                                          @ammawel

                                          Hallo,

                                          ChatGPT hat es so implementiert, wobei bei lastActivity noch ein Fehler drin war und es ist oben eine neue Version drin.

                                          Zitat von ChatGPT
                                          Fazit: Woher kommt der „letzte Wert“? – Kurzfassung

                                          1. lastMeasures
                                            Die Withings-API liefert measuregrps ungeordnet.
                                            Dein Adapter sortiert sie zuerst nach Zeit absteigend (date → created → modified).
                                            Danach ist Index 0 immer die neueste Messung.
                                            writeLastMeasures() geht von oben nach unten durch und nimmt pro Mess-Typ nur den ersten Treffer.
                                            → Ergebnis: Der neueste Wert pro Typ.

                                          2. lastActivity
                                            Der Adapter sortiert activities nach modified absteigend.
                                            Danach ist activities[0] die neueste Aktivität.
                                            writeLastActivity() schreibt genau diesen Eintrag.
                                            → Ergebnis: Immer die aktuellste Aktivität.

                                          3. lastSleep / lastSleepSummary
                                            Sleep-Daten (series) werden nach startdate oder modified absteigend sortiert.
                                            Dadurch ist series[0] die neueste Nacht.
                                            writeLastSleepSummary() nimmt genau diesen Eintrag.
                                            → Ergebnis: Immer die aktuellste Sleep-Summary.

                                          Ultrakurz:
                                          Sortierung entscheidet, nicht Withings.
                                          Alles wird neueste zuerst sortiert.
                                          Der Adapter nimmt immer das erste Element [0] nach der Sortierung.
                                          Deshalb bekommst du überall den wirklich letzten Wert.

                                          Zitat Ende:

                                          Wenn morgen das Sleep durchläuft, werde morgen mal den pull-request erstellen.

                                          Gruß Tino

                                          1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          654

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe