Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 'use strict';
- var WebSocket = require('ws');
- var request = require('request');
- var uuid = require('uuid-v4');
- /**
- * This sample demonstrates a smart home skill using the publicly available API on Amazon's Alexa platform.
- * For more information about developing smart home skills, see
- * https://developer.amazon.com/alexa/smart-home
- *
- * For details on the smart home API, please visit
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference
- */
- const USER_DEVICES = [
- {
- // This id needs to be unique across all devices discovered for a given manufacturer
- endpointId: '119600650576',
- // The name given by the user in your application. Examples include 'Bedroom light' etc
- friendlyName: 'K�chen Thermostat',
- // Should describe the device type and the company/cloud provider.
- // This value will be shown in the Alexa app
- description: 'Thermostat f�r Fritz!Box',
- // Company name that produces and sells the smart home device
- manufacturerName: 'Eurotronic',
- displayCategories: ['THERMOSTAT', 'TEMPERATURE_SENSOR'],
- // not used at this time
- cookie: {
- extraDetail1: 'a',
- extraDetail2: 'b',
- extraDetail3: 'c',
- extraDetail4: 'd',
- },
- capabilities: [
- {
- "type": "AlexaInterface",
- "interface": "Alexa.EndpointHealth",
- "version": "3",
- "properties": {
- "supported": [
- {
- "name": "connectivity"
- }
- ],
- "proactivelyReported": true,
- "retrievable": true
- }
- },
- {
- "type": "AlexaInterface",
- "interface": "Alexa.PowerController",
- "version": "3",
- "properties": {
- "supported": [
- {
- "name": "powerState"
- }
- ],
- "proactivelyReported": true,
- "retrievable": true
- }
- },
- {
- "type": "AlexaInterface",
- "interface": "Alexa.ThermostatController",
- "version": "3",
- "properties": {
- "supported": [
- {
- "name": "targetSetpoint"
- }
- ],
- "proactivelyReported": true,
- "retrievable": true
- }
- },
- {
- "type": "AlexaInterface",
- "interface": "Alexa.TemperatureSensor",
- "version": "3",
- "properties": {
- "supported": [
- {
- "name": "temperature"
- }
- ],
- "proactivelyReported": true,
- "retrievable": true
- }
- }]
- }
- ];
- /**
- * Utility functions
- */
- function log(title, msg) {
- console.log(`[${title}] ${msg}`);
- }
- /**
- * Generate a unique message ID
- *
- * TODO: UUID v4 is recommended as a message ID in production.
- */
- function generateMessageID() {
- return uuid();
- }
- function generateTimeStamp() {
- var time = new Date();
- return time.toISOString();
- }
- /**
- * Generate a response message
- *
- * @param {Object} request - request.directive object
- * @param {string} eventType - eventType e.g. Response or StateReport
- * @param {Object} contextProperties - static array of values to report
- * @param {Object} payload - Any special payload required for the response
- * @returns {Object} Response object
- */
- function generateResponse(request, eventType, contextProperties, payload) {
- return {
- context: {
- properties: contextProperties
- },
- event: {
- header: {
- messageId: generateMessageID(),
- namespace: 'Alexa',
- name: eventType,
- correlationToken: request.header.correlationToken,
- payloadVersion: '3'
- },
- endpoint: {
- scope: {
- type: 'BearerToken',
- token: request.endpoint.scope.token
- },
- endpointId: request.endpoint.endpointId
- },
- payload: payload
- }
- };
- }
- /**
- * Function to access device cloud.
- */
- function getDevicesFromPartnerCloud() {
- return USER_DEVICES;
- }
- function transmitCommandToPi(cmdJson) {
- var socket;
- console.log("Send Cmd to FritzBox: " + cmdJson);
- return new Promise((resolve, reject) => {
- socket = new WebSocket('http://' + 'wkyg3jni0whrfyk6.myfritz.net' + ':9090', function (error) {
- console.log(new Error(error).toString());
- reject("SocketError");
- return;
- });
- socket.on('error', function (e) {
- console.log('Error in WebSocket communication');
- socket.close();
- reject("SocketError");
- return;
- });
- socket.on('message', function (msg) {
- console.log("incoming message: ", msg);
- if (msg == "[REQUEST]") {
- socket.send("[FRITZCMD]=" + cmdJson);
- }
- else if (msg.startsWith("[END]")) {
- let status = msg.substr(msg.indexOf('=') + 1);
- console.log("Cmd END received with status: " + status + ". Shut down socket!");
- socket.close();
- resolve(status);
- }
- });
- });
- }
- //request, name, value, payload
- function turnThermostatOn(request, applianceId) {
- log('DEBUG', `turnOn (applianceId: ${applianceId})`);
- var fritzCmd = { cmd: "turnOn", id: applianceId };
- return new Promise((resolve, reject) => {
- transmitCommandToPi(JSON.stringify(fritzCmd)).then((successMsg, error) => {
- if (error) {
- const errorMessage = `Leider ist bei der �bermittlung des Fritz!Box Befehls ein Problem aufgetreten!`;
- log('ERROR', errorMessage);
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- }
- else {
- var resultJSON = JSON.parse(successMsg);
- console.log(successMsg);
- if (resultJSON.result == "error") {
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- var contextProperties = [{
- namespace: request.header.namespace,
- name: "powerState",
- value: "ON",
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }];
- resolve(generateResponse(request, "Response", contextProperties, {}));
- }
- });
- });
- }
- function turnThermostatOff(request, applianceId) {
- log('DEBUG', `turnOff (applianceId: ${applianceId})`);
- var fritzCmd = { cmd: "turnOff", id: applianceId };
- return new Promise((resolve, reject) => {
- transmitCommandToPi(JSON.stringify(fritzCmd)).then((successMsg, error) => {
- if (error) {
- const errorMessage = `Leider ist bei der �bermittlung des Fritz!Box Befehls ein Problem aufgetreten!`;
- log('ERROR', errorMessage);
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- }
- else {
- var resultJSON = JSON.parse(successMsg);
- console.log(successMsg);
- if (resultJSON.result == "error") {
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- var contextProperties = [{
- namespace: request.header.namespace,
- name: "powerState",
- value: "OFF",
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }];
- resolve(generateResponse(request, "Response", contextProperties, {}));
- }
- });
- });
- }
- function queryThermostatTemp(request, applianceId) {
- log('DEBUG', `Query Temp (applianceId: ${applianceId})`);
- var fritzCmd = { cmd: "getStats", id: applianceId };
- return new Promise((resolve, reject) => {
- transmitCommandToPi(JSON.stringify(fritzCmd)).then((successMsg, error) => {
- if (error) {
- const errorMessage = `Leider ist bei der �bermittlung des Fritz!Box Befehls ein Problem aufgetreten!`;
- log('ERROR', errorMessage);
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- }
- else {
- var resultJSON = JSON.parse(successMsg);
- console.log(successMsg);
- var contextProperties;
- if (resultJSON.result == "error")
- contextProperties = [
- {
- namespace: "Alexa.EndpointHealth",
- name: "connectivity",
- value: "UNREACHABLE", //OK or UNREACHABLE
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- },
- {
- namespace: "Alexa.PowerController",
- name: "powerState",
- value: "OFF", //ON or OFF
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }
- ];
- else {
- contextProperties = [
- {
- namespace: "Alexa.EndpointHealth",
- name: "connectivity",
- value: "OK", //OK or UNREACHABLE
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- },
- {
- namespace: "Alexa.PowerController",
- name: "powerState",
- value: "ON", //ON or OFF
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- },
- {
- namespace: "Alexa.TemperatureSensor",
- name: "temperature",
- value: { value: resultJSON.temperature, scale: "CELSIUS" },
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- },
- {
- namespace: "Alexa.ThermostatController",
- name: "targetSetpoint",
- value: { value: resultJSON.targetSetpoint, scale: "CELSIUS" },
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }
- ];
- }
- resolve(generateResponse(request, "StateReport", contextProperties, {}));
- }
- });
- });
- }
- function adjustTargetTemp(request, applianceId) {
- log('DEBUG', `adjustTargetTemp (applianceId: ${applianceId}, adjustTemp: ${request.payload.targetSetpointDelta.value})`);
- var adjustTemp = request.payload.targetSetpointDelta.value;
- var fritzCmd = { cmd: "adjustTemp", id: applianceId, delta: adjustTemp};
- return new Promise((resolve, reject) => {
- transmitCommandToPi(JSON.stringify(fritzCmd)).then((successMsg, error) => {
- if (error) {
- const errorMessage = `Leider ist bei der �bermittlung des Fritz!Box Befehls ein Problem aufgetreten!`;
- log('ERROR', errorMessage);
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- }
- else {
- var resultJSON = JSON.parse(successMsg);
- console.log(successMsg);
- if (resultJSON.result == "error") {
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- var contextProperties = [{
- namespace: request.header.namespace,
- name: "targetSetpoint",
- value: {
- value: resultJSON.target, scale: "CELSIUS"
- },
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }];
- resolve(generateResponse(request, "Response", contextProperties, {}));
- }
- });
- });
- }
- function setTargetTemp(request, applianceId) {
- log('DEBUG', `setTargetTemp (applianceId: ${applianceId}, targetTemp: ${request.payload.targetSetpoint.value})`);
- var targetTemp = request.payload.targetSetpoint.value;
- var fritzCmd = { cmd: "setTemp", id: applianceId, target: targetTemp };
- return new Promise((resolve, reject) => {
- transmitCommandToPi(JSON.stringify(fritzCmd)).then((successMsg, error) => {
- if (error) {
- const errorMessage = `Leider ist bei der �bermittlung des Fritz!Box Befehls ein Problem aufgetreten!`;
- log('ERROR', errorMessage);
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- }
- else {
- var resultJSON = JSON.parse(successMsg);
- console.log(successMsg);
- if (resultJSON.result == "error") {
- reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- var contextProperties = [{
- namespace: request.header.namespace,
- name: "targetSetpoint",
- value: {
- value: resultJSON.target, scale: "CELSIUS"
- },
- timeOfSample: generateTimeStamp(),
- uncertaintyInMilliseconds: 900000 //15 minutes uncertainy due fritz box polling every 15 minutes
- }];
- resolve(generateResponse(request, "Response", contextProperties, {}));
- }
- });
- });
- }
- /**
- * Main logic
- */
- /**
- * This function is invoked when we receive a "Discovery" message from Alexa Smart Home Skill.
- * We are expected to respond back with a list of appliances that we have discovered for a given customer.
- *
- * @param {Object} request - The full request object from the Alexa smart home service. This represents a DiscoverAppliancesRequest.
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesrequest
- *
- * @param {function} callback - The callback object on which to succeed or fail the response.
- * https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
- * If successful, return <DiscoverAppliancesResponse>.
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesresponse
- */
- function handleAuth(request, callback) {
- console.log("User Tokens for Message Gateway: " + request);
- const response = {
- event: {
- header: {
- messageId: generateMessageID(),
- name: 'AcceptGrant.Response',
- namespace: 'Alexa.Authorization',
- payloadVersion: '3',
- },
- payload: {
- },
- }
- };
- callback(null, response);
- }
- function handleDiscovery(request, callback) {
- log('DEBUG', `Discovery Request: ${JSON.stringify(request)}`);
- /**
- * Get the OAuth token from the request.
- */
- const userAccessToken = request.payload.scope.token.trim();
- if (!userAccessToken) {
- const errorMessage = `Discovery Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`;
- log('ERROR', errorMessage);
- callback(new Error(errorMessage));
- }
- /**
- * Assume access token is valid at this point.
- * Retrieve list of devices from cloud based on token.
- *
- * For more information on a discovery response see
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesresponse
- */
- const response = {
- event: {
- header: {
- messageId: generateMessageID(),
- name: 'Discover.Response',
- namespace: 'Alexa.Discovery',
- payloadVersion: '3',
- },
- payload: {
- endpoints: getDevicesFromPartnerCloud(userAccessToken),
- },
- }
- };
- /**
- * Log the response. These messages will be stored in CloudWatch.
- */
- log('DEBUG', `Discovery Response: ${JSON.stringify(response)}`);
- /**
- * Return result with successful message.
- */
- callback(null, response);
- }
- /**
- * A function to handle power control events.
- * This is called when Alexa requests an action such as turning off an appliance.
- *
- * @param {Object} request - The full request object from the Alexa smart home service.
- * @param {function} callback - The callback object on which to succeed or fail the response.
- */
- function handlePowerControl(request, callback) {
- log('DEBUG', `Control Request: ${JSON.stringify(request)}`);
- /**
- * Get the access token.
- */
- const userAccessToken = request.endpoint.scope.token.trim();
- if (!userAccessToken) {
- log('ERROR', `Power Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`);
- callback(null, generateResponse(request, 'InvalidAccessTokenError', {}, {}));
- return;
- }
- /**
- * Grab the applianceId from the request.
- */
- const applianceId = request.endpoint.endpointId;
- /**
- * If the applianceId is missing, return UnexpectedInformationReceivedError
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#unexpectedinformationreceivederror
- */
- if (!applianceId) {
- log('ERROR', 'No applianceId provided in request');
- const payload = { faultingParameter: `applianceId: ${applianceId}` };
- callback(null, generateResponse(request, 'UnexpectedInformationReceivedError', {}, payload));
- return;
- }
- /**
- * At this point the applianceId and accessToken are present in the request.
- *
- */
- let response;
- switch (request.header.name) {
- case 'TurnOn': {
- turnThermostatOn(request, applianceId).then((successMsg, error) => {
- if (error)
- response = error;
- else
- response = successMsg;
- log('DEBUG', `Control Confirmation: ${JSON.stringify(response)}`);
- callback(null, response);
- });
- break;
- }
- case 'TurnOff': {
- turnThermostatOff(request, applianceId).then((successMsg, error) => {
- if (error)
- response = error;
- else
- response = successMsg;
- log('DEBUG', `Control Confirmation: ${JSON.stringify(response)}`);
- callback(null, response);
- });
- break;
- }
- default: {
- log('ERROR', `No supported directive event name: ${request.header.name}`);
- callback(null, generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- }
- }
- function handleQuery(request, callback) {
- log('DEBUG', `Query Request: ${JSON.stringify(request)}`);
- /**
- * Get the access token.
- */
- const userAccessToken = request.endpoint.scope.token.trim();
- if (!userAccessToken) {
- log('ERROR', `Query Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`);
- callback(null, generateResponse(request, 'InvalidAccessTokenError', {}, {}));
- return;
- }
- /**
- * Grab the applianceId from the request.
- */
- const applianceId = request.endpoint.endpointId;
- /**
- * If the applianceId is missing, return UnexpectedInformationReceivedError
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#unexpectedinformationreceivederror
- */
- if (!applianceId) {
- log('ERROR', 'No applianceId provided in request');
- const payload = { faultingParameter: `applianceId: ${applianceId}` };
- callback(null, generateResponse(request, 'UnexpectedInformationReceivedError', {}, payload));
- return;
- }
- let response;
- switch (request.header.name) {
- case 'ReportState': {
- queryThermostatTemp(request, applianceId).then((successMsg, error) => {
- if (error)
- response = error;
- else
- response = successMsg;
- log('DEBUG', `Query Confirmation: ${JSON.stringify(response)}`);
- callback(null, response);
- });
- break;
- }
- default: {
- log('ERROR', `No supported directive event name: ${request.header.name}`);
- callback(null, generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- }
- }
- function handleThermostatControl(request, callback) {
- log('DEBUG', `SetThermostat Request: ${JSON.stringify(request)}`);
- /**
- * Get the access token.
- */
- const userAccessToken = request.endpoint.scope.token.trim();
- if (!userAccessToken) {
- log('ERROR', `SetThermostat Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`);
- callback(null, generateResponse(request, 'InvalidAccessTokenError', {}, {}));
- return;
- }
- /**
- * Grab the applianceId from the request.
- */
- const applianceId = request.endpoint.endpointId;
- /**
- * If the applianceId is missing, return UnexpectedInformationReceivedError
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#unexpectedinformationreceivederror
- */
- if (!applianceId) {
- log('ERROR', 'No applianceId provided in request');
- const payload = { faultingParameter: `applianceId: ${applianceId}` };
- callback(null, generateResponse(request, 'UnexpectedInformationReceivedError', {}, payload));
- return;
- }
- /**
- * At this point the applianceId and accessToken are present in the request.
- *
- */
- let response;
- switch (request.header.name) {
- case 'SetTargetTemperature': {
- setTargetTemp(request, applianceId).then((successMsg, error) => {
- if (error)
- response = error;
- else
- response = successMsg;
- log('DEBUG', `SetThermostat Confirmation: ${JSON.stringify(response)}`);
- callback(null, response);
- });
- break;
- }
- case 'AdjustTargetTemperature': {
- adjustTargetTemp(request, applianceId).then((successMsg, error) => {
- if (error)
- response = error;
- else
- response = successMsg;
- log('DEBUG', `SetThermostat Confirmation: ${JSON.stringify(response)}`);
- callback(null, response);
- });
- break;
- }
- default: {
- log('ERROR', `No supported directive event name: ${request.header.name}`);
- callback(null, generateResponse(request, 'UnsupportedOperationError', {}, {}));
- return;
- }
- }
- }
- /**
- * Main entry point.
- * Incoming events from Alexa service through Smart Home API are all handled by this function.
- *
- * It is recommended to validate the request and response with Alexa Smart Home Skill API Validation package.
- * https://github.com/alexa/alexa-smarthome-validation
- */
- exports.handler = (request, context, callback) => {
- console.log(request);
- switch (request.directive.header.namespace) {
- case 'Alexa.Authorization':
- handleAuth(request.directive, callback);
- break;
- /**
- * The namespace of 'Alexa.Discovery' indicates a request is being made to the Lambda for
- * discovering all appliances associated with the customer's appliance cloud account.
- *
- * For more information on device discovery, please see
- * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discovery-messages
- */
- case 'Alexa.Discovery':
- handleDiscovery(request.directive, callback);
- break;
- case 'Alexa.PowerController':
- handlePowerControl(request.directive, callback);
- break;
- case 'Alexa.ThermostatController':
- handleThermostatControl(request.directive, callback);
- break;
- //todo: raspberry keeps connection to fritzbox open and polls current temp and target setpoint every minute, saves it in a cache
- case 'Alexa':
- handleQuery(request.directive, callback);
- break;
- /**
- * Received an unexpected message
- */
- default: {
- const errorMessage = `No supported namespace: ${request.directive.header.namespace}`;
- log('ERROR', errorMessage);
- callback(new Error(errorMessage));
- }
- }
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement