192 lines
6.4 KiB
JavaScript
192 lines
6.4 KiB
JavaScript
const Alexa = require('ask-sdk-core');
|
|
const https = require('https');
|
|
|
|
// ---- Konfiguration ----
|
|
// Werte kommen aus der .env-Datei im selben Verzeichnis (siehe unten)
|
|
require('dotenv').config();
|
|
const HA_URL = process.env.HA_URL; // z.B. "https://haos.vogt.de.com"
|
|
const HA_TOKEN = process.env.HA_TOKEN; // Long-Lived Access Token aus HA
|
|
|
|
// Raum-ID (aus dem RoomList Slot) -> climate Entity-ID in Home Assistant
|
|
const roomToClimateEntity = {
|
|
badezimmer: 'climate.badezimmer_heizungssteuerung',
|
|
kinderzimmer: 'climate.kinderzimmer_heizungssteuerung',
|
|
kueche: 'climate.kueche_heizungssteuerung',
|
|
schlafzimmer: 'climate.schlafzimmer_heizungssteuerung',
|
|
wohnzimmer: 'climate.wohnzimmer_heizungssteuerung',
|
|
// diele, gaeste_wc, balkon_hinten, balkon_vorne, kammer: kein Sensor vorhanden
|
|
};
|
|
|
|
// Hilfsfunktion: HA REST API abfragen
|
|
function getHaState(entityId) {
|
|
return new Promise((resolve, reject) => {
|
|
const url = new URL(`${HA_URL}/api/states/${entityId}`);
|
|
const options = {
|
|
hostname: url.hostname,
|
|
path: url.pathname,
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${HA_TOKEN}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', (chunk) => (data += chunk));
|
|
res.on('end', () => {
|
|
if (res.statusCode !== 200) {
|
|
reject(new Error(`HA API Fehler: ${res.statusCode} - ${data}`));
|
|
return;
|
|
}
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
const LaunchRequestHandler = {
|
|
canHandle(handlerInput) {
|
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
|
|
},
|
|
handle(handlerInput) {
|
|
const speakOutput = 'Willkommen beim Hausassistent. Du kannst mich zum Beispiel fragen, wie warm es in einem Raum ist.';
|
|
return handlerInput.responseBuilder
|
|
.speak(speakOutput)
|
|
.reprompt(speakOutput)
|
|
.getResponse();
|
|
},
|
|
};
|
|
|
|
const GetTemperatureIntentHandler = {
|
|
canHandle(handlerInput) {
|
|
return (
|
|
Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
|
|
Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetTemperatureIntent'
|
|
);
|
|
},
|
|
async handle(handlerInput) {
|
|
const slots = handlerInput.requestEnvelope.request.intent.slots;
|
|
const roomSlot = slots.Room;
|
|
|
|
// Die ID und den sauberen Namen aus dem Slot Type RoomList holen
|
|
// (nicht den roh gesprochenen Text, der z.B. "wohnzimmer ist" statt "wohnzimmer" sein kann)
|
|
const resolvedValue = roomSlot?.resolutions?.resolutionsPerAuthority?.[0]?.values?.[0]?.value;
|
|
const roomId = resolvedValue?.id;
|
|
const roomNameSpoken = resolvedValue?.name || roomSlot?.value || 'diesem Raum';
|
|
|
|
if (!roomId) {
|
|
return handlerInput.responseBuilder
|
|
.speak(`Ich kenne den Raum ${roomNameSpoken} leider nicht. Bitte versuch es mit einem anderen Raumnamen.`)
|
|
.reprompt('Für welchen Raum möchtest du die Temperatur wissen?')
|
|
.getResponse();
|
|
}
|
|
|
|
const entityId = roomToClimateEntity[roomId];
|
|
|
|
if (!entityId) {
|
|
return handlerInput.responseBuilder
|
|
.speak(`Für ${roomNameSpoken} habe ich leider keinen Temperatursensor.`)
|
|
.getResponse();
|
|
}
|
|
|
|
try {
|
|
const state = await getHaState(entityId);
|
|
const currentTemp = state.attributes?.current_temperature;
|
|
|
|
if (currentTemp === undefined || currentTemp === null) {
|
|
return handlerInput.responseBuilder
|
|
.speak(`Ich konnte die aktuelle Temperatur für ${roomNameSpoken} nicht auslesen.`)
|
|
.getResponse();
|
|
}
|
|
|
|
const speakOutput = `Die Temperatur im ${roomNameSpoken} beträgt ${currentTemp} Grad.`;
|
|
return handlerInput.responseBuilder.speak(speakOutput).getResponse();
|
|
} catch (err) {
|
|
console.error('HA API Fehler:', err);
|
|
return handlerInput.responseBuilder
|
|
.speak('Ich konnte gerade keine Verbindung zu Home Assistant herstellen. Bitte versuch es später noch einmal.')
|
|
.getResponse();
|
|
}
|
|
},
|
|
};
|
|
|
|
const HelpIntentHandler = {
|
|
canHandle(handlerInput) {
|
|
return (
|
|
Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
|
|
Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent'
|
|
);
|
|
},
|
|
handle(handlerInput) {
|
|
const speakOutput = 'Du kannst mich zum Beispiel fragen: wie warm ist es im Wohnzimmer.';
|
|
return handlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).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) {
|
|
const speakOutput = 'Bis bald!';
|
|
return handlerInput.responseBuilder.speak(speakOutput).getResponse();
|
|
},
|
|
};
|
|
|
|
const FallbackIntentHandler = {
|
|
canHandle(handlerInput) {
|
|
return (
|
|
Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
|
|
Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent'
|
|
);
|
|
},
|
|
handle(handlerInput) {
|
|
const speakOutput = 'Das habe ich nicht verstanden. Du kannst mich zum Beispiel fragen, wie warm es im Wohnzimmer ist.';
|
|
return handlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();
|
|
},
|
|
};
|
|
|
|
const SessionEndedRequestHandler = {
|
|
canHandle(handlerInput) {
|
|
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
|
|
},
|
|
handle(handlerInput) {
|
|
return handlerInput.responseBuilder.getResponse();
|
|
},
|
|
};
|
|
|
|
const ErrorHandler = {
|
|
canHandle() {
|
|
return true;
|
|
},
|
|
handle(handlerInput, error) {
|
|
console.error(`~~~~ Error handled: ${error.stack}`);
|
|
const speakOutput = 'Entschuldigung, da ist etwas schiefgelaufen. Bitte versuch es noch einmal.';
|
|
return handlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();
|
|
},
|
|
};
|
|
|
|
exports.handler = Alexa.SkillBuilders.custom()
|
|
.addRequestHandlers(
|
|
LaunchRequestHandler,
|
|
GetTemperatureIntentHandler,
|
|
HelpIntentHandler,
|
|
CancelAndStopIntentHandler,
|
|
FallbackIntentHandler,
|
|
SessionEndedRequestHandler
|
|
)
|
|
.addErrorHandlers(ErrorHandler)
|
|
.lambda();
|