Advertisement
dragonbane

alexaTVAdvanced

Feb 20th, 2018
593
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.34 KB | None | 0 0
  1. 'use strict';
  2.  
  3. var WebSocket = require('ws');
  4. var request = require('request');
  5. var uuid = require('uuid-v4');
  6.  
  7. /**
  8. * This sample demonstrates a smart home skill using the publicly available API on Amazon's Alexa platform.
  9. * For more information about developing smart home skills, see
  10. * https://developer.amazon.com/alexa/smart-home
  11. *
  12. * For details on the smart home API, please visit
  13. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference
  14. */
  15.  
  16. const USER_DEVICES = [
  17. {
  18. // This id needs to be unique across all devices discovered for a given manufacturer
  19. endpointId: '40163BEAC7DC', //TV Mac
  20. // The name given by the user in your application. Examples include 'Bedroom light' etc
  21. friendlyName: 'Fernseher',
  22. // Should describe the device type and the company/cloud provider.
  23. // This value will be shown in the Alexa app
  24. description: 'Smart TV Wohnzimmer',
  25. // Company name that produces and sells the smart home device
  26. manufacturerName: 'Samsung',
  27. displayCategories: ['TV'],
  28. // not used at this time
  29. cookie: {
  30. extraDetail1: 'a',
  31. extraDetail2: 'b',
  32. extraDetail3: 'c',
  33. extraDetail4: 'd',
  34. },
  35. capabilities: [
  36. {
  37. "type": "AlexaInterface",
  38. "interface": "Alexa.PowerController",
  39. "version": "3",
  40. "properties": {
  41. "supported": [
  42. {
  43. "name": "powerState"
  44. }
  45. ],
  46. "proactivelyReported": false,
  47. "retrievable": false
  48. }
  49. } ]
  50. }
  51. ];
  52.  
  53. /**
  54. * Utility functions
  55. */
  56.  
  57. function log(title, msg) {
  58. console.log(`[${title}] ${msg}`);
  59. }
  60.  
  61. /**
  62. * Generate a unique message ID
  63. *
  64. * TODO: UUID v4 is recommended as a message ID in production.
  65. */
  66. function generateMessageID() {
  67.  
  68. return uuid();
  69. }
  70.  
  71. function generateTimeStamp() {
  72.  
  73. var time = new Date();
  74.  
  75. return time.toISOString();
  76. }
  77.  
  78. /**
  79. * Generate a response message
  80. *
  81. * @param {Object} request - request.directive object
  82. * @param {string} eventType - eventType e.g. Response or StateReport
  83. * @param {Object} contextProperties - static array of values to report
  84. * @param {Object} payload - Any special payload required for the response
  85. * @returns {Object} Response object
  86. */
  87. function generateResponse(request, eventType, contextProperties, payload) {
  88. return {
  89. context: {
  90. properties: contextProperties
  91. },
  92. event: {
  93. header: {
  94. messageId: generateMessageID(),
  95. namespace: 'Alexa',
  96. name: eventType,
  97. correlationToken: request.header.correlationToken,
  98. payloadVersion: '3'
  99. },
  100. endpoint:{
  101. scope: {
  102. type: 'BearerToken',
  103. token: request.endpoint.scope.token
  104. },
  105. endpointId: request.endpoint.endpointId
  106. },
  107. payload: payload
  108. }
  109. };
  110. }
  111.  
  112. /**
  113. * Functions to access device cloud.
  114. */
  115.  
  116. function getDevicesFromPartnerCloud() {
  117.  
  118. return USER_DEVICES;
  119. }
  120.  
  121. function transmitCommandToPi(cmdJson) {
  122.  
  123. var socket;
  124.  
  125. console.log("Send Cmd to TV: " + cmdJson);
  126.  
  127. return new Promise((resolve, reject) => {
  128.  
  129. socket = new WebSocket('http://' + 'wkyg3jni0whrfyk6.myfritz.net' + ':9090', function (error) {
  130. console.log(new Error(error).toString());
  131.  
  132. reject("SocketError");
  133. return;
  134. });
  135.  
  136. socket.on('error', function (e) {
  137. console.log('Error in WebSocket communication');
  138. socket.close();
  139.  
  140. reject("SocketError");
  141. return;
  142. });
  143.  
  144. socket.on('message', function (msg) {
  145.  
  146. console.log("incoming message: ", msg);
  147.  
  148. if (msg == "[REQUEST]") {
  149.  
  150. socket.send("[TVCMD]=" + cmdJson);
  151. }
  152. else if (msg.startsWith("[END]")) {
  153. let status = msg.substr(msg.indexOf('=') + 1);
  154.  
  155. console.log("Cmd END received with status: " + status + ". Shut down socket!");
  156.  
  157. socket.close();
  158.  
  159. resolve(status);
  160. }
  161. });
  162. });
  163. }
  164.  
  165. //request, name, value, payload
  166.  
  167. function turnTVOn(request, applianceId) {
  168. log('DEBUG', `turnOn (applianceId: ${applianceId})`);
  169.  
  170. var tvCmd = "WOL";
  171.  
  172. return new Promise((resolve, reject) => {
  173.  
  174. // Call device cloud's API to turn on the device
  175. transmitCommandToPi(tvCmd).then((successMsg, error) => {
  176.  
  177. if (error) {
  178. const errorMessage = `Leider ist bei der �bermittlung des TV Befehls ein Problem aufgetreten!`;
  179. log('ERROR', errorMessage);
  180.  
  181. reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
  182. }
  183. else {
  184. if (successMsg == "Delay") {
  185.  
  186. var contextProperties = [{
  187. namespace: request.header.namespace,
  188. name: "powerState",
  189. value: "ON",
  190. timeOfSample: generateTimeStamp(),
  191. uncertaintyInMilliseconds: 30000 //30 seconds due TV connection lag
  192. }];
  193.  
  194. resolve(generateResponse(request, "Response", contextProperties, {}));
  195. }
  196. else {
  197.  
  198. const errorMessage = `TV is already on!`;
  199. log('ERROR', errorMessage);
  200.  
  201. reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
  202. }
  203. }
  204. });
  205. });
  206. }
  207.  
  208. function turnTVOff(request, applianceId) {
  209. log('DEBUG', `turnOff (applianceId: ${applianceId})`);
  210.  
  211. var tvCmd = { method: "ms.remote.control", params: { Cmd: "Click", DataOfCmd: "KEY_POWER", Option: "false", TypeOfRemote: "SendRemoteKey", to: "host" } };
  212.  
  213. return new Promise((resolve, reject) => {
  214.  
  215. transmitCommandToPi(JSON.stringify(tvCmd)).then((successMsg, error) => {
  216.  
  217. if (error) {
  218. const errorMessage = `Leider ist bei der �bermittlung des TV Befehls ein Problem aufgetreten!`;
  219. log('ERROR', errorMessage);
  220.  
  221. reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
  222. }
  223. else {
  224.  
  225. if (successMsg == "tvOff") {
  226. const errorMessage = `TV is already off!`;
  227. log('ERROR', errorMessage);
  228.  
  229. reject(generateResponse(request, 'UnsupportedOperationError', {}, {}));
  230. }
  231. else {
  232.  
  233. var contextProperties = [{
  234. namespace: request.header.namespace,
  235. name: "powerState",
  236. value: "OFF",
  237. timeOfSample: generateTimeStamp(),
  238. uncertaintyInMilliseconds: 2000 //2 seconds due TV connection lag
  239. }];
  240.  
  241. resolve(generateResponse(request, "Response", contextProperties, {}));
  242. }
  243. }
  244. });
  245. });
  246. }
  247.  
  248.  
  249. /**
  250. * Main logic
  251. */
  252.  
  253. /**
  254. * This function is invoked when we receive a "Discovery" message from Alexa Smart Home Skill.
  255. * We are expected to respond back with a list of appliances that we have discovered for a given customer.
  256. *
  257. * @param {Object} request - The full request object from the Alexa smart home service. This represents a DiscoverAppliancesRequest.
  258. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesrequest
  259. *
  260. * @param {function} callback - The callback object on which to succeed or fail the response.
  261. * https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
  262. * If successful, return <DiscoverAppliancesResponse>.
  263. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesresponse
  264. */
  265.  
  266. function handleAuth(request, callback) {
  267.  
  268. console.log("User Tokens for Message Gateway: " + request);
  269.  
  270. const response = {
  271. event: {
  272. header: {
  273. messageId: generateMessageID(),
  274. name: 'AcceptGrant.Response',
  275. namespace: 'Alexa.Authorization',
  276. payloadVersion: '3',
  277. },
  278. payload: {
  279. },
  280. }
  281. };
  282.  
  283. callback(null, response);
  284.  
  285. }
  286.  
  287. function handleDiscovery(request, callback) {
  288. log('DEBUG', `Discovery Request: ${JSON.stringify(request)}`);
  289.  
  290. /**
  291. * Get the OAuth token from the request.
  292. */
  293. const userAccessToken = request.payload.scope.token.trim();
  294.  
  295. if (!userAccessToken) {
  296. const errorMessage = `Discovery Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`;
  297. log('ERROR', errorMessage);
  298. callback(new Error(errorMessage));
  299. }
  300.  
  301. /**
  302. * Assume access token is valid at this point.
  303. * Retrieve list of devices from cloud based on token.
  304. *
  305. * For more information on a discovery response see
  306. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discoverappliancesresponse
  307. */
  308. const response = {
  309. event: {
  310. header: {
  311. messageId: generateMessageID(),
  312. name: 'Discover.Response',
  313. namespace: 'Alexa.Discovery',
  314. payloadVersion: '3',
  315. },
  316. payload: {
  317. endpoints: getDevicesFromPartnerCloud(userAccessToken),
  318. },
  319. }
  320. };
  321.  
  322. /**
  323. * Log the response. These messages will be stored in CloudWatch.
  324. */
  325. log('DEBUG', `Discovery Response: ${JSON.stringify(response)}`);
  326.  
  327. /**
  328. * Return result with successful message.
  329. */
  330. callback(null, response);
  331. }
  332.  
  333. /**
  334. * A function to handle power control events.
  335. * This is called when Alexa requests an action such as turning off an appliance.
  336. *
  337. * @param {Object} request - The full request object from the Alexa smart home service.
  338. * @param {function} callback - The callback object on which to succeed or fail the response.
  339. */
  340. function handlePowerControl(request, callback) {
  341. log('DEBUG', `Control Request: ${JSON.stringify(request)}`);
  342.  
  343. /**
  344. * Get the access token.
  345. */
  346. const userAccessToken = request.endpoint.scope.token.trim();
  347.  
  348. if (!userAccessToken) {
  349. log('ERROR', `Power Request [${request.header.messageId}] failed. Invalid access token: ${userAccessToken}`);
  350. callback(null, generateResponse(request, 'InvalidAccessTokenError', {}, {}));
  351. return;
  352. }
  353.  
  354. /**
  355. * Grab the applianceId from the request.
  356. */
  357. const applianceId = request.endpoint.endpointId;
  358.  
  359. /**
  360. * If the applianceId is missing, return UnexpectedInformationReceivedError
  361. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#unexpectedinformationreceivederror
  362. */
  363. if (!applianceId) {
  364. log('ERROR', 'No applianceId provided in request');
  365. const payload = { faultingParameter: `applianceId: ${applianceId}` };
  366. callback(null, generateResponse(request, 'UnexpectedInformationReceivedError', {}, payload));
  367. return;
  368. }
  369.  
  370. /**
  371. * At this point the applianceId and accessToken are present in the request.
  372. *
  373. */
  374.  
  375. let response;
  376.  
  377. switch (request.header.name) {
  378. case 'TurnOn': {
  379. turnTVOn(request, applianceId).then((successMsg, error) => {
  380.  
  381. if (error)
  382. response = error;
  383. else
  384. response = successMsg;
  385.  
  386. log('DEBUG', `Control Confirmation: ${JSON.stringify(response)}`);
  387.  
  388. callback(null, response);
  389. });
  390. break;
  391. }
  392. case 'TurnOff': {
  393. turnTVOff(request, applianceId).then((successMsg, error) => {
  394.  
  395. if (error)
  396. response = error;
  397. else
  398. response = successMsg;
  399.  
  400. log('DEBUG', `Control Confirmation: ${JSON.stringify(response)}`);
  401.  
  402. callback(null, response);
  403. });
  404.  
  405. break;
  406. }
  407. default: {
  408. log('ERROR', `No supported directive event name: ${request.header.name}`);
  409. callback(null, generateResponse(request, 'UnsupportedOperationError', {}, {}));
  410. return;
  411. }
  412. }
  413. }
  414.  
  415. /**
  416. * Main entry point.
  417. * Incoming events from Alexa service through Smart Home API are all handled by this function.
  418. *
  419. * It is recommended to validate the request and response with Alexa Smart Home Skill API Validation package.
  420. * https://github.com/alexa/alexa-smarthome-validation
  421. */
  422. exports.handler = (request, context, callback) => {
  423. console.log(request);
  424.  
  425. switch (request.directive.header.namespace) {
  426. case 'Alexa.Authorization':
  427. handleAuth(request.directive, callback);
  428. break;
  429. /**
  430. * The namespace of 'Alexa.Discovery' indicates a request is being made to the Lambda for
  431. * discovering all appliances associated with the customer's appliance cloud account.
  432. *
  433. * For more information on device discovery, please see
  434. * https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/smart-home-skill-api-reference#discovery-messages
  435. */
  436. case 'Alexa.Discovery':
  437. handleDiscovery(request.directive, callback);
  438. break;
  439.  
  440. case 'Alexa.PowerController':
  441. handlePowerControl(request.directive, callback);
  442. break;
  443.  
  444. /**
  445. * Received an unexpected message
  446. */
  447. default: {
  448. const errorMessage = `No supported namespace: ${request.directive.header.namespace}`;
  449. log('ERROR', errorMessage);
  450. callback(new Error(errorMessage));
  451. }
  452. }
  453. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement