index.js: GetPowerUsageIntent, GetEnergyCostIntent, GetWashingMachineIntent, GetDryerIntent ergänzt
This commit is contained in:
@@ -17,6 +17,26 @@ const roomToClimateEntity = {
|
|||||||
// diele, gaeste_wc, balkon_hinten, balkon_vorne, kammer: kein Sensor vorhanden
|
// 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
|
// Hilfsfunktion: HA REST API abfragen
|
||||||
function getHaState(entityId) {
|
function getHaState(entityId) {
|
||||||
return new Promise((resolve, reject) => {
|
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 = {
|
const HelpIntentHandler = {
|
||||||
canHandle(handlerInput) {
|
canHandle(handlerInput) {
|
||||||
return (
|
return (
|
||||||
@@ -182,6 +408,10 @@ exports.handler = Alexa.SkillBuilders.custom()
|
|||||||
.addRequestHandlers(
|
.addRequestHandlers(
|
||||||
LaunchRequestHandler,
|
LaunchRequestHandler,
|
||||||
GetTemperatureIntentHandler,
|
GetTemperatureIntentHandler,
|
||||||
|
GetPowerUsageIntentHandler,
|
||||||
|
GetEnergyCostIntentHandler,
|
||||||
|
GetWashingMachineIntentHandler,
|
||||||
|
GetDryerIntentHandler,
|
||||||
HelpIntentHandler,
|
HelpIntentHandler,
|
||||||
CancelAndStopIntentHandler,
|
CancelAndStopIntentHandler,
|
||||||
FallbackIntentHandler,
|
FallbackIntentHandler,
|
||||||
|
|||||||
Reference in New Issue
Block a user