From 946f1950780a37ab8c7fcad44c4d6bfa885d5654 Mon Sep 17 00:00:00 2001 From: Oliver Vogt Date: Sat, 22 Aug 2026 18:51:02 +0200 Subject: [PATCH] =?UTF-8?q?Home=20Assistant=20Skill:=20index.js=20hinzuf?= =?UTF-8?q?=C3=BCgen=20(GetTemperatureIntent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- home-assistant-skill/index.js | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 home-assistant-skill/index.js diff --git a/home-assistant-skill/index.js b/home-assistant-skill/index.js new file mode 100644 index 0000000..cbace5d --- /dev/null +++ b/home-assistant-skill/index.js @@ -0,0 +1,191 @@ +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();