var https = require('https');var serialPortControllerId = "SAKA_Krell_10102017";var serialPortEchoId = "SAKA_Krell_Echo_10102017"var particleServer = "api.particle.io";var particlePath = "/v1/devices/";var particleId = "put_here_your_particle_device_id";var powerOn = "1PWRZ";var powerOff = "0PWRZ";var volumeUp = "V10UZ"; //up by 10var volumeDown = "V10DZ"; //down by 10var setVolumeToXXXLevel = "MVLZ"; //append in front volume level xxxMVLZvar muteEnable = "MUTZ";var muteDisable = "UMTZ";var selectInput1 = "SS1MZ";var selectInput2 = "SS2MZ";var selectInput3 = "SS3MZ";exports.handler = function(event, context) { log('Input', event); switch (event.directive.header.namespace) { /** * The namespace of "Discovery" indicates a request is being made to the lambda for * discovering all appliances associated with the customer's appliance cloud account. * can use the accessToken that is made available as part of the payload to determine * the customer. */ case 'Alexa.Discovery': handleDiscovery(event, context); break; case 'Alexa.PowerController': handleControl(event, context); break; case 'Alexa.Speaker': handleControl(event, context); break; case 'Alexa.InputController': handleControl(event, context); break; case 'Alexa.ChannelController': handleControl(event, context); break; default: log('Err', 'No supported namespace: ' + event.directive.header.namespace); context.fail('Something went wrong'); break; }};/*** This method is invoked when we receive a "Discovery" message from Alexa Connected Home Skill.* We are expected to respond back with a list of appliances that we have discovered for a given* customer. */function handleDiscovery(event, context) { var payload = { "endpoints":[ { "endpointId": serialPortControllerId, "friendlyName": "Stereo", "description": "Serial port controller for Krell amplifier S-300i", "manufacturerName": "SAKA", "displayCategories": ["OTHER"], "cookie": { "deviceId" : particleId }, "capabilities": [ { "type": "AlexaInterface", "interface": "Alexa.Speaker", "version": "1.0", "properties":{ "supported": [ { "name": "volume" }, { "name": "muted" } ] } }, { "type": "AlexaInterface", "interface": "Alexa.PowerController", "version": "1.0", "properties": { "supported" : [ { "name": "powerState" } ] } }, { "type": "AlexaInterface", "interface": "Alexa.InputController", "version": "1.0", "properties": { "supported": [ { "name": "input" } ] } }, { "type": "AlexaInterface", "interface": "Alexa.ChannelController", "version": "1.0", "properties": { "supported": [ { "name": "channel" } ] } } ]}, ]}; /** * Craft the final response back to Alexa Connected Home Skill. This will include all the * discoverd appliances. */ var header = event.directive.header; header.name = "Discover.Response"; context.succeed({ event: { header: header, payload: payload } }); //log('Discovery', result);}/** * Control events are processed here. * This is called when Alexa requests an action (IE turn off appliance). */function handleControl(event, context) { var namespace = event.directive.header.namespace; var messageId = event.directive.header.messageId; var correlationToken = event.directive.header.correlationToken; var accessToken = event.directive.endpoint.scope.token; var endpointId = event.directive.endpoint.endpointId; var deviceId = event.directive.endpoint.cookie.deviceId; var name = ""; var value = ""; var UTCDate = new Date(); var jsonDate = JSON.stringify(UTCDate); var strDate = JSON.parse(jsonDate); var funcName = "KrellSerial"; if ((namespace === "Alexa.PowerController") || (namespace === "Alexa.Speaker") || (namespace === "Alexa.InputController") || (namespace === "Alexa.ChannelController")){ var param = ""; var index = "0"; var executeParticle = true; if (namespace === "Alexa.PowerController") { if(event.directive.header.name == "TurnOn"){ krellSerialCommand = powerOn; name = "powerState"; value = "ON"; } else if(event.directive.header.name == "TurnOff"){ krellSerialCommand = powerOff; name = "powerState"; value = "OFF"; } } else if (namespace === "Alexa.Speaker") { if(event.directive.header.name == "SetVolume"){ value = event.directive.payload.volume; krellSerialCommand = pad(value, 3) + setVolumeToXXXLevel; name = "volume"; } else if(event.directive.header.name == "SetMute"){ var mute = event.directive.payload.mute; if (mute) { krellSerialCommand = muteEnable; value = true;} else if (!mute) { krellSerialCommand = muteDisable; value = false;} name = "muted"; } else if(event.directive.header.name == "AdjustVolume"){ var volume = event.directive.payload.volume; if (volume >= 0) { krellSerialCommand = volumeUp; value = 10;} else if (volume << 0) { krellSerialCommand = volumeDown; value = -10;} name = "volume"; } } else if (namespace === "Alexa.InputController") { if(event.directive.header.name == "SelectInput"){ var inputNumber = event.directive.payload.input; if (inputNumber == "1") { krellSerialCommand = selectInput1; } else if (inputNumber == "2") { krellSerialCommand = selectInput2; } else if (inputNumber == "3") { krellSerialCommand = selectInput3; } else { krellSerialCommand = inputNumber; executeParticle = false; context.succeed(generateControlError(event, 'INVALID_VALUE', 'Input name or number not recognized')); } name = "input"; } } else if (namespace === "Alexa.ChannelController") { if(event.directive.header.name == "ChangeChannel"){ var channelNumber = event.directive.payload.channel.number; value = event.directive.payload.channel; if (channelNumber == "1") { krellSerialCommand = selectInput1; } else if (channelNumber == "2") { krellSerialCommand = selectInput2; } else if (channelNumber == "3") { krellSerialCommand = selectInput3; } else { krellSerialCommand = channelNumber; executeParticle = false; context.succeed(generateControlError(event, 'INVALID_VALUE', 'Input name or number not recognized')); } name = "channel"; } } if(deviceId == particleId){ index = "0"; } param = krellSerialCommand; var options = { hostname: particleServer, port: 443, path: particlePath + deviceId + "/" + funcName, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; log(options); var data = "access_token=" + accessToken + "&" + "args=" + param; log(data); var serverError = function (e) { log('Error', e.message); context.fail(generateControlError(event, 'ENDPOINT_UNREACHABLE', 'Unable to connect to server')); }; if (executeParticle) { var callback = function(response) { var str = ''; response.on('data', function(chunk) { str += chunk.toString('utf-8'); }); response.on('end', function() { log('Return Value'); log(str); var result = { "context": { "properties": [ { "namespace": namespace, "name": name, "value": value, "timeOfSample": strDate, "uncertaintyInMilliseconds": 500 } ] }, "event": { "header": { "namespace": "Alexa", "name": "Response", "payloadVersion": "3", "messageId": messageId, "correlationToken": correlationToken }, "endpoint": { "scope": { "type": "BearerToken", "token": accessToken }, "endpointId": endpointId }, "payload": {} } }; context.succeed(result); }); response.on('error', serverError); }; var req = https.request(options, callback); req.on('error', serverError); req.write(data); req.end(); } }}/** * Utility functions. */function log(title, msg) { console.log(title + ": " + msg);}function generateControlError(event, code, description) { var messageId = event.directive.header.messageId; var correlationToken = event.directive.header.correlationToken; var accessToken = event.directive.endpoint.scope.token; var endpointId = event.directive.endpoint.endpointId; var response = { "header": { "namespace": "Alexa", "name": "ErrorResponse", "messageId": messageId, "correlationToken": correlationToken, "payloadVersion": "3" }, "endpoint":{ "scope":{ "type":"BearerToken", "token":accessToken }, "endpointId":endpointId }, "payload": { "type": code, "message": description } }; var result = { event : {response}}; return result;}//Function to format the volume with leading zerosfunction pad(num, size) { var s = "000" + num; return s.substr(s.length-size);}