Witajcie czy jest jakiś magiczny sposób aby HA a najlepiej node-red pobierał kompletne dane z kalendarza google. Nie tylko pierwsze wydarzenie z dnia ale i następne jak np robi to Atomic Calendar Revive, aby można było z konkretnego wydarzenia zrobić automatyzacje albo żeby w statusie porannym wykazywało wszystkie zaplanowane wydarzenia z dzisiaj, jutro, pojutrze.
ACR jest rozszerzeniem frontendu, więc z zasady nie pobiera niczego z kalendarze google, on korzysta z danych jakie HA już posiada.
Skoro posiada takie dane szczegółowe to dlaczego nie da się ich wyciągnąć ?
Jeśli rozszerzenie frontendu by pobierało samowolnie jakieś dane spoza backendu HA to byłoby złamanie podstawowych zasad konstrukcji HA (jako, że frontend jest renderowany w przeglądarce, a nie na serwerze HA).
Czyli, że nie ma takiej możliwości ?
Możliwości to zapewne są, ale poczekaj na kogoś kto intensywniej używa kalendarzy (swoją drogą jeśli dane potrzebujesz w NR i uważasz, że nie ma ich w HA to lepiej pobieraj z poziomu NR w końcu to 2 całkowicie osobne serwery które nie mają nic wspólnego).
Jeżeli Atomic Calendar Revive pobierał by te dane z Google bez twojego zezwolenia od razu został by zablokowany. Zapytania dla kalendarza znajdziesz w Narzędzia deweloperskie → Akcje
calendar.get_events
https://www.home-assistant.io/docs/scripts/perform-actions/#use-templates-to-handle-response-data
Zakres pobierania danych start_date_time
end_date_time
action: calendar.get_events
target:
entity_id: calendar.school
data:
duration:
hours: 24
response_variable: agenda
Czy to jest normalne, że podając encje wszystkich kalendarzy i zakres danych w którym na 100% coś powinno być to i tak nic nie pokazuje w wyniku odpowiedzi akcji?
Powinno pokazywać, jednak jest pusto.
Tu masz template:
- trigger:
- platform: homeassistant
event: start
- platform: event
event_type: event_template_reloaded
- platform: state
entity_id: calendar.gp2023
action:
- service: calendar.get_events
target:
entity_id: calendar.gp2023
data:
duration:
days: 2
response_variable: agenda
- variables:
events: "{{ agenda['calendar.gp2023']['events'] }}"
sensor:
- name: "Nadchodzące wydarzenia"
unique_id: nadchodzace_wydarzenia
icon: mdi:calendar-star
state: "{{ events | count }}"
attributes:
events: >
{%- if events | count > 0 %}
{%- for event in events %}
{%- if (event.start | as_datetime).day == now().day %}
{{ event.start | as_timestamp | timestamp_custom('Today at %-I:%M %p') }}: {{ event.summary }}
{%- else %}
{{ event.start | as_timestamp | timestamp_custom('%A at %-I:%M %p') }}: {{ event.summary }}
{%- endif %}
{%- if event.description is defined %} - {{ event.description }}{% endif %}
{%- endfor %}.
{%- endif -%}
Action/Akcje:
data:
start_date_time: "{{ today_at() - timedelta(days=2) }}"
end_date_time: "{{ now() }}"
action: calendar.get_events
target:
entity_id: calendar.wmu_2023
response_variable: agenda
data:
start_date_time: >-
{{ (now() + timedelta(days=1)).replace(hour=0, minute=0, second=0) |
as_timestamp | timestamp_utc }}
end_date_time: >-
{{ (now() + timedelta(days=7)).replace(hour=0, minute=0, second=0) |
as_timestamp | timestamp_utc }}
action: calendar.get_events
target:
entity_id: calendar.wmu_2023
response_variable: agenda
timedelta(days=6) }}"
← ilość dni
Znalazłem ten “magiczny” sposób, działa tak jak to sobie założyłem i jak chciałem żeby działał. Dzielę się swoją pracą, może komuś się przyda.
Zaczynamy od instalacji dodatku ALT + SHIFT + P
→ Install
→ node-red-contrib-html-pro
.
Kod FLOW do importu:
[
{
"id": "dcbd23739adda60d",
"type": "inject",
"z": "c617c3f41b6c4831",
"name": "Codziennie o 6:00",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "00 06 * * *",
"once": true,
"onceDelay": "0.1",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 230,
"y": 5320,
"wires": [
[
"8b664a9fefb8fdfa"
]
]
},
{
"id": "8b664a9fefb8fdfa",
"type": "function",
"z": "c617c3f41b6c4831",
"name": "Ustaw start i end daty",
"func": "const now = new Date();\n\n// Ustaw start na dzisiaj, godzina 00:00:00\nconst startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);\nconst start = startDate.toISOString();\n\n// Ustaw end na 3 dni później, o 23:59:59\nconst endDate = new Date(startDate);\nendDate.setDate(endDate.getDate() + 3);\nendDate.setHours(23, 59, 59);\nconst end = endDate.toISOString();\n\nmsg.start = start;\nmsg.end = end;\nreturn msg;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 395,
"y": 5340,
"wires": [
[
"695f194cdf1b97f0",
"bd6b2ce4300e5f74",
"058654d44cde1680"
]
],
"l": false
},
{
"id": "695f194cdf1b97f0",
"type": "http request",
"z": "c617c3f41b6c4831",
"name": "Kalendarz Familijny",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "bearer",
"senderr": false,
"headers": [],
"x": 570,
"y": 5300,
"wires": [
[
"fdede8368d9a2516"
]
]
},
{
"id": "bd6b2ce4300e5f74",
"type": "http request",
"z": "c617c3f41b6c4831",
"name": "Kalendarz Sprawdziany",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "bearer",
"senderr": false,
"headers": [],
"x": 590,
"y": 5340,
"wires": [
[
"fdede8368d9a2516"
]
]
},
{
"id": "fdede8368d9a2516",
"type": "join",
"z": "c617c3f41b6c4831",
"name": "Połącz wyniki kalendarzy",
"mode": "custom",
"build": "array",
"property": "payload",
"propertyType": "msg",
"key": "topic",
"joiner": "\\n",
"joinerType": "str",
"useparts": true,
"accumulate": false,
"timeout": "",
"count": "3",
"reduceRight": false,
"reduceExp": "",
"reduceInit": "",
"reduceInitType": "",
"reduceFixup": "",
"x": 880,
"y": 5340,
"wires": [
[
"8e8c3d1213f92cba"
]
]
},
{
"id": "8e8c3d1213f92cba",
"type": "function",
"z": "c617c3f41b6c4831",
"name": "Przetwarzanie wydarzeń",
"func": "const allEvents = [];\n\n// msg.payload to tablica z 2 tablicami wydarzeń\nfor (const events of msg.payload) {\n if (Array.isArray(events)) {\n allEvents.push(...events);\n }\n}\n\nconst now = new Date();\nconst today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\nconst oneDayMs = 24 * 60 * 60 * 1000;\n\nconst grouped = {0: [], 1: [], 2: []};\n\nfor (const event of allEvents) {\n let startDateStr = null;\n\n if (typeof event.start === 'string') {\n startDateStr = event.start;\n } else if (event.start && event.start.dateTime) {\n startDateStr = event.start.dateTime;\n } else if (event.start && event.start.date) {\n startDateStr = event.start.date;\n }\n\n if (!startDateStr || !event.summary) continue;\n\n const startDate = new Date(startDateStr);\n const diffMs = startDate.getTime() - today.getTime();\n const dayDiff = Math.floor(diffMs / oneDayMs);\n\n if (dayDiff >= 0 && dayDiff <= 2) {\n const time = (event.start && event.start.dateTime)\n ? startDate.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' })\n : \"\";\n const line = time ? `${time} – ${event.summary}` : event.summary;\n grouped[dayDiff].push(line);\n }\n}\n\nfunction formatMessages(grouped) {\n let msgText = \"\";\n if (grouped[0].length) {\n msgText += \"Dzisiaj:\\n\" + grouped[0].join('\\n') + \"\\n\\n\";\n }\n if (grouped[1].length) {\n msgText += \"Jutro:\\n\" + grouped[1].join('\\n') + \"\\n\\n\";\n }\n if (grouped[2].length) {\n msgText += \"Pojutrze:\\n\" + grouped[2].join('\\n') + \"\\n\\n\";\n }\n if (!msgText) {\n msgText = \"Brak wydarzeń na najbliższe dni.\";\n }\n return msgText;\n}\n\nconst formatted = formatMessages(grouped);\n\nmsg.wiadomosc = {\n temat: \"Wydarzenia kalendarza\",\n tresc: formatted,\n typ: \"technical\"\n};\n\nreturn msg;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1150,
"y": 5340,
"wires": [
[
"df2d46dc2b60fedd"
]
]
},
{
"id": "df2d46dc2b60fedd",
"type": "debug",
"z": "c617c3f41b6c4831",
"name": "kal Wszystko",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1370,
"y": 5340,
"wires": []
},
{
"id": "058654d44cde1680",
"type": "http request",
"z": "c617c3f41b6c4831",
"name": "Kalendarz Prywatny",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "bearer",
"senderr": false,
"headers": [],
"x": 580,
"y": 5380,
"wires": [
[
"fdede8368d9a2516"
]
]
},
{
"id": "6db016f4a9942bdc",
"type": "inject",
"z": "c617c3f41b6c4831",
"name": "Codziennie o 20:00",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "00 20 * * *",
"once": true,
"onceDelay": "0.1",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 240,
"y": 5360,
"wires": [
[
"8b664a9fefb8fdfa"
]
]
}
]
NOD Kalendarz NAZWA_KALENDARZA:
URL: https://ADRES_HOME_ASSISTANTA/api/calendars/calendar.NAZWA_KALENDARZA?start={{start}}&end={{end}}
jeżeli podajemy adres IP do HA to na końcu musi być port /:8123
NAZWA_KALENDARZA podajemy nazwę z HA, możemy ją sprawdzić w Narzędzia deweloperskie
→ Aktualne stany encji
w Filtr encji
wpisując calendar
TOKEN: musimy go utworzyć w HA Profil
→ Bezpieczeństwo
→ Długotrwałe tokeny dostępu
, jeden token powinien się nadać dla kilku kalendarzy.
Dodając lub odejmując kalendarz wpisujemy ilość kalendarzy w NODZIE Połącz wyniki kalendarzy
→ Send the message:
→ After a number of message parts
.
Powyższy kod pobiera dane kalendarza od godziny 00:00 dnia pierwszego do 23:59 dnia trzeciego, jeżeli chcemy aby podawało tylko wydarzenia aktywne od chwili wywołania to edytujemy NOD Ustaw start i end daty
i wklejamy poniższy kod.
const now = new Date();
const start = now.toISOString();
const endDate = new Date(now);
endDate.setDate(endDate.getDate() + 3);
const end = endDate.toISOString();
msg.start = start;
msg.end = end;
return msg;
Wyjściowy komunikat jest skonfigurowany tak aby działał z powiadomieniami utworzonymi przez @artur w poradniku Procesy z życia wzięte - Powiadomienia, typy powiadomień .