Vidéo (en anglais)
Voici la vidéo de tutoriel en anglais si vous voulez suivre pas à pas le guide.
Introduction
Est-ce qu’il vous est arrivé au moins une fois de rater une alarme parce que vous l’avez éteint pour vous rendormir ? Est-ce que vous avez envie de lancer votre machine à café au bon moment, en fonction de votre réveil ? Avec ce tutoriel, vous pourrez envoyer plusieurs alarmes vers Home Assistant pour déclencher automatiquement différentes actions. L’objectif de ce tutoriel est de simplement créer vos alarmes dans votre iPhone/iPad et laisser le reste se faire tout seul.
Prérequis
Pour suivre ce tutoriel, vous aurez besoin d’un appareil sous iOS (iPhone, iPad) disposant de l’application Raccourcis. Dans mon cas, mes appareils tournent sous iOS 26.2. Vous aurez également besoin de Home Assistant (ma version est 2026.2.2) et Node-Red (ma version est 21.0.0).
Notez également qu’il faut une connexion internet pour envoyer les alarmes vers Home Assistant de manière automatique.
Principe de fonctionnement
Avant de passer à la création du raccourci, prenons un moment pour comprendre le principe de fonctionnement. J’ai préparé un diagramme très simple pour illustrer les différentes étapes.
Notre scénario va commencer par la configuration de nos alarmes. Vous pouvez en créer autant que vous le souhaitez.
Ensuite, via l'application Raccourcis d'iOS, toutes vos alarmes seront envoyées automatiquement vers Home Assistant grâce aux actions call-service, disponibles dès que l'application Home Assistant est installée.
Par la suite, dans Node-Red (dans Home Assistant), les alarmes seront filtrées en fonction de ce qu’il y a dans la description (ou label). Pour chaque alarme, on calculera le délai entre le moment de réception dans Node-Red et l’heure d’activation.
Une fois tous les délais obtenus, à l’heure d’activation de chaque alarme, on commencera par allumer une première bande LED cinq minutes avant l’alarme qu’on fera incrémenter progressivement jusqu’à son intensité maximale au bout de 5 minutes. Ainsi, au moment de l’alarme, la lumière de la LED sera au maximum en plus de la sonnerie du réveil. Au même moment, on va également commencer à allumer le plafonnier progressivement durant 5 minutes.
Ainsi, entre la sonnerie et les deux lumières, il sera difficile de ne pas se lever 😊. Personnellement, je me réveille en général quand la bande LED commence à s’allumer et j’apprécie ce réveil en douceur car j’ai positionné la bande LED au pied de mon lit.
Création du raccourci
Premièrement, on va créer nos alarmes.
Assurez-vous de remplir le champ description.
Dans l’application Raccourci, la première action sera de récupérer toutes les alarmes. Dans notre cas, on va directement filtrer dans l’action pour ne prendre en compte que celles qui sont activées.
Une fois cette étape finie, on va créer une boucle pour traiter chaque alarme parmi toutes les alarmes trouvées. Dans la boucle, on va donc mettre dans un texte l’heure de l’alarme et sa description. Concernant l’élément « heure », vous pouvez vérifier l’option suivante :
Il est très important dans cette partie de faire comme moi pour éviter des problèmes de format. En effet, on enverra vers Home Assistant des données au format JSON, donc il faut impérativement respecter le formalisme.
Après la boucle, on regarde en premier le cas où le texte est vide (pas d’alarmes actives).
Dans ce cas, on va envoyer un booléen vers Home Assistant pour désactiver l’automatisation et éviter un déclenchement. Là aussi, c’est très important d’envoyer des dictionnaires car les données contenues dans les dictionnaires seront automatiquement converties dans le bon format (JSON) par l’application Raccourcis. Je n’ai pas utilisé les dictionnaires dans la boucle car ils ne s'y comportent pas comme prévu.
Comme on envoie un booléen, on va utiliser le call service « input_boolean_turn_off » pour désactiver le booléen. Vous pouvez choisir le nom que vous voulez pour votre variable mais il doit impérativement commencer par « input_boolean».
Ensuite, on va traiter le cas où on a des alarmes.
Dans ce cas, on va combiner toutes les alarmes trouvées pour ne les envoyer qu’une seule fois. Pour ce faire, utilisez l’action combiner texte, mettez « personnalisé » comme option et rajouter une virgule comme séparateur. Ainsi, toutes les alarmes vont être combinées avec des virgules entre chacune. À la fin, on va prendre le résultat (toutes les alarmes combinées) et on va le mettre entre crochet (toujours pour ne pas avoir de problème de format). Maintenant, on pourra envoyer le résultat vers Home Assistant avec un dictionnaire.
Dans ce cas, on va envoyer du texte, donc il faudra utiliser le call service « input_text.value ». Là également, le nom de la variable que vous aurez choisi doit impérativement commencer par « input_text ». Une fois cette étape finie, il nous reste seulement à renvoyer le booléen qu’on avait défini auparavant mais avec l’action « input_boolean.turn_on ».
Maintenant, on a fini la partie raccourcis, on va passer à la partie automatisation pour envoyer automatiquement les alarmes.
Automatisation de l’envoi des alarmes
Dans cette partie, il suffit de créer une nouvelle automatisation pour qu’à chaque fois qu’on ferme l’application horloge, les alarmes soient automatiquement envoyées vers Home Assistant.
Pour ce faire, dans la partie action, choisir d’exécuter le raccourci qu’on vient de créer.
Ainsi, on vient de finir la première étape d’envoi des alarmes.
Configuration dans Home Assistant
Avant de passer à la création de notre automatisation dans Home Assistant, on va d’abord faire les configurations pour s'assurer que les données sont bien reçues dans le bon format.
Dans mon Home Assistant, j’utilise Visual Studio mais cette partie devrait fonctionner avec n’importe quel éditeur de texte. Le fichier configuration.yaml centralise l'ensemble des fichiers de configuration.
Dans notre cas, on va modifier les fichiers input_boolean.yaml, input_text.yaml et template.yaml. Nous modifions les deux premiers fichiers puisque dans le raccourci, nous avons utilisé « input_boolean » et « input_text ».
Pour « input_boolean », il faudra mettre le même nom que celui du raccourci :
Pour « input_text », il faut également mettre le même nom que celui du raccourci mais aussi préciser le maximum de caractère à 255 et le mode text.
Enfin, nous allons créer un sensor dans template.yaml qu’on va formater pour l’utiliser dans Node-Red.
On rajoute un state à notre sensor reprenant la valeur brute de l'input_text de ce qui est envoyé par le raccourci mais surtout, on va rajouter un attribut pour qu’en cas de problème de format, envoyer un tableau JSON vide.
Après avoir rechargé les configurations, vous pouvez tester si vos variables sont correctement reçues dans Home Assistant en créant des alarmes dans l’application horloge et en vérifiant que les valeurs apparaissent correctement dans Home Assistant.
Création de l’automatisation
Pour cette automatisation, nous utiliserons uniquement des blocs simples de Node-Red.
En premier lieu, on prend l’état du booléen.
Quand il est actif, on utilise un current_state pour obtenir les alarmes (en utilisant le sensor qu’on a créé précédemment). Une fois obtenu, on va créer une fonction qui se chargera de calculer les délais et d’envoyer au bon format aux blocs délais.
Calcul du délai
Dans la fonction, on détecte déjà s’il y a une description (normalement toutes les alarmes ont une description même si vous ne l’avez pas rempli : par défaut, elles sont « alarme »). Après cela, on va juste créer un tableau d’alarmes où on a l’heure de l’alarme et son délai avant exécution. Dans le cas où le calcul du délai est négatif (quand l’heure d’alarme est avant l’heure actuelle), cela veut dire que c’est une alarme pour le jour suivant.
Le prochain code est pour s’assurer que les données en entrées du bloc fonction sont dans le bon format.
C’est également une protection supplémentaire pour éviter des problèmes de format (j’en ai eu beaucoup pour créer ce tutoriel).
SplitAprès cette fonction, on va insérer un bloc split pour séparer chaque alarme. Sans lui, le bloc délai ne pourrait gérer qu'une seule alarme à la fois. Cependant, après ce bloc split, le message envoyé n’est plus dans le bon format. On va devoir recréer une nouvelle fonction juste pour le formatage.
Formatage des messages en sortie du bloc Split
Dans cette fonction, on va juste recopier les messages pour les mettre dans le bon format attendu par le bloc délai. Nous allons aussi créer une deuxième sortie pour l’activation de la bande LED 5 minutes avant l’alarme. Cette deuxième sortie sera donc la copie de la première retranchée de 5 minutes. Dans le cas où le délai de la deuxième sortie est nul (activation de l’alarme est dans moins de 5 minutes), nous n’allons rien envoyer.
Configuration du bloc délai
Dans le bloc délai, il faut juste remplacer le délai par msg.delay (délai calculé précédemment).
Actions
Concernant les actions, j’ai fait le choix d’éteindre les lumières dans le cas où elles étaient déjà allumées. Vous pouvez bien sûr choisir un autre scénario en fonction de votre usage. Pour la bande LED, j’ai un modèle de la marque IKEA. Pour la faire allumer progressivement, je dois utiliser deux blocs actions.
Dans le premier, il faut mettre l’intensité de départ (valeur minimale dans mon cas) avec une température de couleur de 1800 Kelvin (teinte proche du lever de soleil et donc douce pour le réveil).
Dans le deuxième bloc, il faut mettre l’intensité finale (valeur maximale dans mon cas) ainsi que la durée de transition en secondes.
Pour le plafonnier, il n’y a pas besoin de deux blocs.
Un seul suffit en mettant l’intensité maximale et la transition voulue.
ResetEnfin, il faudra faire un reset des alarmes dans le cas où il n’y a aucune alarme activée.
On enverra donc un message reset vers les délais lorsqu’aucune alarme n’est activée.
Si par exemple vous créez une nouvelle alarme, il faudra en premier les désactiver toutes et l’envoyer vers Home Assistant pour faire le reset. Une fois cela fait, vous pouvez créer vos alarmes et les renvoyer à Home Assistant.
Maintenant on a fini avec le codage. Si vous voulez créer des alarmes différentes, vous n'avez qu'à changer les descriptions et les prendre en compte dans la fonction de calcul des délais. Une fois tout configuré, vous n'avez plus rien à faire : créez simplement vos alarmes dans votre téléphone et laissez l'automatisation gérer le reste.
Téléchargements
Vous trouverez également le lien du raccourci ici : https://www.icloud.com/shortcuts/d679783c546d4c16b584e014ab7b7e62
Vous pouvez importer le flux Node-Red:
[
{
"id": "f9c814eb00e4931b",
"type": "tab",
"label": "Tuto",
"disabled": false,
"info": "",
"env": []
},
{
"id": "baebc8ef450c9cda",
"type": "server-state-changed",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 6,
"outputs": 2,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"input_boolean.tuto_alarms_active"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "on",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": "0",
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": false,
"ignoreCurrentStateUnavailable": false,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 200,
"y": 160,
"wires": [
[
"c66b6f9764cbf10f"
],
[]
]
},
{
"id": "c66b6f9764cbf10f",
"type": "api-current-state",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 3,
"outputs": 1,
"halt_if": "",
"halt_if_type": "str",
"halt_if_compare": "is",
"entity_id": "sensor.iphone_tuto_alarms",
"state_type": "str",
"blockInputOverrides": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "entity"
}
],
"for": "0",
"forType": "num",
"forUnits": "minutes",
"override_topic": false,
"state_location": "payload",
"override_payload": "msg",
"entity_location": "data",
"override_data": "msg",
"x": 640,
"y": 160,
"wires": [
[
"c670ef7b7cd52d9a"
]
]
},
{
"id": "5022d443bef38707",
"type": "split",
"z": "f9c814eb00e4931b",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 1070,
"y": 160,
"wires": [
[
"58887052eaa93e98"
]
]
},
{
"id": "5527043f8bd23726",
"type": "delay",
"z": "f9c814eb00e4931b",
"name": "Delay until alarm",
"pauseType": "delayv",
"timeout": "5",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 1490,
"y": 40,
"wires": [
[
"23bffa4b5284a189"
]
]
},
{
"id": "c670ef7b7cd52d9a",
"type": "function",
"z": "f9c814eb00e4931b",
"name": "Delay Calc",
"func": "\nif(!Array.isArray(msg.payload)) {\n try {\n msg.payload = JSON.parse(msg.payload);\n } catch (e){\n node.error(\"Payload invalid : \" + msg.payload);\n return null;\n }\n}\n\nif (!Array.isArray(msg.payload)){\n node.error(\"Payload is not an array after parsing\");\n return null;\n}\n\n\n\nlet now = Date.now();\n\nmsg.payload = msg.payload\n .filter(a=> a.label && a.label.toLowerCase() === 'work')\n .map( a=> {\n \n let [h, m] = a.time.split(':').map(Number);\n let alarmDate = new Date();\n alarmDate.setHours(h, m, 0, 0);\n \n if (alarmDate.getTime()< now) {\n alarmDate.setDate(alarmDate.getDate() + 1);\n \n }\n \n return {\n payload: a.time,\n delay: alarmDate.getTime() - now\n };\n });\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 910,
"y": 160,
"wires": [
[
"5022d443bef38707"
]
]
},
{
"id": "58887052eaa93e98",
"type": "function",
"z": "f9c814eb00e4931b",
"name": "Format for delay",
"func": "msg.delay = msg.payload.delay;\nmsg.topic = msg.payload.payload;\nmsg.payload = msg.payload.payload;\n\nlet msg2 = RED.util.cloneMessage(msg);\nmsg2.delay = msg.delay - (5 * 60 * 1000);\nif (msg2.delay <= 0 ) return [msg, null];\n\nreturn [msg, msg2];",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1240,
"y": 160,
"wires": [
[
"5527043f8bd23726"
],
[
"34c470f2d36c1f9f"
]
]
},
{
"id": "34c470f2d36c1f9f",
"type": "delay",
"z": "f9c814eb00e4931b",
"name": "Delay until alarm - 5 min",
"pauseType": "delayv",
"timeout": "5",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 1490,
"y": 280,
"wires": [
[
"33ad3bfe6b14ef70"
]
]
},
{
"id": "33ad3bfe6b14ef70",
"type": "api-current-state",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 3,
"outputs": 2,
"halt_if": "on",
"halt_if_type": "str",
"halt_if_compare": "is",
"entity_id": "light.led_ikea_chambre",
"state_type": "str",
"blockInputOverrides": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "entity"
}
],
"for": "0",
"forType": "num",
"forUnits": "minutes",
"override_topic": false,
"state_location": "payload",
"override_payload": "msg",
"entity_location": "data",
"override_data": "msg",
"x": 1810,
"y": 280,
"wires": [
[
"e620f549064ba85a"
],
[
"ea9bc59927c89518"
]
]
},
{
"id": "e620f549064ba85a",
"type": "api-call-service",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 7,
"debugenabled": false,
"action": "light.turn_off",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"light.led_ikea_chambre"
],
"labelId": [],
"data": "",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "light",
"service": "turn_off",
"x": 2110,
"y": 260,
"wires": [
[]
]
},
{
"id": "ea9bc59927c89518",
"type": "api-call-service",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 7,
"debugenabled": false,
"action": "light.turn_on",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"light.led_ikea_chambre"
],
"labelId": [],
"data": "{\t \"brightness\" : 2,\t \"kelvin\": 1800\t}",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "light",
"service": "turn_on",
"x": 2110,
"y": 320,
"wires": [
[
"63337697f36f159c"
]
]
},
{
"id": "23bffa4b5284a189",
"type": "api-current-state",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 3,
"outputs": 2,
"halt_if": "on",
"halt_if_type": "str",
"halt_if_compare": "is",
"entity_id": "light.plafonnier_chambre",
"state_type": "str",
"blockInputOverrides": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "entity"
}
],
"for": "0",
"forType": "num",
"forUnits": "minutes",
"override_topic": false,
"state_location": "payload",
"override_payload": "msg",
"entity_location": "data",
"override_data": "msg",
"x": 1820,
"y": 40,
"wires": [
[
"807b5c20967f19f3"
],
[
"cba31e81ef519d63"
]
]
},
{
"id": "63337697f36f159c",
"type": "api-call-service",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 7,
"debugenabled": false,
"action": "light.turn_on",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"light.led_ikea_chambre"
],
"labelId": [],
"data": "{\t \"brightness\": 254,\t \"transition\": 300\t}",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "light",
"service": "turn_on",
"x": 2310,
"y": 320,
"wires": [
[]
]
},
{
"id": "807b5c20967f19f3",
"type": "api-call-service",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 7,
"debugenabled": false,
"action": "light.turn_off",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"light.plafonnier_chambre"
],
"labelId": [],
"data": "",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "light",
"service": "turn_off",
"x": 2130,
"y": 20,
"wires": [
[]
]
},
{
"id": "cba31e81ef519d63",
"type": "api-call-service",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 7,
"debugenabled": false,
"action": "light.turn_on",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"light.plafonnier_chambre"
],
"labelId": [],
"data": "{\t \"brightness\": 254,\t \"kelvin\": 1800,\t \"transition\": 300\t}",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "light",
"service": "turn_on",
"x": 2130,
"y": 80,
"wires": [
[]
]
},
{
"id": "f393a8b265b5331c",
"type": "server-state-changed",
"z": "f9c814eb00e4931b",
"name": "",
"server": "4e2c03c1.d491cc",
"version": 6,
"outputs": 2,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"input_boolean.tuto_alarms_active"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "off",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": "0",
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": false,
"ignoreCurrentStateUnavailable": false,
"outputProperties": [
{
"property": "reset",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 1560,
"y": 180,
"wires": [
[
"5527043f8bd23726",
"34c470f2d36c1f9f"
],
[]
]
},
{
"id": "3d30635de28f1a67",
"type": "comment",
"z": "f9c814eb00e4931b",
"name": "Node-Red automation to use multiple iPhone alarms : www.smarthome3d.com",
"info": "",
"x": 330,
"y": 40,
"wires": []
},
{
"id": "4e2c03c1.d491cc",
"type": "server",
"name": "Home Assistant",
"addon": true,
"rejectUnauthorizedCerts": true,
"ha_boolean": "",
"connectionDelay": false,
"cacheJson": false,
"heartbeat": false,
"heartbeatInterval": "",
"statusSeparator": "",
"enableGlobalContextStore": false
},
{
"id": "df33f130934d39ab",
"type": "global-config",
"env": [],
"modules": {
"node-red-contrib-home-assistant-websocket": "0.80.3"
}
}
]
