Dialogue Vocal Bidirectionnel Alexa ↔ Home Assistant
Partie 2 : Création du Skill Alexa
Table des matières - Partie 2
2.1 - Créer un compte Amazon Developer
Le compte Amazon Developer (gratuit) permet de créer et gérer vos skills Alexa personnalisés.
URL :
Action :
-
Allez sur https://developer.amazon.com
-
Cliquez sur « Sign In » (en haut à droite)
-
Important : Utilisez le même compte Amazon que celui utilisé pour votre Alexa
-
Si vous n’avez pas encore de compte Developer, cliquez sur « Create your Amazon Developer account »
-
Acceptez les conditions d’utilisation
[CAPTURE 11 : Page de connexion / création de compte]
Astuce : En utilisant le même compte Amazon que votre Alexa, le skill sera automatiquement disponible sur vos appareils sans configuration supplémentaire.
2.2 - Créer le Skill
Navigation :
https://developer.amazon.com/alexa/console/ask
Action :
-
Une fois connecté, vous arrivez sur la Console Alexa Developer
-
Cliquez sur le bouton bleu « Create Skill » (en haut à droite)
Page 1 : Name, Locale
- Remplissez le formulaire :
Skill name :
Contrôle Maison
Note : Vous pouvez choisir un autre nom si vous préférez (ex: "Ma Maison ", « Assistant Maison », etc.)
Primary locale :
French (FR)
- Cliquez sur le bouton bleu « Next » (en haut à droite)
Page 2 : Experience, Model, Hosting service
- Section 1 - Choose a type of experience
Sélectionnez : Other (tout en bas de la liste)
- Section 2 - Choose a model
La carte Custom devrait se sélectionner automatiquement (encadrée en bleu)
- Section 3 - Hosting services
Cliquez sur la carte : Alexa-hosted (Node.js) (celle du milieu)
- Hosting region
Dans le menu déroulant en bas, sélectionnez : EU (Ireland)
- Sync Locales (toggle en haut) : Laissez-le sur OFF (position gauche)
Note : Ce toggle sert à synchroniser plusieurs langues. Comme on crée le skill uniquement en français, on n’en a pas besoin.
- Cliquez sur « Next » (en haut à droite)
Page 3 : Templates
-
Sélectionnez le template : Start from Scratch (première carte à gauche)
-
Cliquez sur « Next » (en haut à droite)
Page 4 : Review
- Vérifiez que tout est correct :
-
Skill name : Contrôle Maison
-
Primary locale : French (FR)
-
Type : Other
-
Model : Custom
-
Hosting : Alexa-hosted (Node.JS)
-
Region : EU (Ireland)
-
Template : Start from Scratch
-
Cliquez sur le bouton bleu « Create Skill » (en haut à droite)
-
Attendez 30-60 secondes que le skill soit créé
-
Une fois créé, vous arrivez sur le Dashboard du skill
Votre skill est créé !
2.3 - Configurer le nom d’invocation
Le nom d’invocation est la phrase que vous direz pour activer le skill : « Alexa, ouvre ma maison »
Navigation :
Dans le menu de gauche : Invocations → Skill Invocation Name
Action :
- Dans le champ « Skill Invocation Name », remplacez le texte par défaut par :
ma maison
-
Cliquez sur « Save Model » (en haut de la page)
-
Attendez le message « Model saved successfully »
Astuce : Vous pouvez utiliser d’autres noms d’invocation comme « contrôle maison », « assistant maison », etc. Évitez les noms trop courts (1 mot) ou trop longs (plus de 3 mots).
2.4 - Créer les Intents vocaux
Les intents sont les intentions vocales que le skill reconnaîtra. Nous allons créer 2 intents :
2.4.1 - Créer OuiIntent
Navigation :
Menu de gauche : Interaction Model → Intents
Action :
- Cliquez sur « + Add Intent » (bouton en haut)
- « Create custom intent » devrait déjà être sélectionné
- Dans le champ « Enter name for intent », tapez :
OuiIntent
Important : Respectez bien les majuscules ! OuiIntent (pas ouiintent ou Ouiintent)
-
Cliquez sur « Create custom intent »
-
Vous arrivez sur la page de configuration de l’intent
-
Dans la section « Sample Utterances » (exemples de phrases), ajoutez les phrases suivantes une par une :
Tapez chaque phrase et appuyez sur le bouton + ou sur Entrée après chaque phrase :
oui
(Entrée)
oui s'il vous plaît
(Entrée)
oui merci
(Entrée)
d'accord
(Entrée)
ok
(Entrée)
ouais
(Entrée)
bien sûr
(Entrée)
- Cliquez sur « Save Model » (en haut)
2.4.2 - Créer NonIntent
Action :
-
Cliquez sur « + Add Intent » (en haut)
-
« Create custom intent » devrait déjà être sélectionné
-
Nom de l’intent :
NonIntent
-
Cliquez sur « Create custom intent »
-
Ajoutez les Sample Utterances suivantes (une par une) :
non
non merci
pas maintenant
négatif
jamais
ça va
- Cliquez sur « Save Model »
2.5 - Build du modèle
Maintenant que les intents sont créés, il faut compiler le modèle pour que le skill puisse reconnaître vos phrases.
Action :
-
Cliquez sur le bouton bleu « Build Skill » (en haut à droite de la page)
-
Une popup s’affiche avec un message de confirmation
-
Cliquez sur « Build » dans la popup
-
Un message apparaît : « Build in progress… »
-
Attendez 20-30 secondes
-
Un message apparaît : « Build Successful » 
Astuce : Si vous voyez des warnings (avertissements) en jaune, ce n’est pas grave. Seules les erreurs en rouge bloquent le build.
2.5.5 - Créer les scripts de réponse
Avant de passer au code, créons les scripts que le skill va appeler quand vous répondrez « oui » ou « non ».
Pour l’instant, on va créer des scripts simples qui allument/éteignent une lumière.
Important : Ces scripts doivent exister AVANT de tester le skill, sinon vous aurez des erreurs !
Navigation :
Paramètres → Automatisations et scènes → Onglet Scripts
Action :
Script 1 : alexa_reponse_oui
- Cliquez sur « + CRÉER UN SCRIPT » (en bas à droite)
- Cliquez sur les 3 points (⋮) en haut à droite
- Sélectionnez « Renommer »
- Nom du script :
Alexa Reponse OUI
- ID du script (en bas) :
alexa_reponse_oui
-
Appuyez sur « OK »
-
Cliquez sur « AJOUTER UNE ACTION »
-
Sélectionnez « Appeler un service »
-
Service :
light.turn_on
-
Cible → Choisir une entité
-
Sélectionnez une lumière de test (par exemple : light.l_veranda)
-
Cliquez sur « ENREGISTRER » (en haut à droite)
Script 2 : alexa_reponse_non
- Répétez les étapes pour créer un second script :
Nom :
Alexa Reponse NON
ID :
alexa_reponse_non
Action :
- Service :
light.turn_off
- Cible : La même lumière que pour le script OUI
Les 2 scripts sont créés !
Pourquoi maintenant ? Ces scripts seront appelés par le skill Alexa quand vous répondrez « oui » ou « non ». Il faut qu’ils existent avant de tester le skill pour éviter les erreurs !
2.6 - Ajouter le code JavaScript
C’est ici que la magie opère ! Le code permet au skill de :
Navigation :
Cliquez sur l’onglet « Code » (en haut de la page)
Action :
Vous devriez voir directement l’éditeur de code avec index.js dans la liste de gauche.
Note : Comme vous avez choisi « Alexa-hosted (Node.js) » lors de la création, l’éditeur de code est directement disponible. Aucune conversion nécessaire !
2.6.1 - Remplacer par le code dynamique
Action :
-
Dans l’éditeur, SÉLECTIONNEZ TOUT le code (Ctrl+A ou Cmd+A)
-
SUPPRIMEZ TOUT (Delete)
-
COPIEZ le code suivant et COLLEZ-LE dans l’éditeur :
/* *
* Skill Alexa Dynamique - Tout paramétrable depuis Home Assistant
* Par Daniel - Février 2026
* Version 2.0 - Support entités multiples (scripts, lights, switches, covers, etc.)
* */
const Alexa = require('ask-sdk-core');
// ===== CONFIGURATION (à modifier) =====
const HA_CONFIG = {
token: 'VOTRE TOKEN',
hostname: 'URL NABUCASA'
};
// ===== FONCTION POUR LIRE UN ÉTAT HA =====
async function getHAState(entityId) {
const https = require('https');
return new Promise((resolve, reject) => {
const options = {
hostname: HA_CONFIG.hostname,
path: `/api/states/${entityId}`,
method: 'GET',
headers: {
'Authorization': `Bearer ${HA_CONFIG.token}`,
'Content-Type': 'application/json'
}
};
https.get(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const state = JSON.parse(data);
resolve(state.state);
} catch (error) {
console.log('Erreur parse JSON:', error);
reject(error);
}
});
res.on('error', (e) => {
console.log('Erreur lecture état:', e);
reject(e);
});
});
});
}
// ===== FONCTION POUR APPELER UNE ENTITÉ HA (scripts, lights, switches, covers, etc.) =====
async function callHAEntity(entityId, action) {
const https = require('https');
// Détecter le domaine de l'entité
const domain = entityId.split('.')[0];
// Déterminer le service à appeler
let servicePath;
if (domain === 'script') {
servicePath = '/api/services/script/turn_on';
} else if (domain === 'light') {
servicePath = `/api/services/light/${action}`;
} else if (domain === 'switch') {
servicePath = `/api/services/switch/${action}`;
} else if (domain === 'cover') {
if (action === 'turn_on') {
servicePath = '/api/services/cover/open_cover';
} else if (action === 'turn_off') {
servicePath = '/api/services/cover/close_cover';
}
} else if (domain === 'climate') {
if (action === 'turn_on') {
servicePath = '/api/services/climate/turn_on';
} else if (action === 'turn_off') {
servicePath = '/api/services/climate/turn_off';
}
} else if (domain === 'fan') {
servicePath = `/api/services/fan/${action}`;
} else if (domain === 'media_player') {
if (action === 'turn_on') {
servicePath = '/api/services/media_player/turn_on';
} else if (action === 'turn_off') {
servicePath = '/api/services/media_player/turn_off';
}
} else {
// Par défaut, essayer homeassistant.turn_on/off
servicePath = `/api/services/homeassistant/${action}`;
}
const postData = JSON.stringify({
entity_id: entityId
});
return new Promise((resolve, reject) => {
const options = {
hostname: HA_CONFIG.hostname,
path: servicePath,
method: 'POST',
headers: {
'Authorization': `Bearer ${HA_CONFIG.token}`,
'Content-Type': 'application/json',
'Content-Length': postData.length
}
};
const req = https.request(options, (res) => {
console.log(`Entité ${entityId} - Action ${action} - Statut:`, res.statusCode);
resolve();
});
req.on('error', (e) => {
console.log('Erreur appel entité:', e);
reject(e);
});
req.write(postData);
req.end();
});
}
// ===== LAUNCH REQUEST (Pose la question) =====
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
async handle(handlerInput) {
try {
// Lire la question depuis HA
const question = await getHAState('input_text.alexa_question');
console.log('Question lue depuis HA:', question);
return handlerInput.responseBuilder
.speak(question)
.reprompt('Dites oui ou non')
.getResponse();
} catch (error) {
console.log('Erreur LaunchRequest:', error);
return handlerInput.responseBuilder
.speak('Désolé, je ne peux pas récupérer la question depuis Home Assistant.')
.getResponse();
}
}
};
// ===== OUI INTENT (Réponse OUI) =====
const OuiIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'OuiIntent';
},
async handle(handlerInput) {
let speakOutput = 'D\'accord, c\'est fait';
try {
// Lire le message personnalisé
const messageOui = await getHAState('input_text.alexa_message_oui');
speakOutput = messageOui || speakOutput;
// Lire l'entité à appeler depuis HA
const entityOui = await getHAState('input_text.alexa_script_oui');
console.log('Entité OUI à appeler:', entityOui);
// Appeler l'entité avec l'action turn_on
await callHAEntity(entityOui, 'turn_on');
} catch (error) {
console.log('Erreur OuiIntent:', error);
}
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
// ===== NON INTENT (Réponse NON) =====
const NonIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'NonIntent';
},
async handle(handlerInput) {
let speakOutput = 'Très bien, je n\'ai rien fait';
try {
// Lire le message personnalisé
const messageNon = await getHAState('input_text.alexa_message_non');
speakOutput = messageNon || speakOutput;
// Lire l'entité à appeler depuis HA
const entityNon = await getHAState('input_text.alexa_script_non');
console.log('Entité NON à appeler:', entityNon);
// Appeler l'entité avec l'action turn_off
await callHAEntity(entityNon, 'turn_off');
} catch (error) {
console.log('Erreur NonIntent:', error);
}
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
// ===== AUTRES HANDLERS =====
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak('Vous pouvez répondre oui ou non.')
.reprompt('Dites oui ou non')
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak('Au revoir!')
.getResponse();
}
};
const FallbackIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak('Désolé, je ne comprends pas. Réessayez.')
.reprompt('Dites oui ou non')
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
return handlerInput.responseBuilder.getResponse();
}
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error: ${JSON.stringify(error)}`);
return handlerInput.responseBuilder
.speak('Désolé, problème technique. Réessayez.')
.reprompt('Dites oui ou non')
.getResponse();
}
};
// ===== EXPORT =====
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
HelpIntentHandler,
OuiIntentHandler,
NonIntentHandler,
CancelAndStopIntentHandler,
FallbackIntentHandler,
SessionEndedRequestHandler)
.addErrorHandlers(ErrorHandler)
.withCustomUserAgent('home-assistant-skill-dynamic/v2.0')
.lambda();
2.6.2 - Personnaliser le code avec vos informations
ÉTAPE CRUCIALE !
Il faut maintenant remplacer les valeurs temporaires par vos vraies informations (Token et URL Nabu Casa).
Action :
-
Reprenez votre fichier texte où vous avez noté :
-
Votre URL Nabu Casa
-
Votre token
-
Dans l’éditeur, cherchez la ligne 9 :
token: 'VOTRE_TOKEN_ICI',
- Remplacez
VOTRE_TOKEN_ICI par votre vrai token (celui qui commence par eyJ...)
Exemple :
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI1NTljM...',
- Cherchez la ligne 10 :
hostname: 'VOTRE_URL_NABU_CASA_ICI'
- Remplacez
VOTRE_URL_NABU_CASA_ICI par votre URL Nabu Casa (
SANS https://)
Exemple :
hostname: 'abcd1234efgh5678.ui.nabu.casa'
Sécurité : Ne partagez JAMAIS cette capture avec le token visible !
2.7 - Déployer le skill
Maintenant que le code est configuré, il faut le déployer pour qu’il soit actif.
Action :
- Cliquez sur le bouton « Save » (en haut de l’éditeur)
[CAPTURE 40 : Bouton « Save » en haut]
-
Attendez le message « Saved successfully »
-
Cliquez sur le bouton « Deploy » (juste à côté de Save)
-
Attendez 30-60 secondes que le déploiement se termine
-
Un message apparaît : « Deployment successful » 
2.8 - Tester dans le simulateur
Avant de tester vocalement, on vérifie que tout fonctionne dans le simulateur.
Navigation :
Cliquez sur l’onglet « Test » (en haut de la page)
Action :
- En haut à gauche, changez « Off » en « Development »
- Dans le champ texte en bas (qui dit « Type or click… »), tapez :
ouvre ma maison
-
Appuyez sur Entrée
-
Résultat attendu : Alexa répond avec la question stockée dans votre helper HA :
"Voulez-vous allumer la lumière de la véranda ?"
- Dans le champ texte, tapez maintenant :
oui
-
Appuyez sur Entrée
-
Résultat attendu :
-
Alexa répond : « D’accord, c’est fait »
-
Dans Home Assistant, le script script.alexa_reponse_oui devrait s’exécuter
-
Si ce script allume une lumière, elle devrait s’allumer !
Checkpoint : Partie 2 Terminée
Ce que vous avez maintenant :
Compte Amazon Developer créé
Skill Alexa « Contrôle Maison » créé (nouveau workflow en 4 pages)
Nom d’invocation configuré : « ma maison »
2 Intents créés et configurés : OuiIntent, NonIntent
Modèle compilé (Build successful)
Code JavaScript dynamique déployé
Skill testé avec succès dans le simulateur
Le skill lit maintenant dynamiquement :
-
La question depuis input_text.alexa_question
-
Le script OUI depuis input_text.alexa_script_oui
-
Le script NON depuis input_text.alexa_script_non
Prochaine étape
Partie 3 - Configuration finale et automatisation (à venir)
Dans la prochaine partie, nous verrons comment :
-
Exposer le helper alexa_lancer_skill à Alexa
-
Créer la routine Alexa pour le déclenchement automatique
-
Créer vos premières automatisations Home Assistant
-
Tester le workflow complet de bout en bout
Auteur : Daniel
Date : Février 2026
Forum : Home Assistant Community France (HACF)
Licence : Partagé librement avec la communauté 