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. Adapter Hyundai (Bluelink) oder KIA (UVO)

NEWS

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

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

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

Adapter Hyundai (Bluelink) oder KIA (UVO)

Geplant Angeheftet Gesperrt Verschoben Tester
2.3k Beiträge 149 Kommentatoren 872.0k Aufrufe 139 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.
  • Meister MopperM Meister Mopper

    @arteck

    Ich habe die ganze Prozedur noch einmal durchgeführt und einen neuen refresh token geholt.

    Siehe da, die Verbindung steht. Vielen Dank 😊 !

    Meister MopperM Abwesend
    Meister MopperM Abwesend
    Meister Mopper
    schrieb am zuletzt editiert von
    #2181

    @arteck
    Mittlerweile werden ja alle states in bluelink.0.xxxx.vehicleStatusRaw abgebildet. Ich dachte bisher, dass dort alles an verfügbaren Daten drin ist.

    Ich bin gerade dabei, meine vis anzupassen und stelle fest, dass für mich wichtige Daten (z. B. SoC von Hauptbatterie und 12V-Batterie) fehlen.

    Ist dies das Ergebnis der andauernden Umstellung bei Hyundai/Kia und Warten ist das Gebot der Stunde?

    Proxmox und HA

    K S arteckA 3 Antworten Letzte Antwort
    0
    • Meister MopperM Meister Mopper

      @arteck
      Mittlerweile werden ja alle states in bluelink.0.xxxx.vehicleStatusRaw abgebildet. Ich dachte bisher, dass dort alles an verfügbaren Daten drin ist.

      Ich bin gerade dabei, meine vis anzupassen und stelle fest, dass für mich wichtige Daten (z. B. SoC von Hauptbatterie und 12V-Batterie) fehlen.

      Ist dies das Ergebnis der andauernden Umstellung bei Hyundai/Kia und Warten ist das Gebot der Stunde?

      K Offline
      K Offline
      Kumpelstilzchen
      schrieb am zuletzt editiert von
      #2182

      @meister-mopper
      Ich habe einen eNiro und dort finden sich die Daten, die du suchst nach der Neuordnung der Datenstruktur durch Kia unter bluelink.0.xxxxxx.vehicleStatusRaw.engine . Die Datenpunkte sind batteryChargeHV und batteryCharge12v.

      1 Antwort Letzte Antwort
      0
      • Meister MopperM Meister Mopper

        @arteck
        Mittlerweile werden ja alle states in bluelink.0.xxxx.vehicleStatusRaw abgebildet. Ich dachte bisher, dass dort alles an verfügbaren Daten drin ist.

        Ich bin gerade dabei, meine vis anzupassen und stelle fest, dass für mich wichtige Daten (z. B. SoC von Hauptbatterie und 12V-Batterie) fehlen.

        Ist dies das Ergebnis der andauernden Umstellung bei Hyundai/Kia und Warten ist das Gebot der Stunde?

        S Online
        S Online
        schmuh
        schrieb am zuletzt editiert von
        #2183

        @meister-mopper

        Ich denke, beim EV3 sollten das diese DP sein:
        HV-Batterie SoC
        bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Green.BatteryManagement.BatteryRemain.Ratio

        12V Batterie
        bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Electronics.Battery.Level
        Schau mal, ob das passt.

        Beste Grüße

        Meister MopperM 1 Antwort Letzte Antwort
        1
        • Meister MopperM Meister Mopper

          @arteck
          Mittlerweile werden ja alle states in bluelink.0.xxxx.vehicleStatusRaw abgebildet. Ich dachte bisher, dass dort alles an verfügbaren Daten drin ist.

          Ich bin gerade dabei, meine vis anzupassen und stelle fest, dass für mich wichtige Daten (z. B. SoC von Hauptbatterie und 12V-Batterie) fehlen.

          Ist dies das Ergebnis der andauernden Umstellung bei Hyundai/Kia und Warten ist das Gebot der Stunde?

          arteckA Offline
          arteckA Offline
          arteck
          Developer Most Active
          schrieb am zuletzt editiert von
          #2184

          @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

          Ich dachte bisher, dass dort alles an verfügbaren Daten drin ist.

          eigentlich ja.. sollte.. zumindest nach meiner letzten Optimierung

          zigbee hab ich, zwave auch, nuc's genauso und HA auch

          1 Antwort Letzte Antwort
          0
          • S schmuh

            @meister-mopper

            Ich denke, beim EV3 sollten das diese DP sein:
            HV-Batterie SoC
            bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Green.BatteryManagement.BatteryRemain.Ratio

            12V Batterie
            bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Electronics.Battery.Level
            Schau mal, ob das passt.

            Beste Grüße

            Meister MopperM Abwesend
            Meister MopperM Abwesend
            Meister Mopper
            schrieb am zuletzt editiert von
            #2185

            @schmuh sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

            @meister-mopper

            Ich denke, beim EV3 sollten das diese DP sein:
            HV-Batterie SoC
            bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Green.BatteryManagement.BatteryRemain.Ratio

            12V Batterie
            bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Electronics.Battery.Level
            Schau mal, ob das passt.

            Beste Grüße

            Ja super, vielen Dank. Das hatte ich im Rentnerkopp nicht verknüpft 😉 .

            Proxmox und HA

            Meister MopperM 1 Antwort Letzte Antwort
            1
            • Meister MopperM Meister Mopper

              @schmuh sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

              @meister-mopper

              Ich denke, beim EV3 sollten das diese DP sein:
              HV-Batterie SoC
              bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Green.BatteryManagement.BatteryRemain.Ratio

              12V Batterie
              bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Electronics.Battery.Level
              Schau mal, ob das passt.

              Beste Grüße

              Ja super, vielen Dank. Das hatte ich im Rentnerkopp nicht verknüpft 😉 .

              Meister MopperM Abwesend
              Meister MopperM Abwesend
              Meister Mopper
              schrieb am zuletzt editiert von
              #2186

              @schmuh

              1. Hast du einen Tipp, wo ich die Gesamtreichweite finde?

              2. In den States der stable version des Adapters gab es: bluelink.0.KNACC81GFN5121414.vehicleStatus.battery.plugin.
                Dort konnte man überwachen, ob das Kfz zum Laden angeschlossen wurde (1=fast charging, 2=slow charging, 3=fast charging)
                Gibt es ein Pendant unter vehicleStatusRaw?

              Proxmox und HA

              arteckA S 2 Antworten Letzte Antwort
              0
              • Meister MopperM Meister Mopper

                @schmuh

                1. Hast du einen Tipp, wo ich die Gesamtreichweite finde?

                2. In den States der stable version des Adapters gab es: bluelink.0.KNACC81GFN5121414.vehicleStatus.battery.plugin.
                  Dort konnte man überwachen, ob das Kfz zum Laden angeschlossen wurde (1=fast charging, 2=slow charging, 3=fast charging)
                  Gibt es ein Pendant unter vehicleStatusRaw?

                arteckA Offline
                arteckA Offline
                arteck
                Developer Most Active
                schrieb am zuletzt editiert von
                #2187

                @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                Gibt es ein Pendant unter vehicleStatusRaw?

                ja den gibt es besimmt...schaumal nach die DP's sind kategoriesiert.. zur not klap den kompletten Baum auf.

                zigbee hab ich, zwave auch, nuc's genauso und HA auch

                Meister MopperM 1 Antwort Letzte Antwort
                1
                • arteckA arteck

                  @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                  Gibt es ein Pendant unter vehicleStatusRaw?

                  ja den gibt es besimmt...schaumal nach die DP's sind kategoriesiert.. zur not klap den kompletten Baum auf.

                  Meister MopperM Abwesend
                  Meister MopperM Abwesend
                  Meister Mopper
                  schrieb am zuletzt editiert von
                  #2188

                  @arteck

                  Reichweite habe ich schon gefunden 😀 , den anderen suche ich, wenn das Gefährt geladen wird.
                  Danke!

                  Proxmox und HA

                  ilovegymI 1 Antwort Letzte Antwort
                  0
                  • Meister MopperM Meister Mopper

                    @schmuh

                    1. Hast du einen Tipp, wo ich die Gesamtreichweite finde?

                    2. In den States der stable version des Adapters gab es: bluelink.0.KNACC81GFN5121414.vehicleStatus.battery.plugin.
                      Dort konnte man überwachen, ob das Kfz zum Laden angeschlossen wurde (1=fast charging, 2=slow charging, 3=fast charging)
                      Gibt es ein Pendant unter vehicleStatusRaw?

                    S Online
                    S Online
                    schmuh
                    schrieb am zuletzt editiert von schmuh
                    #2189

                    @meister-mopper

                    @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                    1. In den States der stable version des Adapters gab es: bluelink.0.KNACC81GFN5121414.vehicleStatus.battery.plugin.
                      Dort konnte man überwachen, ob das Kfz zum Laden angeschlossen wurde (1=fast charging, 2=slow charging, 3=fast charging)
                      Gibt es ein Pendant unter vehicleStatusRaw?

                    Den habe ich leider auch noch nicht gefunden 😬

                    Edit:
                    Vielleicht der, bin mir aber nicht sicher? bluelink.0.xxxxxxxxxxxxxxxxx.vehicleStatusRaw.Green.ChargingInformation.ConnectorFastening.State

                    1 Antwort Letzte Antwort
                    1
                    • Meister MopperM Meister Mopper

                      @arteck

                      Reichweite habe ich schon gefunden 😀 , den anderen suche ich, wenn das Gefährt geladen wird.
                      Danke!

                      ilovegymI Online
                      ilovegymI Online
                      ilovegym
                      schrieb am zuletzt editiert von ilovegym
                      #2190

                      @meister-mopper @schmuh

                      schaut mal unter bluelink.0.KMHKRxxxxxxxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.ChargingInformation.ConnectorFastening.State

                      kann auch sein, dass das einfach nur der Stecker connected ist.. war gestern nur AC laden an der Wallbox (da war er auf 1, sonst 0, DC muesste dann 2 sein)

                      S Meister MopperM 2 Antworten Letzte Antwort
                      0
                      • ilovegymI ilovegym

                        @meister-mopper @schmuh

                        schaut mal unter bluelink.0.KMHKRxxxxxxxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.ChargingInformation.ConnectorFastening.State

                        kann auch sein, dass das einfach nur der Stecker connected ist.. war gestern nur AC laden an der Wallbox (da war er auf 1, sonst 0, DC muesste dann 2 sein)

                        S Online
                        S Online
                        schmuh
                        schrieb am zuletzt editiert von schmuh
                        #2191

                        @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                        ar gestern nur AC laden an der Wallbox (da war er auf 1

                        Ja, im Moment ist er am AC laden und er hat den Wert 1, kann ihn aber jetzt nicht abstecken.
                        Edit:
                        bluelink.0.xxxxxxxxxxx.vehicleStatusRaw.Green.Electric.SmartGrid.RealTimePower scheint die aktuelle Ladestromstärke in kW zu sein.

                        ilovegymI 1 Antwort Letzte Antwort
                        2
                        • S schmuh

                          @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                          ar gestern nur AC laden an der Wallbox (da war er auf 1

                          Ja, im Moment ist er am AC laden und er hat den Wert 1, kann ihn aber jetzt nicht abstecken.
                          Edit:
                          bluelink.0.xxxxxxxxxxx.vehicleStatusRaw.Green.Electric.SmartGrid.RealTimePower scheint die aktuelle Ladestromstärke in kW zu sein.

                          ilovegymI Online
                          ilovegymI Online
                          ilovegym
                          schrieb am zuletzt editiert von
                          #2192

                          @schmuh

                          ich hatte in den letzten 4 Wochen bestimmt 20x DC geladen von unterwegs aus, bin viel gefahren, hatte daher aber leider keine Zeit, darauf zu achten, dass er auch bluelink abfragt, teilweise sogar bei einer ionity nichmal LTE gehabt.. zum Glueck ging die Ladekarte..

                          Waere cool, wenn man seine Ladevorgaenge von ionity/enbw in den iobroker bringen koennte.. aber die haben keine Api (nur fuer die Flottenkarten) .. da musste erstmal mit n token rausbekommen..

                          Alternativ koennte man sich n script schreiben, das den charging-state abfragt, dazu den standort analysiert, schaut, ob es ionity/enbw in der naehe gibt, und dann die geladene kw entsprechend in einen state schreibt.. hmm mal wieder was fuer n Onkel GPT 🙂

                          @schmuh super(dank history), ja, schau, ist halt immer knapp, der braucht ja nur 15min von 15-80% bei >200kw
                          Screenshot 2025-09-29 at 11.38.05.png

                          1 Antwort Letzte Antwort
                          1
                          • ilovegymI ilovegym

                            @meister-mopper @schmuh

                            schaut mal unter bluelink.0.KMHKRxxxxxxxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.ChargingInformation.ConnectorFastening.State

                            kann auch sein, dass das einfach nur der Stecker connected ist.. war gestern nur AC laden an der Wallbox (da war er auf 1, sonst 0, DC muesste dann 2 sein)

                            Meister MopperM Abwesend
                            Meister MopperM Abwesend
                            Meister Mopper
                            schrieb am zuletzt editiert von
                            #2193

                            @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                            @meister-mopper @schmuh

                            schaut mal unter bluelink.0.KMHKRxxxxxxxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.ChargingInformation.ConnectorFastening.State

                            kann auch sein, dass das einfach nur der Stecker connected ist.. war gestern nur AC laden an der Wallbox (da war er auf 1, sonst 0, DC muesste dann 2 sein)

                            Guude Bernd,

                            ich hab da nur bluelink.0.blabla.vehicleStatusRaw.Green.ChargingInformation.ConnectorFastening.State
                            gefunden.

                            Offenbar haben wir verschiedene Adapter-Versionen, aber von der Logik sieht das gut aus.

                            Irgendwie ist das mit dem vehicelStatusRaw ein lustiges Ratespiel. Wenn Hyundai/Kia irgendwann mal fertig ist, könnte ich ja @arteck bei der Doku behilflich sein (Zeit habe ich ja jetzt genug 😉 ).

                            Proxmox und HA

                            1 Antwort Letzte Antwort
                            1
                            • ilovegymI Online
                              ilovegymI Online
                              ilovegym
                              schrieb am zuletzt editiert von
                              #2194

                              @meister-mopper

                              Ja haben wir, ich „sitz“ auf der 3.1.6 mit meinem Ioniq5N , die funktioniert, neuere haben ein Login Problem..

                              Meister MopperM 1 Antwort Letzte Antwort
                              1
                              • ilovegymI ilovegym

                                @meister-mopper

                                Ja haben wir, ich „sitz“ auf der 3.1.6 mit meinem Ioniq5N , die funktioniert, neuere haben ein Login Problem..

                                Meister MopperM Abwesend
                                Meister MopperM Abwesend
                                Meister Mopper
                                schrieb am zuletzt editiert von
                                #2195

                                @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                Ioniq5N

                                Das Kfz ist ja Teufelszeug, damit könnte ich meine Lady nie fahren lassen, die würde allen BMW und Audi Losern den Beschleunigungskrieg erklären.

                                Proxmox und HA

                                ilovegymI 1 Antwort Letzte Antwort
                                1
                                • Meister MopperM Meister Mopper

                                  @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                  Ioniq5N

                                  Das Kfz ist ja Teufelszeug, damit könnte ich meine Lady nie fahren lassen, die würde allen BMW und Audi Losern den Beschleunigungskrieg erklären.

                                  ilovegymI Online
                                  ilovegymI Online
                                  ilovegym
                                  schrieb am zuletzt editiert von
                                  #2196

                                  @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                  @ilovegym sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                  Ioniq5N

                                  Das Kfz ist ja Teufelszeug, damit könnte ich meine Lady nie fahren lassen, die würde allen BMW und Audi Losern den Beschleunigungskrieg erklären.

                                  … und das macht sowas von Spaß… 😆😂🙈😇😇😇 wäre bei meiner aber auch, nur wenn se nebendran sitzt, darf ich die 200 nicht überschreiten.. 🙈😅

                                  1 Antwort Letzte Antwort
                                  0
                                  • GarganoG Offline
                                    GarganoG Offline
                                    Gargano
                                    schrieb am zuletzt editiert von
                                    #2197

                                    @arteck Der Workaround hat jetzt 5 Tage funktioniert und jetzt wieder der Fehler mit Login. Hat Kia wieder etwas verändert ?

                                    arteckA 1 Antwort Letzte Antwort
                                    0
                                    • GarganoG Gargano

                                      @arteck Der Workaround hat jetzt 5 Tage funktioniert und jetzt wieder der Fehler mit Login. Hat Kia wieder etwas verändert ?

                                      arteckA Offline
                                      arteckA Offline
                                      arteck
                                      Developer Most Active
                                      schrieb am zuletzt editiert von arteck
                                      #2198

                                      @gargano sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                      Hat Kia wieder etwas verändert ?

                                      woher soll ich das wissen ??

                                      geht es den mit neuen Token ?

                                      zigbee hab ich, zwave auch, nuc's genauso und HA auch

                                      ilovegymI 1 Antwort Letzte Antwort
                                      1
                                      • GarganoG Offline
                                        GarganoG Offline
                                        Gargano
                                        schrieb am zuletzt editiert von
                                        #2199

                                        @arteck sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                        geht es den mit neuen Token

                                        Weder mit dem bisherigen Token, noch mit einem neu generierten.
                                        Ich wollte auch nicht nerven, sondern nur Bescheid geben.

                                        Meister MopperM 1 Antwort Letzte Antwort
                                        0
                                        • arteckA arteck

                                          @gargano sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                                          Hat Kia wieder etwas verändert ?

                                          woher soll ich das wissen ??

                                          geht es den mit neuen Token ?

                                          ilovegymI Online
                                          ilovegymI Online
                                          ilovegym
                                          schrieb am zuletzt editiert von
                                          #2200

                                          @arteck @Meister-Mopper und alle anderen.. 🙂

                                          Habe hier das oben von mir gerade erfundene Script, das den naechsten HPC und Ladeleistung etc raussucht, ausrechnet und in einem Dashboard darstellt, mal gemacht. Laeuft hier unter javascript 8.9.2 ohne irgendwelche Plugins ohne warnings/errors, auch die States werden quiet angelegt ( outHPCRoot und outEnergyRoot und das Dashboard kann am Anfang vom Script konfiguriert werden. Ebenso die States vom bluelink-Adapter, koennen auch aehnlich sein, je nach Fahrzeugtyp.)
                                          Zur Abfrage von OpenchargeMap sollte ein kostenloser Api-Key eingetragen sein, link im Script.

                                          Das Dashboard stelle ich mit MinuVis (Widget HTML auf den State) dar. Geht auch garantiert in allen anderen Vis-Varianten.

                                          Hier das Script:

                                          /**************************************************************
                                          * IONIQ 5 / Bluelink – HPC Nearby + Energy Integration + Dashboard
                                          * ioBroker JavaScript-Adapter >= 8.9.2 (Node 18+)
                                          * Version: 1.3.0 (selftest map centering, energy tiles, vis path)
                                          * (c) by ilovegym66
                                          **************************************************************/
                                          'use strict';
                                          
                                          const https = require('https');
                                          
                                          /*** ===== KONFIG ===== ***/
                                          const CFG = {
                                           bluelink: {
                                             power:    'bluelink.0.KMHKRxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.Electric.SmartGrid.RealTimePower',
                                             charging: 'bluelink.0.KMHKRxxxx.vehicleStatus.battery.charge',
                                             soc:      'bluelink.0.KMHKRxxxx.vehicleStatus.battery.soc',
                                             lat:      'bluelink.0.KMHKRxxxx.vehicleLocation.lat',
                                             lon:      'bluelink.0.KMHKRxxxx.vehicleLocation.lon',
                                             latAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.latitude',
                                             lonAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.longitude'
                                           },
                                          
                                           hpc: {
                                             thresholdKW: 0,        // Suche auch ohne hohe Ladeleistung erlauben
                                             hpcMinKW:    149,      // Client-Filter Mindestleistung (kaskadiert runter falls 0 Treffer)
                                             radiusKm:    20,
                                             maxResults:  200,
                                             coordMinMoveM: 30,
                                             minCheckIntervalSec: 30,
                                             preferredOperators: ['ionity','enbw','aral pulse','fastned','shell recharge','allego','mer','ewe go','totalenergies','eviny','maingau','entega','pfalzwerke','tesla'],
                                             ocmEndpoint: 'https://api.openchargemap.io/v3/poi/',
                                             ocmApiKey:   'DEIN-OCM-KEY-HIER', // <— eintragen oder per State lesen (siehe unten)
                                             requireChargingForSearch: false,
                                          
                                             // Serverseitige Filter (erste Stufe „strict“)
                                             apiFilterFastDC: true,     // Level 3 + DC
                                             apiMinPowerKW:   120,
                                          
                                             // Kaskade bei 0 Treffern
                                             cascadeIfZero: true,
                                             cascadeMinKWClient: 120
                                           },
                                          
                                           energy: {
                                             powerIsWattAuto:    true,
                                             sessionPowerMinKW:  0.5,
                                             integTickSec:       10,
                                             sessionIdleEndSec:  180,
                                             usableCapacityKWh:  84
                                           },
                                          
                                           // Dashboard-Ausgabe (zurück in vis-Pfad)
                                           dash: {
                                             htmlState: '0_userdata.0.vis.Dashboards.HPC.HTML', // <- hier landet das fertige HTML
                                             allowScroll: false,    // MinuVis: kein Scroll/Wheel
                                             heightPx: 420          // Kartenhöhe
                                           },
                                          
                                           // Ausgabe-Roots (States)
                                           outHpcRoot:    '0_userdata.0.Cars.HPCNearby',
                                           outEnergyRoot: '0_userdata.0.Cars.Energy',
                                          
                                           debug: true
                                          };
                                          
                                          // OPTIONAL: OCM-Key aus State laden (wenn gewünscht)
                                          // try { const s=getState('0_userdata.0.secrets.ocmApiKey'); if (s && s.val) CFG.hpc.ocmApiKey = String(s.val); } catch(e){}
                                          
                                          /*** ===== Utils ===== ***/
                                          const idJ = (...a)=>a.join('.');
                                          const logI = m => log(`[Ioniq-HPC+Energy] ${m}`, 'info');
                                          const logD = m => CFG.debug && log(`[Ioniq-HPC+Energy] ${m}`, 'debug');
                                          const nowSec = ()=>Math.floor(Date.now()/1000);
                                          
                                          function exObj(id){ try{ return existsObject(id); }catch(e){ return false; } }
                                          function exState(id){ try{ return existsState(id); }catch(e){ return false; } }
                                          function g(id){
                                           try{
                                             if (!exState(id)) return undefined;
                                             const s = getState(id);
                                             return s ? s.val : undefined;
                                           }catch(e){ return undefined; }
                                          }
                                          async function es(id, common, init){ try{ if (!exObj(id)) await createStateAsync(id, common, init ?? null); }catch(e){} }
                                          async function ss(id, val){ try{ await setStateAsync(id, {val, ack:true}); }catch(e){} }
                                          function toNum(x, d=0){ const n = Number(x); return Number.isFinite(n) ? n : d; }
                                          function toBool(x){ return x===true || x===1 || x==='1' || String(x).toLowerCase()==='true'; }
                                          
                                          /*** ===== Haversine (m) ===== ***/
                                          function haversineMeters(lat1, lon1, lat2, lon2){
                                           const R=6371000, rad=d=>d*Math.PI/180;
                                           const dLat=rad(lat2-lat1), dLon=rad(lon2-lon1);
                                           const a=Math.sin(dLat/2)**2+Math.cos(rad(lat1))*Math.cos(rad(lat2))*Math.sin(dLon/2)**2;
                                           return 2*R*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
                                          }
                                          
                                          /*** ====== OUTPUT STATES: HPC ====== ***/
                                          const HPC = {
                                           ROOT:     CFG.outHpcRoot,
                                           HAS:      idJ(CFG.outHpcRoot,'hasHPCNearby'),
                                           COUNT:    idJ(CFG.outHpcRoot,'count'),
                                           NAME:     idJ(CFG.outHpcRoot,'nearest.name'),
                                           DISTM:    idJ(CFG.outHpcRoot,'nearest.distance_m'),
                                           KW:       idJ(CFG.outHpcRoot,'nearest.maxPower_kW'),
                                           OP:       idJ(CFG.outHpcRoot,'nearest.operator'),
                                           LASTJSON: idJ(CFG.outHpcRoot,'lastResultJson'),
                                           LASTCHK:  idJ(CFG.outHpcRoot,'lastCheck'),
                                           LASTWHY:  idJ(CFG.outHpcRoot,'lastReason'),
                                           LASTERR:  idJ(CFG.outHpcRoot,'lastError'),
                                           MISSING:  idJ(CFG.outHpcRoot,'debug.missingStates'),
                                          
                                           // Debug
                                           DBG_URL:   idJ(CFG.outHpcRoot,'debug.lastQueryUrl'),
                                           DBG_URL2:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_swapped'),
                                           DBG_URL3:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_wide'),
                                           DBG_RAW:   idJ(CFG.outHpcRoot,'debug.rawCount'),
                                           DBG_FIL:   idJ(CFG.outHpcRoot,'debug.filteredCount'),
                                           DBG_SAMPLE:idJ(CFG.outHpcRoot,'debug.sampleJson'),
                                           DBG_COORD_LAT: idJ(CFG.outHpcRoot,'debug.lastLat'),
                                           DBG_COORD_LON: idJ(CFG.outHpcRoot,'debug.lastLon'),
                                           DBG_HTTP:      idJ(CFG.outHpcRoot,'debug.lastHttpStatus'),
                                           DBG_ERRSHORT:  idJ(CFG.outHpcRoot,'debug.lastErrorShort'),
                                          
                                           // Commands
                                           CMD_TEST:      idJ(CFG.outHpcRoot,'cmd.TestSearch'),
                                           TEST_RADIUS:   idJ(CFG.outHpcRoot,'cmd.TestRadiusKm'),
                                           CMD_TEST_FFM:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Frankfurt'),
                                           CMD_TEST_CGN:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Koeln'),
                                           CMD_TEST_MUC:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Muenchen')
                                          };
                                          
                                          async function ensureHpcStates(){
                                           await es(HPC.HAS,{type:'boolean',role:'indicator'},false);
                                           await es(HPC.COUNT,{type:'number',role:'value'},0);
                                           await es(HPC.NAME,{type:'string',role:'text'},'');
                                           await es(HPC.DISTM,{type:'number',role:'value'},0);
                                           await es(HPC.KW,{type:'number',role:'value'},0);
                                           await es(HPC.OP,{type:'string',role:'text'},'');
                                           await es(HPC.LASTJSON,{type:'string',role:'json'},'[]');
                                           await es(HPC.LASTCHK,{type:'string',role:'text'},'');
                                           await es(HPC.LASTWHY,{type:'string',role:'text'},'');
                                           await es(HPC.LASTERR,{type:'string',role:'text'},'');
                                           await es(HPC.MISSING,{type:'string',role:'text'},'');
                                          
                                           await es(HPC.DBG_URL,{type:'string',role:'text'},'');
                                           await es(HPC.DBG_URL2,{type:'string',role:'text'},'');
                                           await es(HPC.DBG_URL3,{type:'string',role:'text'},'');
                                           await es(HPC.DBG_RAW,{type:'number',role:'value'},0);
                                           await es(HPC.DBG_FIL,{type:'number',role:'value'},0);
                                           await es(HPC.DBG_SAMPLE,{type:'string',role:'json'},'');
                                           await es(HPC.DBG_COORD_LAT,{type:'number',role:'value.gps'},0);
                                           await es(HPC.DBG_COORD_LON,{type:'number',role:'value.gps'},0);
                                           await es(HPC.DBG_HTTP,{type:'string',role:'text'},'');
                                           await es(HPC.DBG_ERRSHORT,{type:'string',role:'text'},'');
                                          
                                           await es(HPC.CMD_TEST,{type:'boolean',role:'button'},false);
                                           await es(HPC.TEST_RADIUS,{type:'number',role:'value'},NaN);
                                           await es(HPC.CMD_TEST_FFM,{type:'boolean',role:'button'},false);
                                           await es(HPC.CMD_TEST_CGN,{type:'boolean',role:'button'},false);
                                           await es(HPC.CMD_TEST_MUC,{type:'boolean',role:'button'},false);
                                          }
                                          
                                          /*** ====== OUTPUT STATES: ENERGY ====== ***/
                                          const EN = {
                                           ROOT:          CFG.outEnergyRoot,
                                           ACTIVE:        idJ(CFG.outEnergyRoot,'session.active'),
                                           START_TS:      idJ(CFG.outEnergyRoot,'session.startTs'),
                                           END_TS:        idJ(CFG.outEnergyRoot,'session.endTs'),
                                           START_SOC:     idJ(CFG.outEnergyRoot,'session.startSoC'),
                                           END_SOC:       idJ(CFG.outEnergyRoot,'session.endSoC'),
                                           ENERGY_KWH:    idJ(CFG.outEnergyRoot,'session.energy_kWh'),
                                           ENERGY_SOC_KWH:idJ(CFG.outEnergyRoot,'session.energySoc_kWh'),
                                           LAST_ENERGY:   idJ(CFG.outEnergyRoot,'lastSession.energy_kWh'),
                                           LAST_START:    idJ(CFG.outEnergyRoot,'lastSession.startTs'),
                                           LAST_END:      idJ(CFG.outEnergyRoot,'lastSession.endTs'),
                                           TODAY_KWH:     idJ(CFG.outEnergyRoot,'today.energy_kWh'),
                                           TOTAL_KWH:     idJ(CFG.outEnergyRoot,'total.energy_kWh'),
                                           LAST_REASON:   idJ(CFG.outEnergyRoot,'debug.lastReason'),
                                           LAST_ERR:      idJ(CFG.outEnergyRoot,'debug.lastError')
                                          };
                                          async function ensureEnergyStates(){
                                           await es(EN.ACTIVE,        {type:'boolean', role:'indicator'}, false);
                                           await es(EN.START_TS,      {type:'string',  role:'text'}, '');
                                           await es(EN.END_TS,        {type:'string',  role:'text'}, '');
                                           await es(EN.START_SOC,     {type:'number',  role:'value'}, null);
                                           await es(EN.END_SOC,       {type:'number',  role:'value'}, null);
                                           await es(EN.ENERGY_KWH,    {type:'number',  role:'value.energy'}, 0);
                                           await es(EN.ENERGY_SOC_KWH,{type:'number',  role:'value.energy'}, 0);
                                           await es(EN.LAST_ENERGY,   {type:'number',  role:'value.energy'}, 0);
                                           await es(EN.LAST_START,    {type:'string',  role:'text'}, '');
                                           await es(EN.LAST_END,      {type:'string',  role:'text'}, '');
                                           await es(EN.TODAY_KWH,     {type:'number',  role:'value.energy'}, 0);
                                           await es(EN.TOTAL_KWH,     {type:'number',  role:'value.energy'}, 0);
                                           await es(EN.LAST_REASON,   {type:'string',  role:'text'}, '');
                                           await es(EN.LAST_ERR,      {type:'string',  role:'text'}, '');
                                          }
                                          
                                          /*** ===== OCM Helper ===== ***/
                                          function isPreferredOperator(op){
                                           if (!op) return false;
                                           const t = String(op).toLowerCase();
                                           return CFG.hpc.preferredOperators.some(x => t.includes(String(x).toLowerCase()));
                                          }
                                          function powerFromConn(c){
                                           if (!c) return 0;
                                           let pk = toNum(c.PowerKW, NaN);
                                           if (!Number.isFinite(pk)) pk = toNum(c.RatedPowerKW, NaN);
                                           if (!Number.isFinite(pk)) pk = toNum(c.Power, NaN);
                                           if (Number.isFinite(pk) && pk > 0) return pk;
                                          
                                           let v = toNum(c.Voltage, NaN); if (!Number.isFinite(v)) v = toNum(c.Volts, NaN);
                                           let a = toNum(c.Amps, NaN);    if (!Number.isFinite(a)) a = toNum(c.Current, NaN);
                                           if (Number.isFinite(v) && Number.isFinite(a) && v>0 && a>0) return (v*a)/1000;
                                          
                                           const levelId  = toNum(c.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
                                           const currentId= toNum(c.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
                                           const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
                                           const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
                                           const lvlFast  = levelId >= 3 || lvlTitle.includes('3');
                                           const isDC     = currentId === 30 || curTitle.includes('dc');
                                           if (lvlFast && isDC) return 150; // Fallback
                                           return 0;
                                          }
                                          function extractMaxPowerKW(poi){
                                           const arr = Array.isArray(poi?.Connections) ? poi.Connections : [];
                                           let max = 0;
                                           for (const c of arr){ const p = powerFromConn(c); if (p > max) max = p; }
                                           return max;
                                          }
                                          
                                          /*** HTTP ***/
                                          function buildOcmUrlStr(lat, lon, radiusKm, maxResults, opts){
                                           opts = opts || {}; // { fastDC?:boolean, minPowerKW?:number }
                                           function add(q, k, v){ if (v===undefined||v===null||v==='') return q; q.push(encodeURIComponent(k)+'='+encodeURIComponent(String(v))); return q; }
                                           const base = String(CFG.hpc.ocmEndpoint||'https://api.openchargemap.io/v3/poi/').replace(/\?+.*/, '');
                                           const params = [];
                                           add(params,'output','json');
                                           add(params,'latitude', lat);
                                           add(params,'longitude', lon);
                                           add(params,'distance', radiusKm);
                                           add(params,'distanceunit','KM');
                                           add(params,'maxresults', maxResults || CFG.hpc.maxResults);
                                           add(params,'compact','false'); add(params,'verbose','true');
                                           if (opts.fastDC){ add(params,'levelid',3); add(params,'currenttypeid',30); if (opts.minPowerKW!=null) add(params,'minpowerkw', opts.minPowerKW); }
                                           if (CFG.hpc.ocmApiKey && CFG.hpc.ocmApiKey.trim()){ add(params,'key', CFG.hpc.ocmApiKey.trim()); }
                                           return base + '?' + params.join('&');
                                          }
                                          function httpGetJson(urlStr){
                                           return new Promise((resolve, reject) => {
                                             const headers = { 'User-Agent':'ioBroker-HPC-Nearby/1.3 (+contact:local)' };
                                             // keinen X-API-Key Header verwenden – Key steckt in URL
                                             const req = https.get(urlStr, { headers, timeout: 12000 }, (res) => {
                                               let data=''; res.on('data', c=>data+=c);
                                               res.on('end', ()=>{ if (res.statusCode>=200 && res.statusCode<300) { try{ resolve({json:JSON.parse(data), status:res.statusCode}); } catch(e){ reject(new Error('OCM JSON parse error: '+e.message)); } } else reject(new Error('OCM HTTP '+res.statusCode)); });
                                             });
                                             req.on('timeout', ()=>req.destroy(new Error('timeout')));
                                             req.on('error', reject);
                                           });
                                          }
                                          async function fetchOCM(lat, lon, radiusKm, maxResults, mode){
                                           let url;
                                           if (mode==='strict') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true,minPowerKW:CFG.hpc.apiMinPowerKW});
                                           else if (mode==='dcOnly') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true});
                                           else url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{});
                                           const r = await httpGetJson(url);
                                           return { mode, url, status:r.status, json:r.json };
                                          }
                                          
                                          /*** ===== HPC intern ===== ***/
                                          let lastCheckSec = 0, lastLat = null, lastLon = null;
                                          function readLatLon(){
                                           let lat = g(CFG.bluelink.lat), lon = g(CFG.bluelink.lon);
                                           if ((lat===undefined||lat===null) && exState(CFG.bluelink.latAlt)) lat = g(CFG.bluelink.latAlt);
                                           if ((lon===undefined||lon===null) && exState(CFG.bluelink.lonAlt)) lon = g(CFG.bluelink.lonAlt);
                                           return {lat: toNum(lat, 0), lon: toNum(lon, 0)};
                                          }
                                          function listMissing(ids){ return ids.filter(id => !exState(id)); }
                                          
                                          async function hpcNo(reason){
                                           await ss(HPC.HAS,false); await ss(HPC.COUNT,0);
                                           await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
                                           await ss(HPC.LASTJSON,'[]'); await ss(HPC.LASTCHK,new Date().toISOString());
                                           await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY,reason||'');
                                           await ss(HPC.DBG_RAW,0); await ss(HPC.DBG_FIL,0);
                                           await ss(HPC.DBG_URL,''); await ss(HPC.DBG_URL2,''); await ss(HPC.DBG_URL3,''); await ss(HPC.DBG_HTTP,''); await ss(HPC.DBG_ERRSHORT,'');
                                          }
                                          
                                          function enrichAndFilter(json, lat, lon){
                                           const enriched=[];
                                           for (const poi of Array.isArray(json)?json:[]){
                                             const pMax = extractMaxPowerKW(poi);
                                             const conns = Array.isArray(poi?.Connections)?poi.Connections:[];
                                             const isDCfast = conns.some(c=>{
                                               const lvl = toNum(c?.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
                                               const cur = toNum(c?.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
                                               const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
                                               const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
                                               const lvlFast = lvl >= 3 || lvlTitle.includes('3'); const isDC = cur === 30 || curTitle.includes('dc');
                                               return lvlFast && isDC;
                                             });
                                             if (!(pMax >= CFG.hpc.hpcMinKW || (pMax === 0 && isDCfast))) continue;
                                          
                                             const op   = poi?.OperatorInfo?.Title || '';
                                             const name = poi?.AddressInfo?.Title || '';
                                             const plat = toNum(poi?.AddressInfo?.Latitude, NaN);
                                             const plon = toNum(poi?.AddressInfo?.Longitude, NaN);
                                             const distM = (Number.isFinite(plat)&&Number.isFinite(plon))?Math.round(haversineMeters(lat,lon,plat,plon)):null;
                                             enriched.push({
                                               id: poi?.ID, operator: op, preferred: isPreferredOperator(op), name,
                                               maxPower_kW: Math.round(pMax), distance_m: distM,
                                               lat: plat, lon: plon
                                             });
                                           }
                                           enriched.sort((a,b)=>{
                                             if (a.preferred!==b.preferred) return a.preferred?-1:1;
                                             const da=a.distance_m??9e9, db=b.distance_m??9e9;
                                             if (da!==db) return da-db;
                                             return (b.maxPower_kW||0)-(a.maxPower_kW||0);
                                           });
                                           return enriched;
                                          }
                                          
                                          async function maybeCheckHPC(reason, opts){
                                           opts = opts||{};
                                           await ss(HPC.LASTWHY, reason||'');
                                           await ss(HPC.DBG_ERRSHORT, '');
                                           try{
                                             const reqIds=[CFG.bluelink.power, CFG.bluelink.charging];
                                             const missing=listMissing(reqIds); if(missing.length){ await ss(HPC.MISSING,missing.join(', ')); return; } else await ss(HPC.MISSING,'');
                                          
                                             let powerKW = toNum(g(CFG.bluelink.power), 0);
                                             if (CFG.energy.powerIsWattAuto && Math.abs(powerKW)>1000) powerKW/=1000; powerKW=Math.abs(powerKW);
                                             const charging = toBool(g(CFG.bluelink.charging));
                                             const {lat,lon}=readLatLon();
                                             await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
                                          
                                             if (!opts.force){
                                               if (CFG.hpc.requireChargingForSearch && !charging) return hpcNo('charging=false');
                                               if (!Number.isFinite(powerKW) || powerKW <= CFG.hpc.thresholdKW) return hpcNo(`power ${powerKW.toFixed(1)} <= ${CFG.hpc.thresholdKW}`);
                                             }
                                             if (!Number.isFinite(lat)||!Number.isFinite(lon)||lat===0||lon===0) return hpcNo('invalid coords');
                                          
                                             const tNow=nowSec(), since=tNow-lastCheckSec;
                                             if (!opts.force && lastLat!=null && lastLon!=null){
                                               const dist=haversineMeters(lastLat,lastLon,lat,lon);
                                               if (dist<CFG.hpc.coordMinMoveM && since<CFG.hpc.minCheckIntervalSec){ logD(`HPC skip: moved ${Math.round(dist)}m, since ${since}s`); return; }
                                             }
                                          
                                             // Stufe 1: strict
                                             let q = await fetchOCM(lat,lon,(opts.radiusKmOverride&&isFinite(opts.radiusKmOverride))?Number(opts.radiusKmOverride):CFG.hpc.radiusKm, CFG.hpc.maxResults, 'strict');
                                             lastCheckSec=tNow; lastLat=lat; lastLon=lon;
                                             await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `strict:${q.status}`);
                                             await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
                                          
                                             let enriched = enrichAndFilter(q.json, lat, lon);
                                             await ss(HPC.DBG_FIL, enriched.length);
                                          
                                             // Stufe 2: dcOnly
                                             if (CFG.hpc.cascadeIfZero && enriched.length===0){
                                               q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
                                               await ss(HPC.DBG_URL2, q.url); await ss(HPC.DBG_HTTP, `dcOnly:${q.status}`);
                                               await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
                                               enriched = enrichAndFilter(q.json, lat, lon);
                                               await ss(HPC.DBG_FIL, enriched.length);
                                             }
                                             // Stufe 3: all (client ≥ 120 kW)
                                             if (CFG.hpc.cascadeIfZero && enriched.length===0){
                                               const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
                                               q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
                                               await ss(HPC.DBG_URL3, q.url); await ss(HPC.DBG_HTTP, `all:${q.status}`);
                                               await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
                                               enriched = enrichAndFilter(q.json, lat, lon);
                                               await ss(HPC.DBG_FIL, enriched.length);
                                               CFG.hpc.hpcMinKW=old;
                                             }
                                          
                                             await ss(HPC.COUNT, enriched.length);
                                             await ss(HPC.HAS, enriched.length>0);
                                             await ss(HPC.LASTJSON, JSON.stringify(enriched));
                                             await ss(HPC.LASTCHK, new Date().toISOString());
                                             await ss(HPC.LASTERR, '');
                                          
                                             if (enriched.length){
                                               const n=enriched[0];
                                               await ss(HPC.NAME, n.name||'');
                                               await ss(HPC.DISTM, n.distance_m||0);
                                               await ss(HPC.KW, n.maxPower_kW||0);
                                               await ss(HPC.OP, n.operator||'');
                                               logI(`HPC nearby: ${n.name} (${n.operator||'–'}), ${n.maxPower_kW} kW, ~${n.distance_m} m`);
                                             } else {
                                               await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
                                               logD('HPC: none in radius');
                                             }
                                          
                                             // Dashboard neu rendern
                                             renderDashboard();
                                          
                                           } catch(err){
                                             await ss(HPC.LASTERR, String(err?.message||err));
                                             await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
                                             await ss(HPC.LASTCHK, new Date().toISOString());
                                             renderDashboard();
                                           }
                                          }
                                          
                                          /*** ===== ENERGY intern ===== ***/
                                          let lastTickTs = Date.now(); let idleSince = null;
                                          function readPowerKW(){ let p=toNum(g(CFG.bluelink.power),0); if (CFG.energy.powerIsWattAuto && Math.abs(p)>1000) p/=1000; return Math.abs(p); }
                                          function readSocPct(){ const s=g(CFG.bluelink.soc); return s===undefined?null:toNum(s,null); }
                                          function readChargingActive(){ return toBool(g(CFG.bluelink.charging)); }
                                          
                                          async function sessionStart(){
                                           await ss(EN.ACTIVE,true); await ss(EN.START_TS,new Date().toISOString());
                                           await ss(EN.END_TS,''); await ss(EN.START_SOC, readSocPct());
                                           await ss(EN.END_SOC,null); await ss(EN.ENERGY_KWH,0); await ss(EN.ENERGY_SOC_KWH,0);
                                           renderDashboard();
                                          }
                                          async function sessionFinish(reason){
                                           const nowIso=new Date().toISOString(); const energy=toNum(g(EN.ENERGY_KWH),0);
                                           await ss(EN.LAST_ENERGY,energy); await ss(EN.LAST_START, g(EN.START_TS)||''); await ss(EN.LAST_END,nowIso);
                                           await ss(EN.END_TS,nowIso); await ss(EN.ACTIVE,false); await ss(EN.LAST_REASON,`finish:${reason||''}`);
                                          
                                           const today=new Date().toISOString().slice(0,10); const keyToday=idJ(EN.ROOT,'daily',today);
                                           if (!exObj(keyToday)) await es(keyToday,{type:'number',role:'value.energy'},0);
                                           const curDay=toNum(g(keyToday),0)+energy;
                                           await ss(keyToday,curDay); await ss(EN.TODAY_KWH,curDay);
                                           await ss(EN.TOTAL_KWH, toNum(g(EN.TOTAL_KWH),0)+energy);
                                           renderDashboard();
                                          }
                                          async function updateSocEstimate(){
                                           if (CFG.energy.usableCapacityKWh<=0) return;
                                           const s0=g(EN.START_SOC), s1=readSocPct(); if (s0==null || s1==null) return;
                                           const est=((toNum(s1,0)-toNum(s0,0))/100)*CFG.energy.usableCapacityKWh;
                                           await ss(EN.END_SOC,s1); await ss(EN.ENERGY_SOC_KWH, Math.max(0,est));
                                          }
                                          async function integrationTick(){
                                           try{
                                             const need=[CFG.bluelink.power, CFG.bluelink.charging]; if (listMissing(need).length) return;
                                             const active=toBool(g(EN.ACTIVE)), charging=readChargingActive(), powerKW=readPowerKW();
                                             const now=Date.now(); const dt_h=Math.max(0,(now-lastTickTs)/3600000); lastTickTs=now;
                                          
                                             if (!active && charging && powerKW>=CFG.energy.sessionPowerMinKW){ idleSince=null; await sessionStart(); }
                                             if (active){
                                               const eAdd=powerKW*dt_h; const eNow=Math.max(0, toNum(g(EN.ENERGY_KWH),0)+eAdd);
                                               await ss(EN.ENERGY_KWH, eNow); await updateSocEstimate();
                                               if (powerKW < CFG.energy.sessionPowerMinKW || !charging){
                                                 if (idleSince===null) idleSince=now; const idleSec=(now-idleSince)/1000;
                                                 if (idleSec>=CFG.energy.sessionIdleEndSec){ await sessionFinish(!charging?'charging=false':'power<min'); idleSince=null; }
                                               } else idleSince=null;
                                             }
                                             const todayKey=idJ(EN.ROOT,'daily', new Date().toISOString().slice(0,10));
                                             if (exObj(todayKey)) await ss(EN.TODAY_KWH, toNum(g(todayKey),0));
                                           } catch(err){ await ss(EN.LAST_ERR, String(err?.message||err)); }
                                           renderDashboard();
                                          }
                                          
                                          /*** ===== Dashboard ===== ***/
                                          async function ensureDashState(){ await es(CFG.dash.htmlState, {type:'string', role:'html'}, ''); }
                                          
                                          // Mini Map-Engine (OSM Tiles im Iframe; kein Scroll/Wheel)
                                          function buildMapIframeHTML(points, centerHint){
                                           // points: [{lat,lon,label,type:'car'|'hpc'}]
                                           // centerHint: {lat,lon} optional
                                           const W=100, H=CFG.dash.heightPx; // Breite 100% via CSS
                                           const payload = { points: points||[], centerHint: centerHint||null, lockScroll: !CFG.dash.allowScroll };
                                           const css =
                                             'html,body{margin:0;height:100%;background:#0b1020}#root{position:relative;width:100%;height:100%}'+
                                             '.tile{position:absolute}canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}'+
                                             '.mk{position:absolute;transform:translate(-50%,-100%);padding:3px 6px;border-radius:8px;border:1px solid rgba(148,163,184,.5);background:rgba(13,23,45,.9);color:#e5e7eb;font:12px system-ui,sans-serif;white-space:nowrap}'+
                                             '.dot{position:absolute;width:10px;height:10px;border-radius:50%;box-shadow:0 0 0 2px rgba(96,165,250,.35)}'+
                                             '.dot.car{background:#34d399} .dot.hpc{background:#60a5fa} .attr{position:absolute;right:6px;bottom:4px;font:11px system-ui;color:#93a3b8;background:rgba(0,0,0,.35);padding:2px 6px;border-radius:6px}';
                                           const js =
                                             '(function(){var DATA=window.__PAYLOAD__||{points:[],centerHint:null,lockScroll:true};'+
                                             'var root=document.getElementById("root");var W=root.clientWidth,H=root.clientHeight;'+
                                             'function rad(d){return d*Math.PI/180;} function lat2y(lat){var s=Math.sin(rad(lat));return 0.5-Math.log((1+s)/(1-s))/(4*Math.PI);} function lon2x(lon){return lon/360+0.5;}'+
                                             'function project(lat,lon,z){var s=256*Math.pow(2,z);return {x:lon2x(lon)*s,y:lat2y(lat)*s};} function deproject(x,y,z){var s=256*Math.pow(2,z);var lon=(x/s-0.5)*360;var n=Math.PI-2*Math.PI*(y/s-0.5);var lat=180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)));return {lat:lat,lon:lon};}'+
                                             'function fitBounds(pts){ if(!pts||!pts.length){ return {c:(DATA.centerHint||{lat:51,lon:10}), z:8}; } var minLat=90,maxLat=-90,minLon=180,maxLon=-180; pts.forEach(function(p){if(p.lat<minLat)minLat=p.lat;if(p.lat>maxLat)maxLat=p.lat;if(p.lon<minLon)minLon=p.lon;if(p.lon>maxLon)maxLon=p.lon;}); var c={lat:(minLat+maxLat)/2,lon:(minLon+maxLon)/2}; var z=6; for(var zz=6;zz<=19;zz++){var a=project(maxLat,minLon,zz), b=project(minLat,maxLon,zz); var w=Math.abs(b.x-a.x), h=Math.abs(b.y-a.y); if(w<=W*0.85 && h<=H*0.85) z=zz; else break;} return {c:c,z:z}; }'+
                                             'var pts=DATA.points||[]; var f=fitBounds(pts.length?pts:(DATA.centerHint?[DATA.centerHint]:[])); var center=f.c, zoom=f.z;'+
                                             'var world=project(center.lat,center.lon,zoom); var originX=world.x-W/2, originY=world.y-H/2;'+
                                             'var tiles=document.createElement("div"); tiles.style.position="absolute"; root.appendChild(tiles); var ctx=document.createElement("canvas"); ctx.width=W; ctx.height=H; root.appendChild(ctx); var g=ctx.getContext("2d");'+
                                             'function wrapX(x,n){return ((x%n)+n)%n;} function toXY(lat,lon){var p=project(lat,lon,zoom); return {x:p.x-originX,y:p.y-originY};}'+
                                             'function draw(){ tiles.innerHTML=""; var n=Math.pow(2,zoom); var x0=Math.floor(originX/256), y0=Math.floor(originY/256); var x1=Math.floor((originX+W)/256), y1=Math.floor((originY+H)/256); for(var ty=y0;ty<=y1;ty++){ if(ty<0||ty>=n) continue; for(var tx=x0;tx<=x1;tx++){ var wx=wrapX(tx,n); var img=new Image(); img.className="tile"; img.src="https://tile.openstreetmap.org/"+zoom+"/"+wx+"/"+ty+".png"; img.width=256; img.height=256; img.style.left=(tx*256-originX)+"px"; img.style.top=(ty*256-originY)+"px"; tiles.appendChild(img);} } g.clearRect(0,0,W,H);'+
                                             '  (DATA.points||[]).forEach(function(p){ var q=toXY(p.lat,p.lon); var d=document.createElement("div"); d.className="dot "+(p.type||"hpc"); d.style.left=q.x+"px"; d.style.top=q.y+"px"; root.appendChild(d); var m=document.createElement("div"); m.className="mk"; m.style.left=q.x+"px"; m.style.top=(q.y-12)+"px"; m.textContent=p.label||""; root.appendChild(m); });'+
                                             '  var attr=document.createElement("div"); attr.className="attr"; attr.textContent="© OpenStreetMap-Mitwirkende"; root.appendChild(attr);'+
                                             '} draw();'+
                                             (CFG.dash.allowScroll ? '' : ' root.addEventListener("wheel", function(ev){ev.preventDefault();}, {passive:false}); ') +
                                             '})();';
                                           const srcdoc = '<!doctype html><html><head><meta charset="utf-8"><style>'+css+'</style></head><body><div id="root" style="width:100%;height:100%"></div><script>window.__PAYLOAD__='+JSON.stringify(payload)+'<\/script><script>'+js+'<\/script></body></html>';
                                           return '<iframe style="width:100%;height:'+H+'px;border:0;border-radius:12px;overflow:hidden;background:#0b1020" srcdoc="'+
                                                  String(srcdoc).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')+'"></iframe>';
                                          }
                                          
                                          function fmtEnergy(n){ return (n==null||isNaN(n)) ? '0.0' : Number(n).toFixed(2); }
                                          function fmtDist(m){ if (m==null||isNaN(m)) return '-'; return (m>=1000)?(m/1000).toFixed(2)+' km': Math.round(m)+' m'; }
                                          
                                          // Render: liest aktuelle States + erzeugt HTML in CFG.dash.htmlState
                                          async function renderDashboard(centerOverride){
                                           try{
                                             await ensureDashState();
                                          
                                             const car = readLatLon();
                                             const nearestName = String(g(HPC.NAME)||'–');
                                             const nearestOp   = String(g(HPC.OP)||'');
                                             const nearestKW   = toNum(g(HPC.KW),0);
                                             const nearestDist = toNum(g(HPC.DISTM),0);
                                             const count       = toNum(g(HPC.COUNT),0);
                                          
                                             const sessActive  = toBool(g(EN.ACTIVE));
                                             const eSess       = fmtEnergy(toNum(g(EN.ENERGY_KWH),0));
                                             const eSoc        = fmtEnergy(toNum(g(EN.ENERGY_SOC_KWH),0));
                                             const eToday      = fmtEnergy(toNum(g(EN.TODAY_KWH),0));
                                             const eTotal      = fmtEnergy(toNum(g(EN.TOTAL_KWH),0));
                                          
                                             // Punkte für Map: Auto + bis zu 12 HPC
                                             let list=[]; try{ list = JSON.parse(String(g(HPC.LASTJSON)||'[]')); }catch(e){ list=[]; }
                                             const top = (Array.isArray(list)?list:[]).slice(0,12);
                                             const points = [];
                                             if (Number.isFinite(car.lat) && Number.isFinite(car.lon) && car.lat && car.lon){
                                               points.push({lat:car.lat, lon:car.lon, label:'Car', type:'car'});
                                             }
                                             for (const x of top){
                                               if (!Number.isFinite(x.lat)||!Number.isFinite(x.lon)) continue;
                                               const lbl = (x.name||'HPC') + ' · ' + (x.maxPower_kW||'?') + ' kW';
                                               points.push({lat:x.lat, lon:x.lon, label:lbl, type:'hpc'});
                                             }
                                          
                                             // SelfTest-Fix: falls centerOverride gesetzt -> nehmen. Sonst fitBounds macht’s automatisch auf Car+HPC
                                             const map = buildMapIframeHTML(points, centerOverride || null);
                                          
                                             const css =
                                               '.wrap{color:#e5e7eb;font:14px system-ui,-apple-system,Segoe UI,Roboto;line-height:1.4;background:#0b1020;padding:10px;border-radius:14px;border:1px solid #334155}'+
                                               '.row{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 10px}'+
                                               '.chip{background:#111827;border:1px solid #374151;border-radius:999px;padding:6px 10px;font-weight:700}'+
                                               '.muted{opacity:.8;font-weight:600} .title{font-weight:800;font-size:14px}'+
                                               '.list{margin-top:10px;border-top:1px solid #334155;padding-top:6px} .item{padding:6px 0;border-bottom:1px dashed #334155}'+
                                               '.item:last-child{border-bottom:0} .badge{font-weight:700} .ok{color:#34d399} .warn{color:#f59e0b}';
                                          
                                             const listHtml = top.map(o=>{
                                               const pref = o.preferred ? ' • ★' : '';
                                               return `<div class="item">• <span class="title">${o.name||'-'}</span> <span class="muted">(${o.operator||'–'}${pref})</span><br/>
                                                 <span class="muted">${o.maxPower_kW||'?'} kW · ${fmtDist(o.distance_m)}</span></div>`;
                                             }).join('');
                                          
                                             const headChips =
                                               `<div class="row">
                                                 <span class="chip">Nearest: <span class="badge">${nearestName}</span> <span class="muted">(${nearestOp||'–'})</span></span>
                                                 <span class="chip">Power: ${nearestKW||0} kW</span>
                                                 <span class="chip">Distance: ${fmtDist(nearestDist)}</span>
                                                 <span class="chip">Spots: ${count}</span>
                                                 <span class="chip">Session: ${eSess} kWh</span>
                                                 <span class="chip">Today: ${eToday} kWh</span>
                                                 <span class="chip">Total: ${eTotal} kWh</span>
                                                 <span class="chip">Car Pos: ${Number(car.lat||0).toFixed(5)}, ${Number(car.lon||0).toFixed(5)}</span>
                                                 <span class="chip">Active: <span class="${sessActive?'ok':'warn'}">${sessActive?'yes':'no'}</span></span>
                                               </div>`;
                                          
                                             const html = `<div class="wrap">${headChips}${map}<div class="list">${listHtml||'<div class="muted">Keine Stationen.</div>'}</div></div>`;
                                             await ss(CFG.dash.htmlState, `<style>${css}</style>` + html);
                                           } catch(e){ /* noop */ }
                                          }
                                          
                                          /*** ===== Subscriptions / Scheduler ===== ***/
                                          function attachTriggers(){
                                           // HPC triggers
                                           on({id: CFG.bluelink.power,    change:'ne'}, ()=> maybeCheckHPC('power_changed'));
                                           on({id: CFG.bluelink.charging, change:'ne'}, ()=> maybeCheckHPC('charging_changed'));
                                           on({id: CFG.bluelink.lat,      change:'ne'}, ()=> maybeCheckHPC('lat_changed'));
                                           on({id: CFG.bluelink.lon,      change:'ne'}, ()=> maybeCheckHPC('lon_changed'));
                                           on({id: CFG.bluelink.latAlt,   change:'ne'}, ()=> maybeCheckHPC('lat_changed_alt'));
                                           on({id: CFG.bluelink.lonAlt,   change:'ne'}, ()=> maybeCheckHPC('lon_changed_alt'));
                                          
                                           // Energy ticker
                                           schedule(`*/${CFG.energy.integTickSec} * * * * *`, integrationTick);
                                          
                                           // Periodische HPC-Abfrage
                                           schedule('*/5 * * * *', ()=> maybeCheckHPC('scheduled'));
                                          
                                           // Test-Button
                                           on({id: HPC.CMD_TEST, change:'ne'}, async (s)=>{
                                             if (s && s.state && s.state.val===true){
                                               await ss(HPC.CMD_TEST,false);
                                               const rOverride = toNum(g(HPC.TEST_RADIUS), NaN);
                                               await maybeCheckHPC('manual_test', { force:true, radiusKmOverride: isFinite(rOverride)?rOverride:undefined });
                                             }
                                           });
                                          
                                           // SelfTests – **fixes map centering**: wir übergeben centerOverride
                                           function selfTest(lat, lon, label){
                                             return async (s)=>{
                                               if (s && s.state && s.state.val===true){
                                                 await ss(s.id,false);
                                                 // Debug-Anzeige der Test-Geo
                                                 await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
                                                 // Direkte OCM-Abfrage (wie maybeCheckHPC, aber ohne Rate-Limit & mit Center-Override)
                                                 try{
                                                   let q = await fetchOCM(lat,lon,Math.max(20,CFG.hpc.radiusKm),Math.max(80,CFG.hpc.maxResults),'strict');
                                                   await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `self:${q.status}`); await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
                                                   let enriched = enrichAndFilter(q.json, lat, lon);
                                                   if (enriched.length===0){
                                                     q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
                                                     enriched = enrichAndFilter(q.json, lat, lon);
                                                   }
                                                   if (enriched.length===0){
                                                     const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
                                                     q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
                                                     enriched = enrichAndFilter(q.json, lat, lon);
                                                     CFG.hpc.hpcMinKW=old;
                                                   }
                                                   await ss(HPC.COUNT, enriched.length);
                                                   await ss(HPC.HAS, enriched.length>0);
                                                   await ss(HPC.LASTJSON, JSON.stringify(enriched));
                                                   if (enriched.length){
                                                     const n=enriched[0];
                                                     await ss(HPC.NAME, n.name||''); await ss(HPC.DISTM, n.distance_m||0);
                                                     await ss(HPC.KW, n.maxPower_kW||0); await ss(HPC.OP, n.operator||'');
                                                   } else {
                                                     await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
                                                   }
                                                   await ss(HPC.LASTCHK, new Date().toISOString()); await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY, 'selftest_'+label);
                                                   // **WICHTIG**: Karte explizit um die Test-Geo zentrieren
                                                   renderDashboard({lat:lat, lon:lon});
                                                 } catch(err){
                                                   await ss(HPC.LASTERR, String(err?.message||err));
                                                   await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
                                                   renderDashboard({lat:lat, lon:lon});
                                                 }
                                               }
                                             };
                                           }
                                           on({id:HPC.CMD_TEST_FFM,change:'ne'}, selfTest(50.1109, 8.6821, 'FFM'));
                                           on({id:HPC.CMD_TEST_CGN,change:'ne'}, selfTest(50.9375, 6.9603, 'CGN'));
                                           on({id:HPC.CMD_TEST_MUC,change:'ne'}, selfTest(48.1372,11.5756, 'MUC'));
                                          }
                                          
                                          /*** ===== Bootstrap ===== ***/
                                          async function init(){
                                           await ensureHpcStates();
                                           await ensureEnergyStates();
                                           await ensureDashState();
                                           // kleiner Delay, dann initial laden + rendern
                                           setTimeout(()=>{ maybeCheckHPC('startup'); integrationTick(); }, 1200);
                                           attachTriggers();
                                           logI('Init: ready');
                                          }
                                          init();
                                          
                                          

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


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          591

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                          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