From 4cc0fa01f3ab80d8433d451fe437a728aa4e42fb Mon Sep 17 00:00:00 2001 From: Oliver Vogt Date: Sat, 22 Aug 2026 21:48:07 +0200 Subject: [PATCH] =?UTF-8?q?index.js:=20GetPowerUsageIntent,=20GetEnergyCos?= =?UTF-8?q?tIntent,=20GetWashingMachineIntent,=20GetDryerIntent=20erg?= =?UTF-8?q?=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- home-assistant-skill/index.js | 230 ++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/home-assistant-skill/index.js b/home-assistant-skill/index.js index cbace5d..ff3cc1b 100644 --- a/home-assistant-skill/index.js +++ b/home-assistant-skill/index.js @@ -17,6 +17,26 @@ const roomToClimateEntity = { // diele, gaeste_wc, balkon_hinten, balkon_vorne, kammer: kein Sensor vorhanden }; +// Aktueller Stromverbrauch (Leistung in Watt) +const POWER_ENTITY = 'sensor.kammer_netzbezug_plus_keller_power_calc'; + +// Stromkosten je Zeitraum (Periode-ID aus dem PeriodList Slot -> Entity-ID) +const periodToEnergyCostEntity = { + tag: 'sensor.netzbezug_kosten_tag', + woche: 'sensor.netzbezug_kosten_woche', + monat: 'sensor.netzbezug_kosten_monat', +}; + +// Waschmaschine +const WASHING_MACHINE_STATUS_ENTITY = 'sensor.waschmaschine_betriebszustand'; +const WASHING_MACHINE_PROGRESS_ENTITY = 'sensor.waschmaschine_programm_fortschritt'; +const WASHING_MACHINE_END_TIME_ENTITY = 'sensor.waschmaschine_programm_endzeit'; + +// Trockner +const DRYER_STATUS_ENTITY = 'sensor.trockner_betriebszustand'; +const DRYER_PROGRESS_ENTITY = 'sensor.trockner_programm_fortschritt'; +const DRYER_END_TIME_ENTITY = 'sensor.trockner_programm_endzeit'; + // Hilfsfunktion: HA REST API abfragen function getHaState(entityId) { return new Promise((resolve, reject) => { @@ -118,6 +138,212 @@ const GetTemperatureIntentHandler = { }, }; +const GetPowerUsageIntentHandler = { + canHandle(handlerInput) { + return ( + Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' && + Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetPowerUsageIntent' + ); + }, + async handle(handlerInput) { + try { + const state = await getHaState(POWER_ENTITY); + const powerValue = state.state; + + if (powerValue === undefined || powerValue === null || powerValue === 'unknown' || powerValue === 'unavailable') { + return handlerInput.responseBuilder + .speak('Ich konnte den aktuellen Stromverbrauch nicht auslesen.') + .getResponse(); + } + + const unit = state.attributes?.unit_of_measurement || 'Watt'; + const roundedPower = Math.round(parseFloat(powerValue)); + const speakOutput = `Der aktuelle Stromverbrauch beträgt ${roundedPower} ${unit}.`; + 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 GetEnergyCostIntentHandler = { + canHandle(handlerInput) { + return ( + Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' && + Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetEnergyCostIntent' + ); + }, + async handle(handlerInput) { + const slots = handlerInput.requestEnvelope.request.intent.slots; + const periodSlot = slots.Period; + + const resolvedValue = periodSlot?.resolutions?.resolutionsPerAuthority?.[0]?.values?.[0]?.value; + const periodId = resolvedValue?.id; + const periodNameSpoken = resolvedValue?.name || periodSlot?.value || 'diesem Zeitraum'; + + if (!periodId) { + return handlerInput.responseBuilder + .speak('Ich habe den Zeitraum leider nicht verstanden. Du kannst zum Beispiel nach Tag, Woche oder Monat fragen.') + .reprompt('Für welchen Zeitraum möchtest du die Stromkosten wissen?') + .getResponse(); + } + + const entityId = periodToEnergyCostEntity[periodId]; + + if (!entityId) { + return handlerInput.responseBuilder + .speak(`Für ${periodNameSpoken} habe ich leider keine Kostenauswertung.`) + .getResponse(); + } + + try { + const state = await getHaState(entityId); + const costValue = state.state; + + if (costValue === undefined || costValue === null || costValue === 'unknown' || costValue === 'unavailable') { + return handlerInput.responseBuilder + .speak(`Ich konnte die Stromkosten für ${periodNameSpoken} nicht auslesen.`) + .getResponse(); + } + + const unit = state.attributes?.unit_of_measurement || 'Euro'; + const roundedCost = parseFloat(costValue).toFixed(2).replace('.', ','); + const speakOutput = `Die Stromkosten für diesen ${periodNameSpoken} betragen ${roundedCost} ${unit}.`; + 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 GetWashingMachineIntentHandler = { + canHandle(handlerInput) { + return ( + Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' && + Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetWashingMachineIntent' + ); + }, + async handle(handlerInput) { + try { + const [statusState, progressState, endTimeState] = await Promise.all([ + getHaState(WASHING_MACHINE_STATUS_ENTITY).catch(() => null), // optional, falls Entity nicht existiert + getHaState(WASHING_MACHINE_PROGRESS_ENTITY), + getHaState(WASHING_MACHINE_END_TIME_ENTITY), + ]); + + // Betriebszustand prüfen (z.B. "fertig", "inaktiv") - analog zum HA-Template + const betrieb = statusState?.state?.toLowerCase().trim(); + const inactiveStates = ['fertig', 'inaktiv', 'finished', 'inactive']; + if (betrieb && inactiveStates.includes(betrieb)) { + return handlerInput.responseBuilder + .speak('Die Waschmaschine läuft aktuell nicht.') + .getResponse(); + } + + const progress = progressState.state; + const endTimeRaw = endTimeState.state; + const unknownValues = ['unknown', 'unavailable', 'none']; + + if (unknownValues.includes(progress) || unknownValues.includes(endTimeRaw)) { + return handlerInput.responseBuilder + .speak('Die Waschmaschine läuft aktuell nicht.') + .getResponse(); + } + + const endTime = new Date(endTimeRaw); + const now = new Date(); + const diffMs = endTime.getTime() - now.getTime(); + + if (diffMs <= 0) { + return handlerInput.responseBuilder.speak('Die Waschmaschine ist fertig.').getResponse(); + } + + const roundedProgress = Math.round(parseFloat(progress)); + + // Endzeit als HH:MM in deutscher Zeitzone formatieren + const timeFormatted = new Intl.DateTimeFormat('de-DE', { + hour: '2-digit', + minute: '2-digit', + timeZone: 'Europe/Berlin', + }).format(endTime); + + const speakOutput = `Die Waschmaschine ist ${roundedProgress} Prozent fertig und endet um ${timeFormatted} Uhr.`; + 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 GetDryerIntentHandler = { + canHandle(handlerInput) { + return ( + Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' && + Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetDryerIntent' + ); + }, + async handle(handlerInput) { + try { + const [statusState, progressState, endTimeState] = await Promise.all([ + getHaState(DRYER_STATUS_ENTITY).catch(() => null), + getHaState(DRYER_PROGRESS_ENTITY), + getHaState(DRYER_END_TIME_ENTITY), + ]); + + const betrieb = statusState?.state?.toLowerCase().trim(); + const inactiveStates = ['fertig', 'inaktiv', 'finished', 'inactive']; + if (betrieb && inactiveStates.includes(betrieb)) { + return handlerInput.responseBuilder + .speak('Der Trockner läuft aktuell nicht.') + .getResponse(); + } + + const progress = progressState.state; + const endTimeRaw = endTimeState.state; + const unknownValues = ['unknown', 'unavailable', 'none']; + + if (unknownValues.includes(progress) || unknownValues.includes(endTimeRaw)) { + return handlerInput.responseBuilder + .speak('Der Trockner läuft aktuell nicht.') + .getResponse(); + } + + const endTime = new Date(endTimeRaw); + const now = new Date(); + const diffMs = endTime.getTime() - now.getTime(); + + if (diffMs <= 0) { + return handlerInput.responseBuilder.speak('Der Trockner ist fertig.').getResponse(); + } + + const roundedProgress = Math.round(parseFloat(progress)); + + const timeFormatted = new Intl.DateTimeFormat('de-DE', { + hour: '2-digit', + minute: '2-digit', + timeZone: 'Europe/Berlin', + }).format(endTime); + + const speakOutput = `Der Trockner ist ${roundedProgress} Prozent fertig und endet um ${timeFormatted} Uhr.`; + 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 ( @@ -182,6 +408,10 @@ exports.handler = Alexa.SkillBuilders.custom() .addRequestHandlers( LaunchRequestHandler, GetTemperatureIntentHandler, + GetPowerUsageIntentHandler, + GetEnergyCostIntentHandler, + GetWashingMachineIntentHandler, + GetDryerIntentHandler, HelpIntentHandler, CancelAndStopIntentHandler, FallbackIntentHandler,