Jak wykreślić w HA wykres funkcji obliczającej się w Node-RED?

Przede mną nie lada wyzwanie - na karcie w HA chcę wykreślić wartości funkcji Node-RED obliczającej biorytm.

W Node-RED jeden z nodów typu function wylicza mi wartości tej funkcji z parametrów typu data i zwraca 30-elementową tablicę jej wartości tab [0,…,29].:

tab[i] = Math.sin ( 2 * pi * (Math.round((new Date(dzien).getTime() - new Date(urodziny).getTime()) / doba) - 1) / 23)
# i - licznik pętli; dzien - kolejna data z zadanego przedziału czasu

Muszę teraz tę funkcję jakoś wykreślić na którejś z kart w HA (np. custom:mini-graph-card). Nie mam pojęcia w jaki sposób wartości tej funkcji wystawić na kartę nie mając żadnej dedykowanej encji, jedynie tablicę wynikową z funkcji w NodeRED…

Parametr wejściowy do funkcji jest jeden: data urodzin. Funkcja oblicza swoją wartość dla każdej daty w przedziale od (dziś-15dni) do (dziś+15dni), dlatego tablica wynikowa ma 30 elementów. Nawiasem mówiąc mam aż trzy takie tablice, osobne dla każdego z 3-ech cykli (P, E, I).
Pierwotnie cały algorytm obliczeniowy chciałem umieścić w templatce definiującej dedykowany sensor, ale niestety Jinja2 jest bardzo uboga w zaawansowane funkcje matematyczne (np. brak funkcji trygonometrycznych czy funkcji przeliczania dat), więc ten pomysł się nie sprawdził.
Stąd próba (całkiem udana) zapisu algorytmu w nodzie funkcyjnym NodeRED. Ale trzeba to teraz jakoś wydobyć z tablicy wynikowej i wywalić na ekran w postaci wykresu…
Pytanie do ekspertów w tej dziedzinie: Czy macie jakiś pomysł jak to zrobić?

może tak spróbuj po MQTT powinieneś otrzymać wartość z 1 tablicy, ale jak utkać całą tablicę w jeden sensor nie mam pojęcia, choć próbowałem się trochę pobawić, ani tablica ani string nie wchodzi.


    name: Biorytm
    state_topic: "topic/biorytm"  # Zmień na właściwy temat
    value_template: "{{ value_json[0] }}"

Inny wykres: Circadian HA

Karta HA:

Screenshot - 18.08.2023 , 00_52_36

Kod:

type: custom:mini-graph-card
name: Circadian NR
show:
  icon: false
  labels: false
font_size: 80
font_size_header: 10
hour24: true
hours_to_show: 24
points_per_hour: 60
line_width: 2
lower_bound: 1
upper_bound: 100
smoothing: true
entities:
  - entity: sensor.nr_circadian_gain1_2
    name: Brightness NR
  - entity: sensor.nr_circadian_warmth1_2
    name: Warmth NR
    show_state: true
    y_axis: secondary

Mógłbyś podesłać także flow.json? Chciałbym się temu przyjrzeć, bo nie bardzo qumam jak to działa.
No i jak to się dzieje, że na Twoim ekranie obok schematu pojawia się okienko do grafiki? Nigdy z tego nie korzystałem, nawet nie byłem świadom takich możliwości NR…

[{"id":"6b19158c.9268ac","type":"subflow","name":"Sub Circadian NR","info":"","category":"","in":[{"x":60,"y":160,"wires":[{"id":"a103c146.6c32f"}]}],"out":[{"x":1120,"y":140,"wires":[{"id":"b1a9d7fc.ee4c98","port":0}]},{"x":1120,"y":340,"wires":[{"id":"b3ac8cc8.a4c22","port":0}]},{"x":500,"y":500,"wires":[{"id":"66290782.60fc78","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"6c971105.ef196","type":"switch","z":"6b19158c.9268ac","name":"curve switch","property":"payload.times_of_day.active.name","propertyType":"msg","rules":[{"t":"eq","v":"night","vt":"str"},{"t":"eq","v":"morning","vt":"str"},{"t":"eq","v":"day","vt":"str"},{"t":"eq","v":"evening","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":510,"y":240,"wires":[["732ea191.d00f6","3e61774c.b55038"],["b120795d.826b98","f3460130.aaa94"],["5569b456.1bdfdc","29cf0cab.f02324"],["cef1561a.30ee98","8f05ca84.b6aed8"]]},{"id":"66290782.60fc78","type":"function","z":"6b19158c.9268ac","name":"Times of Day","func":"var newmsg = JSON.parse(JSON.stringify(msg));\nnewmsg.payload.times_of_day = {};\n\nprocessTimeOfDay = function(nme, obj, name_start, name_end) {\n    var ret = {};\n    ret.name  = nme;\n    ret.start = obj.times[name_start].ts;\n    ret.end   = obj.times[name_end].ts;\n    ret.perc  = (obj.ts - ret.start) / (ret.end - ret.start);\n    \n    \n    // to cover range starting before midnight and ending after it.\n    if (ret.start > ret.end)\n    {\n        var a;\n\n        // before midnight\n        if (ret.start < obj.ts) \n        {\n            a = new Date(ret.end);\n            a.setUTCDate(a.getUTCDate() + 1);\n            ret.end = a.getTime();\n             \n             \n        }\n        else if (ret.end > obj.ts) \n        {\n            a = new Date(ret.start);\n            a.setUTCDate(a.getUTCDate() - 1);\n            ret.start = a.getTime();\n\n        }\n\n    }\n    \n    ret.perc  = (obj.ts - ret.start) / (ret.end - ret.start);\n    \n    obj.times_of_day[nme] = ret;\n    if (ret.start <= obj.ts && obj.ts < ret.end ) obj.times_of_day.active = ret;\n}\n\n\nprocessTimeOfDay('night', newmsg.payload, \"astronomicalDusk\", \"astronomicalDawn\");\nprocessTimeOfDay('morning', newmsg.payload, \"astronomicalDawn\", \"goldenHourDawnEnd\");\nprocessTimeOfDay('day', newmsg.payload, \"goldenHourDawnEnd\", \"sunsetStart\");\nprocessTimeOfDay('evening', newmsg.payload, \"sunsetStart\", \"astronomicalDusk\");\n\n\nreturn newmsg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":290,"y":240,"wires":[["6c971105.ef196"]]},{"id":"b1a9d7fc.ee4c98","type":"range","z":"6b19158c.9268ac","minin":"0","maxin":"1","minout":"1","maxout":"100","action":"scale","round":true,"property":"payload","name":"","x":980,"y":140,"wires":[[]]},{"id":"b3ac8cc8.a4c22","type":"range","z":"6b19158c.9268ac","minin":"0","maxin":"1","minout":"2000","maxout":"5500","action":"scale","round":true,"property":"payload","name":"","x":990,"y":340,"wires":[[]]},{"id":"a103c146.6c32f","type":"sun-position","z":"6b19158c.9268ac","name":"","positionConfig":"24f3bf88.9c0a2","rules":[],"onlyOnChange":"true","topic":"","outputs":1,"start":"","startType":"none","startOffset":0,"startOffsetType":"num","startOffsetMultiplier":60000,"end":"","endType":"none","endOffset":0,"endOffsetType":"num","endOffsetMultiplier":60000,"x":190,"y":160,"wires":[["66290782.60fc78"]]},{"id":"732ea191.d00f6","type":"spline-curve","z":"6b19158c.9268ac","name":"night curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.3},{"x":0.333,"y":0.037},{"x":0.477,"y":0},{"x":0.783,"y":0},{"x":1,"y":0}],"x":750,"y":80,"wires":[["b1a9d7fc.ee4c98"]]},{"id":"b120795d.826b98","type":"spline-curve","z":"6b19158c.9268ac","name":"morning curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.01},{"x":0.132,"y":0.01},{"x":0.269,"y":0.027},{"x":0.449,"y":0.124},{"x":0.676,"y":0.334},{"x":1,"y":0.75}],"x":760,"y":120,"wires":[["b1a9d7fc.ee4c98"]]},{"id":"cef1561a.30ee98","type":"spline-curve","z":"6b19158c.9268ac","name":"evening curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.85},{"x":0.036,"y":0.78},{"x":0.176,"y":0.63},{"x":1,"y":0.3}],"x":760,"y":200,"wires":[["b1a9d7fc.ee4c98"]]},{"id":"5569b456.1bdfdc","type":"spline-curve","z":"6b19158c.9268ac","name":"daylight curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.744},{"x":0.03,"y":0.838},{"x":0.097,"y":0.941},{"x":0.183,"y":1},{"x":0.86,"y":0.995},{"x":0.943,"y":0.931},{"x":1,"y":0.85}],"x":760,"y":160,"wires":[["b1a9d7fc.ee4c98"]]},{"id":"3e61774c.b55038","type":"spline-curve","z":"6b19158c.9268ac","name":"night curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.5},{"x":0.067,"y":0.368},{"x":0.133,"y":0.265},{"x":0.27,"y":0.171},{"x":0.333,"y":0.16},{"x":0.506,"y":0.124},{"x":0.606,"y":0}],"x":750,"y":280,"wires":[["b3ac8cc8.a4c22"]]},{"id":"f3460130.aaa94","type":"spline-curve","z":"6b19158c.9268ac","name":"morning curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0},{"x":0.42,"y":0.018},{"x":0.54,"y":0.051},{"x":0.587,"y":0.101},{"x":0.817,"y":0.675},{"x":1,"y":0.85}],"x":760,"y":320,"wires":[["b3ac8cc8.a4c22"]]},{"id":"8f05ca84.b6aed8","type":"spline-curve","z":"6b19158c.9268ac","name":"evening curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.85},{"x":0.787,"y":0.598},{"x":1,"y":0.5}],"x":760,"y":400,"wires":[["b3ac8cc8.a4c22"]]},{"id":"29cf0cab.f02324","type":"spline-curve","z":"6b19158c.9268ac","name":"daylight curve","output_key":"","input_key":"payload.times_of_day.active.perc","points":[{"x":0,"y":0.85},{"x":0.123,"y":0.927},{"x":0.5,"y":1},{"x":0.92,"y":0.904},{"x":1,"y":0.85}],"x":760,"y":360,"wires":[["b3ac8cc8.a4c22"]]},{"id":"24f3bf88.9c0a2","type":"position-config","name":"Lokalizacja Dom","isValide":"true","longitude":"0","latitude":"0","angleType":"deg","timeZoneOffset":99,"timeZoneDST":0,"stateTimeFormat":"3","stateDateFormat":"12","contextStore":""},{"id":"a7224158.bce14","type":"tab","label":"Circadian  HA","disabled":false,"info":""},{"id":"4a33acb1.ef9cd4","type":"inject","z":"a7224158.bce14","name":"","props":[{"p":"payload"}],"repeat":"60","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":60,"wires":[["267e819b.76b53e"]]},{"id":"267e819b.76b53e","type":"subflow:6b19158c.9268ac","z":"a7224158.bce14","name":"","env":[],"x":330,"y":60,"wires":[["f609dab0c9db17a5"],["539deae4c1946e59"],[]],"outputLabels":["Bigthness [%]","Warmth [K]","Debug"],"icon":"node-red-node-suncalc/sun.png"},{"id":"f609dab0c9db17a5","type":"ha-sensor","z":"a7224158.bce14","name":"Gain [%]","entityConfig":"b2b43bbe.3f8d38","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":560,"y":20,"wires":[[]]},{"id":"539deae4c1946e59","type":"ha-sensor","z":"a7224158.bce14","name":"Warmth [K]","entityConfig":"603755ea.9b13ac","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"x":570,"y":100,"wires":[[]]},{"id":"b2b43bbe.3f8d38","type":"ha-entity-config","server":"1be24b09.4dad55","deviceConfig":"","name":"sensor config for Gain [%]","version":6,"entityType":"sensor","haConfig":[{"property":"name","value":"nr_circadian_gain1"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:brightness-percent"},{"property":"unit_of_measurement","value":"%"},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"resend":true},{"id":"603755ea.9b13ac","type":"ha-entity-config","server":"1be24b09.4dad55","deviceConfig":"","name":"sensor config for Warmth [K]","version":6,"entityType":"sensor","haConfig":[{"property":"name","value":"nr_circadian_warmth1"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:brightness-6"},{"property":"unit_of_measurement","value":"K"},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"resend":true},{"id":"1be24b09.4dad55","type":"server","name":"Home Assistant_2022","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":true,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

node-red-contrib-spline-curve

To jest tylko przykład nie i wiem czy zadziała to w twojej konfiguracji.