Source: extensions/msfs2020/msfs2020.js

  1. /**
  2. * StreamRoller Copyright 2023 "SilenusTA https://www.twitch.tv/olddepressedgamer"
  3. *
  4. * StreamRoller is an all in one streaming solution designed to give a single
  5. * 'second monitor' control page and allow easy integration for configuring
  6. * content (ie. tweets linked to chat, overlays triggered by messages, hue lights
  7. * controlled by donations etc)
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as published
  11. * by the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  21. */
  22. // ############################# msfs2020.js ##############################
  23. // Allows data to be sent/received from Microsoft Flight Sim 2020
  24. // ---------------------------- creation --------------------------------------
  25. // Author: Silenus aka twitch.tv/OldDepressedGamer
  26. // GitHub: https://github.com/SilenusTA/StreamRoller
  27. // Date: 01-Jul-2023
  28. // ============================================================================
  29. /**
  30. * @extension MSFS2020
  31. * Game extension to allow access to control Microsoft Flight simulator and also receive data from on of ten's of thousands of sim variables
  32. */
  33. // ============================================================================
  34. // IMPORTS/VARIABLES
  35. // ============================================================================
  36. import * as fs from "fs";
  37. import { MSFS_API, SystemEvents } from 'msfs-simconnect-api-wrapper';
  38. import { SimVars } from "msfs-simconnect-api-wrapper/simvars/index.js";
  39. import { dirname } from 'path';
  40. import { fileURLToPath } from 'url';
  41. import * as logger from "../../backend/data_center/modules/logger.js";
  42. import sr_api from "../../backend/data_center/public/streamroller-message-api.cjs";
  43. const __dirname = dirname(fileURLToPath(import.meta.url));
  44. const localConfig = {
  45. OUR_CHANNEL: "MSFS2020_CHANNEL",
  46. EXTENSION_NAME: "msfs2020",
  47. SYSTEM_LOGGING_TAG: "[EXTENSION]",
  48. DataCenterSocket: null,
  49. MAX_CONNECTION_ATTEMPTS: 5,
  50. state: {
  51. color: "red",
  52. msfsconnected: false,
  53. msfsconnecting: false
  54. },
  55. heartBeatTimeout: 5000,
  56. heartBeatHandle: null,
  57. pollMSFSHandle: null,
  58. msfs_api: new MSFS_API(),
  59. msfs_api_connected_handle: null,
  60. //msfs_api_recveventhandler: null,
  61. //maintain a list of trigger handles for deregistering
  62. EventCallabackHandles: [],
  63. previousValue: [], // holds the last value read so we can provide 'onchange' only triggers
  64. dumpTriggers: false //autodocs needs to manually parse this so we dump it to file and manually create the readme triggers and actions table
  65. };
  66. const default_serverConfig = {
  67. __version__: "0.2.1",
  68. extensionname: localConfig.EXTENSION_NAME,
  69. channel: localConfig.OUR_CHANNEL,
  70. msfs2020ennabled: "off",
  71. msfs2020SimPollInterval: "5",
  72. msfs2020extension_restore_defaults: "off"
  73. };
  74. let serverConfig = structuredClone(default_serverConfig)
  75. const default_triggersandactions =
  76. {
  77. extensionname: serverConfig.extensionname,
  78. description: "Connects to Microsoft Flight Sim 2020 and reads/writes simvars. when connected to MSFS2020 thousands more triggers/options will be available than appear in this default list. <BR> ie you can set a trigger on simvar 'FLAPS HANDLE INDEX' index 0 position 2 to trigger an action when the flaps are set to position 2<BR>Ie you can set an action on the simvar 'GENERAL ENG THROTTLE LEVER POSITION' using index 1 and a value of 50 to set the postition of throttle 1 to 50%. Please feel free to post useful triggers and actions on teh discord server for others to play with<BR><B>DON'T FORGET TO TURN ON MONITORING FOR ANY VARS YOU WANT TO TRIGGER ON (IN THE SETTINGS PAGE)</B>",
  79. version: "0.2",
  80. channel: serverConfig.channel,
  81. triggers:
  82. [
  83. {
  84. name: "onRequest_PLANE LATITUDE LONGITUDE",
  85. displaytitle: "Current lat/long (OnRequest)",
  86. description: "The current lat long in one message with separate variables",
  87. messagetype: "trigger_onRequest_PLANE LATITUDE LONGITUDE",
  88. parameters: { lat: "", long: "" }
  89. }
  90. ],
  91. actions:
  92. [
  93. {
  94. name: "PLANE LATITUDE LONGITUDE_get",
  95. displaytitle: "Get lat/long (Get)",
  96. description: "Get the current lat long in one message",
  97. messagetype: "action_PLANE LATITUDE LONGITUDE_get",
  98. parameters: {}
  99. }
  100. ],
  101. }
  102. let triggersandactions = structuredClone(default_triggersandactions)
  103. const default_serverData =
  104. {
  105. __version__: "0.2",
  106. SimVars: {},
  107. EventVars:
  108. ["AIRCRAFT_LOADED", "CRASHED", "CRASH_RESET", "CUSTOM_MISSION_ACTION_EXECUTED"
  109. , "FLIGHT_LOADED", "FLIGHT_SAVED", "FLIGHT_PLAN_ACTIVATED", "FLIGHT_PLAN_DEACTIVATED"
  110. , "OBJECT_ADDED", "OBJECT_REMOVED", "PAUSE", "PAUSE_EX1", "PAUSED", "POSITION_CHANGED"
  111. , "SIM_START", "SIM_STOP", "UNPAUSED", "VIEW"
  112. ],
  113. triggersNamesArray: [] // names of triggers we are monitoring ie. FLAPS_POSITION:1
  114. }
  115. let serverData = structuredClone(default_serverData)
  116. // ============================================================================
  117. // FUNCTION: initialise
  118. // ============================================================================
  119. /**
  120. * Starts the extension using the given data.
  121. * @param {object:Express} app
  122. * @param {string} host
  123. * @param {number} port
  124. * @param {number} heartbeat
  125. */
  126. function initialise (app, host, port, heartbeat)
  127. {
  128. try
  129. {
  130. if (typeof (heartbeat) != "undefined")
  131. localConfig.heartBeatTimeout = heartbeat;
  132. else
  133. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".initialise", "DataCenterSocket no heatbeat passed:", heartbeat);
  134. localConfig.DataCenterSocket = sr_api.setupConnection(onDataCenterMessage, onDataCenterConnect,
  135. onDataCenterDisconnect, host, port);
  136. } catch (err)
  137. {
  138. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".initialise", "localConfig.DataCenterSocket connection failed:", err);
  139. }
  140. }
  141. // ============================================================================
  142. // FUNCTION: onDataCenterDisconnect
  143. // ============================================================================
  144. /**
  145. * Called when connection is lost to StreamRoller
  146. * @param {string} reason
  147. */
  148. function onDataCenterDisconnect (reason)
  149. {
  150. logger.log(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterDisconnect", reason);
  151. }
  152. // ============================================================================
  153. // FUNCTION: onDataCenterConnect
  154. // ============================================================================
  155. /**
  156. * Called when server connects
  157. * @param {object} socket
  158. */
  159. function onDataCenterConnect (socket)
  160. {
  161. logger.log(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterConnect", "Creating our channel");
  162. sr_api.sendMessage(localConfig.DataCenterSocket,
  163. sr_api.ServerPacket("RequestConfig", serverConfig.extensionname));
  164. sr_api.sendMessage(localConfig.DataCenterSocket,
  165. sr_api.ServerPacket("RequestData", serverConfig.extensionname));
  166. sr_api.sendMessage(localConfig.DataCenterSocket,
  167. sr_api.ServerPacket("CreateChannel", serverConfig.extensionname, serverConfig.channel)
  168. );
  169. clearTimeout(localConfig.heartBeatHandle);
  170. heartBeatCallback();
  171. }
  172. // ============================================================================
  173. // FUNCTION: onDataCenterMessage
  174. // ============================================================================
  175. /**
  176. * Called when we receive a message from the server
  177. * @param {object} server_packet
  178. */
  179. function onDataCenterMessage (server_packet)
  180. {
  181. if (server_packet.type === "ConfigFile")
  182. {
  183. if (server_packet.data != "" && server_packet.to === serverConfig.extensionname)
  184. {
  185. if (server_packet.data.__version__ != default_serverConfig.__version__)
  186. {
  187. serverConfig = structuredClone(default_serverConfig);
  188. console.log("\x1b[31m" + serverConfig.extensionname + " ConfigFile Updated", "The config file has been Restored. Your settings may have changed" + "\x1b[0m");
  189. }
  190. else
  191. serverConfig = structuredClone(server_packet.data);
  192. SaveConfigToServer();
  193. pollMSFS();
  194. }
  195. }
  196. else if (server_packet.type === "DataFile")
  197. {
  198. if (server_packet.data != "")
  199. {
  200. if (server_packet.data.__version__ != default_serverData.__version__)
  201. {
  202. serverData = structuredClone(default_serverData)
  203. console.log("\x1b[31m" + serverConfig.extensionname + " DataFile Updated", "The data file has been Restored. Your settings may have changed" + "\x1b[0m");
  204. }
  205. if (server_packet.to === serverConfig.extensionname && server_packet.data != undefined && server_packet.data.SimVars != undefined
  206. && Object.keys(server_packet.data.SimVars).length > 0)
  207. {
  208. // SaveDataToServer();
  209. serverData = structuredClone(server_packet.data)
  210. // connect to msfs
  211. initSimVarsandTriggers();
  212. pollMSFS();
  213. }
  214. }
  215. }
  216. else if (server_packet.type === "ExtensionMessage")
  217. {
  218. let extension_packet = server_packet.data;
  219. if (extension_packet.type === "RequestSettingsWidgetSmallCode")
  220. SendSettingsWidgetSmall(extension_packet.from);
  221. else if (extension_packet.type === 'RequestSettingsWidgetLargeCode')
  222. SendSettingsWidgetLarge(extension_packet.from);
  223. else if (extension_packet.type === "SettingsWidgetSmallData")
  224. {
  225. if (extension_packet.data.extensionname === serverConfig.extensionname)
  226. {
  227. let reconnect = false
  228. let disconnect = false
  229. // check if we are changing to on
  230. if (serverConfig.msfs2020ennabled == "off" && extension_packet.data.msfs2020ennabled == "on")
  231. reconnect = true
  232. // check if we are changing to off
  233. else if (serverConfig.msfs2020ennabled == "on" && !extension_packet.data)
  234. disconnect = true
  235. serverConfig.msfs2020ennabled = "off";
  236. for (const [key, value] of Object.entries(extension_packet.data))
  237. serverConfig[key] = value;
  238. SaveConfigToServer();
  239. if (reconnect)
  240. {
  241. initSimVarsandTriggers()
  242. pollMSFS()
  243. sendTriggersAndActions(server_packet.from)
  244. }
  245. else if (disconnect)
  246. {
  247. MSFS2020Disconnect()
  248. }
  249. else
  250. pollMSFS()
  251. SendSettingsWidgetSmall("");
  252. SendSettingsWidgetLarge("");
  253. }
  254. }
  255. else if (extension_packet.type === "SettingsWidgetLargeData")
  256. {
  257. if (extension_packet.to === serverConfig.extensionname)
  258. {
  259. if (extension_packet.data.demoextension_restore_defaults == "on")
  260. {
  261. serverConfig = structuredClone(default_serverConfig);
  262. serverData = structuredClone(default_serverData)
  263. console.log("\x1b[31m" + serverConfig.extensionname + " ConfigFile and Data files Updated.", "The config file has been Restored. Your settings may have changed" + "\x1b[0m");
  264. }
  265. else
  266. {
  267. let reconnect = false
  268. if (serverConfig.msfs2020ennabled == "off" && extension_packet.data.msfs2020ennabled == "on")
  269. reconnect = true
  270. handleSettingsWidgetLargeData(extension_packet.data)
  271. SaveConfigToServer();
  272. SaveDataToServer();
  273. if (reconnect || (extension_packet.data.msfs2020ennabled == "on" && !localConfig.state.msfsconnected))
  274. {
  275. initSimVarsandTriggers()
  276. pollMSFS()
  277. sendTriggersAndActions(server_packet.from)
  278. }
  279. // broadcast our modal out so anyone showing it can update it
  280. SendSettingsWidgetSmall("");
  281. SendSettingsWidgetLarge("");
  282. }
  283. }
  284. }
  285. else if (extension_packet.type === "SendTriggerAndActions")
  286. {
  287. if (extension_packet.to === serverConfig.extensionname)
  288. sendTriggersAndActions(server_packet.from)
  289. }
  290. else if (extension_packet.type.indexOf("action_" == 0))
  291. {
  292. if (extension_packet.to === serverConfig.extensionname)
  293. {
  294. if (serverConfig.msfs2020ennabled == "on")
  295. {
  296. // test for our home grown versions
  297. if (extension_packet.type == "action_PLANE LATITUDE LONGITUDE_get")
  298. {
  299. performActionGetLatLong(extension_packet)
  300. }
  301. else
  302. performAction(extension_packet)
  303. }
  304. }
  305. }
  306. else
  307. logger.log(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterMessage", "received unhandled ExtensionMessage ", server_packet);
  308. }
  309. else if (server_packet.type === "UnknownChannel")
  310. {
  311. logger.info(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterMessage", "Channel " + server_packet.data + " doesn't exist, scheduling rejoin");
  312. setTimeout(() =>
  313. {
  314. sr_api.sendMessage(localConfig.DataCenterSocket,
  315. sr_api.ServerPacket(
  316. "JoinChannel", serverConfig.extensionname, server_packet.data
  317. ));
  318. }, 5000);
  319. }
  320. else if (server_packet.type === "ChannelData")
  321. {
  322. let extension_packet = server_packet.data;
  323. if (extension_packet.type === "HeartBeat")
  324. {
  325. //Just ignore messages we know we don't want to handle
  326. }
  327. else
  328. {
  329. logger.log(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterMessage", "received message from unhandled channel ", server_packet.dest_channel);
  330. }
  331. }
  332. else if (server_packet.type === "InvalidMessage")
  333. {
  334. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".onDataCenterMessage",
  335. "InvalidMessage ", server_packet.data.error, server_packet);
  336. }
  337. else if (server_packet.type === "LoggingLevel")
  338. {
  339. logger.setLoggingLevel(server_packet.data)
  340. }
  341. else if (server_packet.type === "ChannelJoined"
  342. || server_packet.type === "ChannelCreated"
  343. || server_packet.type === "ChannelLeft")
  344. {
  345. // just a blank handler for items we are not using to avoid message from the catchall
  346. }
  347. // ------------------------------------------------ unknown message type received -----------------------------------------------
  348. else
  349. logger.warn(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname +
  350. ".onDataCenterMessage", "Unhandled message type", server_packet.type);
  351. }
  352. // ===========================================================================
  353. // FUNCTION: SendSettingsWidgetSmall
  354. // ===========================================================================
  355. /**
  356. * Parses and sends out our small settings widget to the given extension
  357. * @param {string} to
  358. */
  359. function SendSettingsWidgetSmall (to)
  360. {
  361. fs.readFile(__dirname + '/msfs2020settingswidgetsmall.html', function (err, filedata)
  362. {
  363. if (err)
  364. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname +
  365. ".SendSettingsWidgetSmall", "failed to load modal", err);
  366. //throw err;
  367. else
  368. {
  369. let modalstring = filedata.toString();
  370. for (const [key, value] of Object.entries(serverConfig))
  371. {
  372. if (value === "on")
  373. modalstring = modalstring.replace(key + "checked", "checked");
  374. else if (typeof (value) == "string")
  375. modalstring = modalstring.replace(key + "text", value);
  376. }
  377. sr_api.sendMessage(localConfig.DataCenterSocket,
  378. sr_api.ServerPacket(
  379. "ExtensionMessage",
  380. serverConfig.extensionname,
  381. sr_api.ExtensionPacket(
  382. "SettingsWidgetSmallCode",
  383. serverConfig.extensionname,
  384. modalstring,
  385. "",
  386. to,
  387. serverConfig.channel
  388. ),
  389. "",
  390. to
  391. ))
  392. }
  393. });
  394. }
  395. // ===========================================================================
  396. // FUNCTION: handleSettingsWidgetSmallData
  397. // ===========================================================================
  398. /**
  399. * Handles user submitted code from our large settings widget/page
  400. * @param {object} modalcode
  401. */
  402. function handleSettingsWidgetLargeData (modalcode)
  403. {
  404. /////////////////////////////////////////////////
  405. // Restore Defaults
  406. /////////////////////////////////////////////////
  407. if (modalcode.msfs2020_restore_defaults == "on")
  408. {
  409. console.log("MSFS defaults restored")
  410. console.log("\x1b[31m" + serverConfig.extensionname + " ConfigFile Updated", "The config file has been Restored. Your settings may have changed" + "\x1b[0m");
  411. serverConfig = structuredClone(default_serverConfig);
  412. serverData = structuredClone(default_serverData)
  413. return;
  414. }
  415. //Clear our previous triggers (we will re-add the ones we have been sent)
  416. serverData.triggersNamesArray = [];
  417. /////////////////////////////////////////////////
  418. // get serverConfig values
  419. /////////////////////////////////////////////////
  420. for (const [key, value] of Object.entries(modalcode))
  421. {
  422. if (key in serverConfig)
  423. serverConfig[key] = value;
  424. }
  425. /////////////////////////////////////////////////
  426. // get SimVar values
  427. /////////////////////////////////////////////////
  428. const varKeys = Object.keys(serverData.SimVars);
  429. // loop through all our keys as we need to know what has been changed
  430. for (let i = 0; i < varKeys.length; i++)
  431. {
  432. // check if we have been sent one (must be checked/set to "on" to be sent)
  433. if (varKeys[i] in modalcode)
  434. {
  435. if (serverData.SimVars[varKeys[i]].enabled != modalcode[varKeys[i]])
  436. {
  437. // it is set to on and ours is off
  438. serverData.SimVars[varKeys[i]].enabled = "on"
  439. }
  440. // else if it is the same do nothing
  441. }
  442. else
  443. {
  444. // not been sent it so it must be off
  445. if (serverData.SimVars[varKeys[i]].enabled == "on")
  446. {
  447. // we are currently set to on and need to turn off
  448. serverData.SimVars[varKeys[i]].enabled = "off"
  449. }
  450. }
  451. // have we been sent a value (box checked)
  452. if (varKeys[i] in modalcode)
  453. {
  454. // check for an index number simvar
  455. if (varKeys[i].indexOf(":index") > 0)
  456. {
  457. let txtfieldname = varKeys[i].replace(":index", "") + "_index"
  458. // check we have a field for the index ()
  459. addToTriggersArray(varKeys[i], modalcode[txtfieldname])
  460. serverData.SimVars[varKeys[i]].index = modalcode[txtfieldname]
  461. }
  462. else
  463. {
  464. // check we have a field for the index ()
  465. addToTriggersArray(varKeys[i])
  466. serverData.SimVars[varKeys[i]].index = modalcode[varKeys[i]]
  467. }
  468. }
  469. }
  470. }
  471. // ===========================================================================
  472. // FUNCTION: SendSettingsWidgetLarge
  473. // ===========================================================================
  474. /**
  475. * Creates and sends our large settings widget html code to the given extension
  476. * @param {string} to
  477. */
  478. function SendSettingsWidgetLarge (to)
  479. {
  480. let generatedpage = "";
  481. let first = true
  482. let readwritestate = ""
  483. fs.readFile(__dirname + "/msfs2020settingswidgetlarge.html", function (err, filedata)
  484. {
  485. if (err)
  486. {
  487. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname +
  488. ".SendSettingsWidgetLarge", "failed to load modal", err);
  489. //throw err;
  490. }
  491. else
  492. {
  493. //get the file as a string
  494. let modalstring = filedata.toString();
  495. //////////////////////////////////////////////////
  496. // mormal replaces(ie enabled and reset checkboxes
  497. //////////////////////////////////////////////////
  498. for (const [key, value] of Object.entries(serverConfig))
  499. {
  500. // checkboxes
  501. if (value === "on")
  502. modalstring = modalstring.replace(key + "checked", "checked");
  503. else if (typeof (value) === "string" || typeof (value) === "number")
  504. modalstring = modalstring.replaceAll(key + "text", value);
  505. }
  506. //////////////////////////////////////////////////
  507. // Enabled Simvars
  508. // Done separately so we can delete them easier
  509. //////////////////////////////////////////////////
  510. const triggerKeys = Object.keys(serverData.triggersNamesArray);
  511. first = false;
  512. generatedpage = ""
  513. generatedpage += "Polling too many Sim Variables too fast can cause lag in the game. Check the tool tips by hovering mouse over the SimVar name for description</p>"
  514. generatedpage += "<p>Note: A simvar needs to be monitored to be able to set a trigger on it.</p>"
  515. generatedpage += "<p> An example trigger might be to position your camera in obs to show your height. To do this you would set the trigger to ... <BR>"
  516. generatedpage += "<BR>TRIGGER: 'msfs2020', 'trigger_INDICATED ALTITUDE'"
  517. generatedpage += "<BR>ACTION: 'obs','action_SetSceneItemTransform','SceneName'= 'Camera_feed', 'sourceName' = '#4x3_Cam', 'positionY' = '((%%data%% - 10000) / (0 - 10000)) * (1040-50) + 50'"
  518. generatedpage += "<BR>In this example my group name that the camera is in is called 'Camera_feed' and the camera source is called '#4x3_Cam'"
  519. generatedpage += "<BR>I set the positionY of the camera based on the value in the 'data' field of the trigger (altitude from MSFS) but as this is a big range it needs converting to the pixel position in obs"
  520. generatedpage += "<BR>The equasion set converts an alt range of 0 to 10,000 feet into a pixel position of 50 to 1040 pixels (what I need for my screen res in obs)"
  521. generatedpage += "<HR><h5>Currently Active Simvars</H5> "
  522. for (let i = 0; i < triggerKeys.length; i++)
  523. {
  524. // every 5 items start a new row but only close out after the first itme
  525. if (!first && i % 5 == 0)
  526. generatedpage += "</tr>"
  527. if (i % 5 == 0)
  528. generatedpage += "<tr>"
  529. // ''''''''''''''''''''' TD '''''''''''''''''''''''''''''''''
  530. generatedpage += "<td scope='row'>" + serverData.triggersNamesArray[triggerKeys[i]]
  531. // Does this variable have an index part
  532. if (serverData.triggersNamesArray[triggerKeys[i]].indexOf(":index") > 0)
  533. generatedpage += ":" + serverData.triggersNamesArray[triggerKeys[i]].index
  534. //check that we have this value
  535. if (typeof (serverData.triggersNamesArray[triggerKeys[i]].enabled) == "undefined")
  536. serverData.triggersNamesArray[triggerKeys[i]].enabled == "off"
  537. generatedpage += "</td>"
  538. first = false;
  539. }
  540. if (!first)
  541. generatedpage += "</tr>"
  542. modalstring = modalstring.replace("MSFSActiveCode", generatedpage);
  543. //////////////////////////////////////////////////
  544. // SimVars
  545. //////////////////////////////////////////////////
  546. const varKeys = Object.keys(serverData.SimVars);
  547. first = false;
  548. generatedpage = ""
  549. generatedpage += "<h5>Simvars Available</H5><p>Select checkbox to set as a Trigger. Some simvars have "
  550. generatedpage += "an index (ie GENERAL ENG RPM:index) for these an index number is needed to identify "
  551. generatedpage += "a specific item (ie which engine rpm). The letters in brackets indicate it the item "
  552. generatedpage += "is readonly (R) or ReadWrite (RW)</p>"
  553. for (let i = 0; i < varKeys.length; i++)
  554. {
  555. // every 5 items start a new row but only close out after the first itme
  556. if (!first && i % 5 == 0)
  557. generatedpage += "</tr>"
  558. if (i % 5 == 0)
  559. generatedpage += "<tr>"
  560. // ''''''''''''''''''''' TD '''''''''''''''''''''''''''''''''
  561. // are we able to set this vaiable
  562. if (serverData.SimVars[varKeys[i]].settable == true)
  563. readwritestate = "(RW)"
  564. else
  565. readwritestate = "(R)"
  566. generatedpage += "<td scope='row'>" + serverData.SimVars[varKeys[i]].name + " " + readwritestate + " "
  567. // Does this variable have an index part
  568. if (serverData.SimVars[varKeys[i]].name.indexOf(":index") > 0)
  569. {
  570. let fieldname = serverData.SimVars[varKeys[i]].name.replace(":index", "") + "_index"
  571. generatedpage += "<input type='text' style='width: 30px' name='" + fieldname + "'"
  572. generatedpage += " id='" + fieldname + "' value='" + serverData.SimVars[varKeys[i]].index + "'> "
  573. }
  574. //check that we have this value
  575. if (typeof (serverData.SimVars[varKeys[i]].enabled) == "undefined")
  576. serverData.SimVars[varKeys[i]].enabled == "off"
  577. if (serverData.SimVars[varKeys[i]].enabled == "on")
  578. generatedpage += " <input class='form-check-input' name='" + serverData.SimVars[varKeys[i]].name + "' type='checkbox' id='" + serverData.SimVars[varKeys[i]].name + "' checked >"
  579. else
  580. generatedpage += " <input class='form-check-input' name='" + serverData.SimVars[varKeys[i]].name + "' type='checkbox' id='" + serverData.SimVars[varKeys[i]].name + "'>"
  581. generatedpage += "</td>"
  582. first = false;
  583. }
  584. if (!first)
  585. generatedpage += "</tr>"
  586. modalstring = modalstring.replace("MSFSSimVarsCode", generatedpage);
  587. //////////////////////////////////////////////////
  588. // Send out the new code
  589. //////////////////////////////////////////////////
  590. // send the modified modal data to the server
  591. sr_api.sendMessage(localConfig.DataCenterSocket,
  592. sr_api.ServerPacket(
  593. "ExtensionMessage", // this type of message is just forwarded on to the extension
  594. serverConfig.extensionname,
  595. sr_api.ExtensionPacket(
  596. "SettingsWidgetLargeCode", // message type
  597. serverConfig.extensionname, //our name
  598. modalstring,// data
  599. "",
  600. to,
  601. serverConfig.channel
  602. ),
  603. "",
  604. to // in this case we only need the "to" channel as we will send only to the requester
  605. ))
  606. }
  607. });
  608. // done as we might have added some enabled entries
  609. SaveDataToServer();
  610. }
  611. // ============================================================================
  612. // FUNCTION: SaveConfigToServer
  613. // ============================================================================
  614. /**
  615. * Saves our config to the server
  616. */
  617. function SaveConfigToServer ()
  618. {
  619. sr_api.sendMessage(localConfig.DataCenterSocket, sr_api.ServerPacket
  620. ("SaveConfig",
  621. serverConfig.extensionname,
  622. serverConfig))
  623. }
  624. // ============================================================================
  625. // FUNCTION: SaveDataToServer
  626. // ============================================================================
  627. /**
  628. * Saves our data to the server
  629. */
  630. function SaveDataToServer ()
  631. {
  632. sr_api.sendMessage(localConfig.DataCenterSocket,
  633. sr_api.ServerPacket(
  634. "SaveData",
  635. serverConfig.extensionname,
  636. serverData));
  637. }
  638. // ============================================================================
  639. // FUNCTION: heartBeat
  640. // ============================================================================
  641. /**
  642. * Sends out heartbeat messages so other extensions can see our status
  643. */
  644. function heartBeatCallback ()
  645. {
  646. localConfig.state.msfsconnected = localConfig.msfs_api.connected
  647. localConfig.state.color = "red"
  648. if (serverConfig.msfs2020ennabled == "on")
  649. {
  650. // if we are not connected and we should be then attempt to reconnect
  651. if (!localConfig.state.msfsconnected && !localConfig.state.msfsconnecting)
  652. msfsapiconnect()
  653. if (localConfig.state.msfsconnected != true)
  654. localConfig.state.color = "orange"
  655. else
  656. localConfig.state.color = "green"
  657. }
  658. sr_api.sendMessage(localConfig.DataCenterSocket,
  659. sr_api.ServerPacket("ChannelData",
  660. serverConfig.extensionname,
  661. sr_api.ExtensionPacket(
  662. "HeartBeat",
  663. serverConfig.extensionname,
  664. localConfig.state
  665. ,
  666. serverConfig.channel),
  667. serverConfig.channel
  668. ),
  669. );
  670. localConfig.heartBeatHandle = setTimeout(heartBeatCallback, localConfig.heartBeatTimeout)
  671. }
  672. // ############################################################################
  673. // MSFS specific code
  674. // ############################################################################
  675. // ============================================================================
  676. // FUNCTION: initSimVarsandTriggers
  677. // ============================================================================
  678. /*
  679. serverData.SimVars['ACCELERATION BODY X']
  680. simvar example {
  681. desc: 'Acceleration relative to aircraft X axis, in east/west direction',
  682. units: 'feet',
  683. data_type: 4,
  684. settable: false,
  685. name: 'ACCELERATION BODY X',
  686. enabled: 'off'
  687. }
  688. */
  689. /**
  690. * initialises our sim variables dynamically creates triggers and actions based on what is available.
  691. * Note there can be thousands of possible options created here
  692. */
  693. function initSimVarsandTriggers ()
  694. {
  695. if (serverConfig.msfs2020ennabled == "on")
  696. {
  697. /// *************** Create our Simvars array *****************************
  698. // make a clone of the simVars (so we can add extra fields for enabled etc)
  699. const varKeys = Object.keys(SimVars);
  700. varKeys.sort();
  701. for (let i = 0; i < varKeys.length; i++)
  702. {
  703. if (typeof serverData.SimVars[varKeys[i]] == "undefined")
  704. serverData.SimVars[varKeys[i]] = SimVars[varKeys[i]]
  705. // this creates the enabled variable if it doesn't exist
  706. if (serverData.SimVars[varKeys[i]].enabled != "on")
  707. serverData.SimVars[varKeys[i]].enabled = "off"
  708. if (varKeys[i].indexOf(":index") > 0 && typeof (serverData.SimVars[varKeys[i]].index) == "undefined")
  709. {
  710. serverData.SimVars[varKeys[i]].index = "0"
  711. //(serverData.SimVars[varKeys[i]])
  712. }
  713. }
  714. SaveDataToServer()
  715. /// *************** Create our triggers and actions *****************************
  716. // Create triggers and actions
  717. triggersandactions = {}
  718. triggersandactions = structuredClone(default_triggersandactions)
  719. /// *************** Add Event variables *************************
  720. for (let i = 0; i < serverData.EventVars.length; i++)
  721. {
  722. triggersandactions.triggers.push(
  723. {
  724. name: SystemEvents[serverData.EventVars[i]].name,
  725. displaytitle: SystemEvents[serverData.EventVars[i]].name,
  726. description: SystemEvents[serverData.EventVars[i]].desc,
  727. messagetype: "trigger_" + SystemEvents[serverData.EventVars[i]].name,
  728. parameters: { data: "" }
  729. });
  730. triggersandactions.triggers.push(
  731. {
  732. name: "onChange_" + SystemEvents[serverData.EventVars[i]].name,
  733. displaytitle: SystemEvents[serverData.EventVars[i]].name + " (OnChange)",
  734. description: SystemEvents[serverData.EventVars[i]].desc,
  735. messagetype: "trigger_onChange_" + SystemEvents[serverData.EventVars[i]].name,
  736. parameters: { data: "" }
  737. });
  738. }
  739. //// *************** Add SimVars *************************
  740. for (let i = 0; i < varKeys.length; i++)
  741. {
  742. //// *************** If it is writeable add an action to write the variable *************************
  743. if (serverData.SimVars[varKeys[i]].settable == true)
  744. {
  745. if (varKeys[i].indexOf(":index") > 0)
  746. {
  747. triggersandactions.actions.push(
  748. {
  749. name: serverData.SimVars[varKeys[i]].name,
  750. displaytitle: serverData.SimVars[varKeys[i]].name + "(Set)",
  751. description: "Set the simvar " + serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units,
  752. messagetype: "action_" + serverData.SimVars[varKeys[i]].name,
  753. parameters: { index: "0", data: "" }
  754. }
  755. )
  756. }
  757. else
  758. {
  759. triggersandactions.actions.push(
  760. {
  761. name: serverData.SimVars[varKeys[i]].name,
  762. displaytitle: serverData.SimVars[varKeys[i]].name + "(Set)",
  763. description: "Set the simvar " + serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units,
  764. messagetype: "action_" + serverData.SimVars[varKeys[i]].name,
  765. parameters: { data: "" }
  766. }
  767. )
  768. }
  769. }
  770. //// *************** If readable add an action to read it *************************
  771. if (varKeys[i].indexOf(":index") > 0)
  772. {
  773. triggersandactions.actions.push(
  774. {
  775. name: serverData.SimVars[varKeys[i]].name + "_get",
  776. displaytitle: serverData.SimVars[varKeys[i]].name + "(Get)",
  777. description: "Request the value, will be returned in a 'name_(Single) 'trigger" + serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units,
  778. messagetype: "action_" + serverData.SimVars[varKeys[i]].name + "_get",
  779. parameters: { index: "0" }
  780. }
  781. )
  782. }
  783. else
  784. {
  785. triggersandactions.actions.push(
  786. {
  787. name: serverData.SimVars[varKeys[i]].name + "_get",
  788. displaytitle: serverData.SimVars[varKeys[i]].name + "(Get)",
  789. description: "Request the value, will be returned in a 'name_(Single) 'trigger" + serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units,
  790. messagetype: "action_" + serverData.SimVars[varKeys[i]].name + "_get",
  791. parameters: {}
  792. }
  793. )
  794. }
  795. // triggers with indexes
  796. let paramdata = {}
  797. if (varKeys[i].indexOf(":index") > 0)
  798. {
  799. paramdata = { index: "0", data: "" }
  800. }
  801. else
  802. {
  803. paramdata = { data: "" }
  804. }
  805. triggersandactions.triggers.push
  806. (
  807. {
  808. name: serverData.SimVars[varKeys[i]].name,
  809. displaytitle: serverData.SimVars[varKeys[i]].name + " (Poll)",
  810. description: serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units + ": settable " + serverData.SimVars[varKeys[i]].settable,
  811. messagetype: "trigger_" + serverData.SimVars[varKeys[i]].name,
  812. parameters: paramdata
  813. }
  814. )
  815. // add an onchange trigger (we only send this when data changes)
  816. triggersandactions.triggers.push
  817. (
  818. {
  819. name: "onChange_" + serverData.SimVars[varKeys[i]].name,
  820. displaytitle: serverData.SimVars[varKeys[i]].name + " (OnChange)",
  821. description: serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units + ": settable " + serverData.SimVars[varKeys[i]].settable,
  822. messagetype: "trigger_onChange_" + serverData.SimVars[varKeys[i]].name,
  823. parameters: paramdata
  824. }
  825. )
  826. triggersandactions.triggers.push
  827. (
  828. {
  829. name: "onRequest_" + serverData.SimVars[varKeys[i]].name,
  830. displaytitle: serverData.SimVars[varKeys[i]].name + " (OnRequest)",
  831. description: serverData.SimVars[varKeys[i]].desc + ": Units " + serverData.SimVars[varKeys[i]].units + ": settable " + serverData.SimVars[varKeys[i]].settable,
  832. messagetype: "trigger_onRequest_" + serverData.SimVars[varKeys[i]].name,
  833. parameters: paramdata
  834. }
  835. )
  836. }
  837. }
  838. /* uncomment to log the data structures if you are interested in the data we get from msfs */
  839. //file_log("SimVars", serverData.SimVars)
  840. //file_log("triggersandactions", triggersandactions)
  841. //file_log("SystemEvents", SystemEvents)
  842. }
  843. // ============================================================================
  844. // FUNCTION: msfsapiconnect
  845. // ============================================================================
  846. /**
  847. * connect to the MSFS2020 api interface
  848. */
  849. function msfsapiconnect ()
  850. {
  851. if (serverConfig.msfs2020ennabled == "on")
  852. {
  853. localConfig.state.msfsconnecting = true;
  854. localConfig.msfs_api.connect({ onConnect: (nodeSimconnectHandle) => MSFS2020RegisterSimvars(nodeSimconnectHandle) })
  855. .catch(err =>
  856. {
  857. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".msfsapiconnect", "Connection failed. Reconnecting in", serverConfig.msfs2020SimPollInterval, "seconds");
  858. })
  859. /* no idea how to put a handler in for recvevents :( I'll come back to this later
  860. console.log(localConfig.msfs_api_recveventhandler)
  861. if (localConfig.state.connected && localConfig.msfs_api_recveventhandler == null)
  862. localConfig.msfs_api.on("exception", () =>
  863. {
  864. console.log("oops recv received")
  865. })
  866. */
  867. }
  868. }
  869. // ============================================================================
  870. // FUNCTION: MSFS2020Disconnect
  871. // ============================================================================
  872. /**
  873. * disconnect from the MSFS2020 interface and callbacks
  874. */
  875. function MSFS2020Disconnect ()
  876. {
  877. clearTimeout(localConfig.pollMSFSHandle)
  878. localConfig.state.msfsconnected = false
  879. for (let i = 0; i < serverData.EventVars.length; i++)
  880. {
  881. if (localConfig.EventCallabackHandles[serverData.EventVars[i]])
  882. localConfig.EventCallabackHandles[serverData.EventVars[i]]()
  883. }
  884. for (let i = 0; i < serverData.AirportVars.length; i++)
  885. {
  886. if (localConfig.EventCallabackHandles[serverData.AirportVars[i]])
  887. localConfig.EventCallabackHandles[serverData.AirportVars[i]]()
  888. }
  889. }
  890. // ============================================================================
  891. // FUNCTION: MSFS2020RegisterSimvars
  892. // ============================================================================
  893. /**
  894. * Registers with MSFS2020 for callbacks on simvars
  895. * @param {object} handle simconnecnt handle
  896. */
  897. async function MSFS2020RegisterSimvars (handle)
  898. {
  899. try
  900. {
  901. localConfig.state.msfsconnected = true;
  902. localConfig.state.msfsconnecting = false;
  903. localConfig.msfs_api_connected_handle = handle;
  904. localConfig.EventHandles = []
  905. for (let i = 0; i < serverData.EventVars.length; i++)
  906. {
  907. localConfig.EventCallabackHandles[serverData.EventVars[i]]
  908. = localConfig.msfs_api.on(SystemEvents[serverData.EventVars[i]], (data) =>
  909. {
  910. let triggertopost = findtriggerByMessageType("trigger_" + SystemEvents[serverData.EventVars[i]].name)
  911. triggertopost.parameters.data = data;
  912. if (data != localConfig.previousValue[serverData.EventVars[i]])
  913. {
  914. sr_api.sendMessage(localConfig.DataCenterSocket,
  915. sr_api.ServerPacket(
  916. "ChannelData",
  917. serverConfig.extensionname,
  918. sr_api.ExtensionPacket(
  919. "trigger_onChange_" + SystemEvents[serverData.EventVars[i]].name,
  920. serverConfig.extensionname,
  921. triggertopost,//{ index: simvarindex, data: data[simvar] },
  922. serverConfig.channel
  923. ),
  924. serverConfig.channel
  925. ));
  926. localConfig.previousValue[serverData.EventVars[i]] = data;
  927. }
  928. sr_api.sendMessage(localConfig.DataCenterSocket,
  929. sr_api.ServerPacket(
  930. "ChannelData",
  931. serverConfig.extensionname,
  932. sr_api.ExtensionPacket(
  933. "trigger_" + SystemEvents[serverData.EventVars[i]].name,
  934. serverConfig.extensionname,
  935. triggertopost,//{ index: simvarindex, data: data[simvar] },
  936. serverConfig.channel
  937. ),
  938. serverConfig.channel
  939. ));
  940. });
  941. }
  942. /*
  943. this is left in as a hint if we decide to add this in future. Airports appear
  944. to use a different api that quick testing seemed to fail on.
  945. localConfig.NEARBY_AIRPORTS = await localConfig.msfs_api.get(`NEARBY_AIRPORTS`);
  946. //console.log(`${localConfig.NEARBY_AIRPORTS.length} nearby airports`);
  947. localConfig.ALL_AIRPORTS = await localConfig.msfs_api.get(`ALL_AIRPORTS`);
  948. //console.log(`${localConfig.ALL_AIRPORTS.length} total airports on the planet`);
  949. */
  950. //const airportData = await localConfig.msfs_api.get(`AIRPORT:CYYJ`);
  951. //console.log(JSON.stringify(airportData, null, 2));
  952. //console.log(JSON.stringify(await localConfig.msfs_api.get(`AIRPORT:CYYJ`), null, 2));
  953. /*
  954. console.log("AIRPORTS_IN_RANGE", AIRPORTS_IN_RANGE)
  955. const inRange = localConfig.msfs_api.on(AIRPORTS_IN_RANGE, (data) =>
  956. {
  957. console.log("in range airports:", data);
  958. inRange();
  959. });
  960. /*
  961. const outOfRange = localConfig.msfs_api.on(SystemEvents.AIRPORTS_OUT_OF_RANGE, (data) =>
  962. {
  963. console.log("outOfRange",data);
  964. outOfRange();
  965. });
  966. */
  967. if (localConfig.dumpTriggers)
  968. dumpTriggersActions()
  969. }
  970. catch (err)
  971. {
  972. localConfig.state.msfsconnected = false;
  973. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".MSFS2020RegisterSimvars", "Error ", err.message);
  974. }
  975. }
  976. // ============================================================================
  977. // FUNCTION: addToTriggersArray
  978. // ============================================================================
  979. function dumpTriggersActions ()
  980. {
  981. setTimeout(() =>
  982. {
  983. file_log("", "", true)
  984. }, 10000);
  985. }
  986. // ============================================================================
  987. // FUNCTION: addToTriggersArray
  988. // ============================================================================
  989. /**
  990. * Adds a simvar to our known list of names. A simvar may be an array and have an index (ie engines have an index for each engine)
  991. * others may not have index's
  992. * @param {string} name
  993. * @param {number} [index=0]
  994. */
  995. function addToTriggersArray (name, index = 0)
  996. {
  997. if (name.indexOf(":index") > 0)
  998. name = name.replace(":index", ":" + index)
  999. // edge case when the user doesn't have this in their datafile
  1000. if (typeof serverData.triggersNamesArray == "undefined")
  1001. serverData.triggersNamesArray = [];
  1002. if (!serverData.triggersNamesArray.includes(name))
  1003. serverData.triggersNamesArray.push(name);
  1004. // make it alphabetical
  1005. serverData.triggersNamesArray.sort();
  1006. }
  1007. // ============================================================================
  1008. // FUNCTION: postTriggers
  1009. // ============================================================================
  1010. /**
  1011. * posts a trigger out when a given symvar fires. Will also post a "changed" trigger
  1012. * if the value was previously different
  1013. * possible message types posted (NAME) is the simvar name and (INDEX) is the
  1014. * index if an indexed simvar
  1015. * trigger_(NAME)
  1016. * trigger_(NAME):(INDEX)
  1017. * trigger_onChange_(NAME)
  1018. * trigger_onChange_(NAME):(INDEX)
  1019. */
  1020. async function postTriggers ()
  1021. {
  1022. let index = ""
  1023. let simvarindex = 0
  1024. let name = ""
  1025. let simvar = "";
  1026. let indexToSplit = 0;
  1027. let data = ""
  1028. let messageType = ""
  1029. let onChangeMessageType = ""
  1030. //check if connected
  1031. if (!localConfig.state.msfsconnected)
  1032. return;
  1033. for (index in serverData.triggersNamesArray)
  1034. {
  1035. // check if we have an index (':n' part)
  1036. if (serverData.triggersNamesArray[index].indexOf(":") > 0)
  1037. {
  1038. // the name will have the index attached so we need to split it up
  1039. simvar = serverData.triggersNamesArray[index]
  1040. indexToSplit = simvar.indexOf(':');
  1041. name = simvar.slice(0, indexToSplit);
  1042. simvarindex = simvar.slice(indexToSplit + 1);
  1043. simvar = simvar.replaceAll(" ", "_")
  1044. }
  1045. else
  1046. {
  1047. name = serverData.triggersNamesArray[index]
  1048. simvar = serverData.triggersNamesArray[index]
  1049. simvar = simvar.replaceAll(" ", "_")
  1050. }
  1051. // get the simvar data
  1052. try
  1053. {
  1054. data = await localConfig.msfs_api.get(simvar);
  1055. }
  1056. catch (err)
  1057. {
  1058. console.log("Error during get", err.message)
  1059. }
  1060. // set the message type
  1061. if (simvar.indexOf(":") > 0)
  1062. {
  1063. messageType = "trigger_" + name + ":index"
  1064. onChangeMessageType = "trigger_onChange_" + name + ":index"
  1065. }
  1066. else
  1067. {
  1068. messageType = "trigger_" + name
  1069. onChangeMessageType = "trigger_onChange_" + name
  1070. }
  1071. let triggertopost = findtriggerByMessageType(messageType)
  1072. triggertopost.parameters.index = simvarindex;
  1073. triggertopost.parameters.data = data[simvar];
  1074. sr_api.sendMessage(localConfig.DataCenterSocket,
  1075. sr_api.ServerPacket(
  1076. "ChannelData",
  1077. serverConfig.extensionname,
  1078. sr_api.ExtensionPacket(
  1079. messageType,
  1080. serverConfig.extensionname,
  1081. triggertopost,//{ index: simvarindex, data: data[simvar] },
  1082. serverConfig.channel
  1083. ),
  1084. serverConfig.channel
  1085. ));
  1086. if (localConfig.previousValue[simvar] != triggertopost.parameters.data)
  1087. {
  1088. let triggertopost = findtriggerByMessageType(onChangeMessageType)
  1089. triggertopost.parameters.index = simvarindex;
  1090. triggertopost.parameters.data = data[simvar];
  1091. sr_api.sendMessage(localConfig.DataCenterSocket,
  1092. sr_api.ServerPacket(
  1093. "ChannelData",
  1094. serverConfig.extensionname,
  1095. sr_api.ExtensionPacket(
  1096. onChangeMessageType,
  1097. serverConfig.extensionname,
  1098. triggertopost,//{ index: simvarindex, data: data[simvar] },
  1099. serverConfig.channel
  1100. ),
  1101. serverConfig.channel
  1102. ));
  1103. localConfig.previousValue[simvar] = triggertopost.parameters.data
  1104. }
  1105. }
  1106. }
  1107. // ============================================================================
  1108. // FUNCTION: performActionGetLatLong
  1109. // ============================================================================
  1110. /**
  1111. * Special simvar just for streamroller :D
  1112. * Creates a simvar containing both lat and long rather
  1113. * than having to create two requests actions and getting two responses back
  1114. * @param {object} data
  1115. */
  1116. function performActionGetLatLong (data)
  1117. {
  1118. let action = ""
  1119. if (localConfig.msfs_api.connected != true)
  1120. return;
  1121. // Request for data, not a set
  1122. if (data.type == "action_PLANE LATITUDE LONGITUDE_get")
  1123. {
  1124. try
  1125. {
  1126. let onRequestMessageType = "trigger_onRequest_PLANE LATITUDE LONGITUDE"
  1127. localConfig.msfs_api.get("PLANE LATITUDE")
  1128. .then(data =>
  1129. {
  1130. let triggertopost = findtriggerByMessageType(onRequestMessageType)
  1131. // returned data doesn't have spaces it has underscores
  1132. triggertopost.parameters.lat = String(data["PLANE_LATITUDE"] * 180 / Math.PI);
  1133. localConfig.msfs_api.get("PLANE LONGITUDE")
  1134. .then(data =>
  1135. {
  1136. triggertopost.parameters.long = String(data["PLANE_LONGITUDE"] * 180 / Math.PI);
  1137. sr_api.sendMessage(localConfig.DataCenterSocket,
  1138. sr_api.ServerPacket(
  1139. "ChannelData",
  1140. serverConfig.extensionname,
  1141. sr_api.ExtensionPacket(
  1142. triggertopost.messagetype,
  1143. serverConfig.extensionname,
  1144. triggertopost,//{ index: simvarindex, data: data[simvar] },
  1145. serverConfig.channel
  1146. ),
  1147. serverConfig.channel
  1148. ));
  1149. }).catch(error => console.log("ERROR:performActionGetLatLong(2): Failed to get simvar", error))
  1150. }).catch(error => console.log("ERROR:performActionGetLatLong(1):Failed to get simvar", error))
  1151. }
  1152. catch (err)
  1153. {
  1154. console.log("ERROR:performActionGetLatLong: Error during get", err.message)
  1155. }
  1156. }
  1157. }
  1158. // ============================================================================
  1159. // FUNCTION: performAction
  1160. // ============================================================================
  1161. /**
  1162. * An action was received to set/change a simvar or retrieve some data
  1163. * A "trigger_onRequest_(simvarname)" will be fired on a request
  1164. * @param {object} data
  1165. */
  1166. function performAction (data)
  1167. {
  1168. let action = ""
  1169. // Request for data, not a set
  1170. if (data.type.indexOf("_get") > -1)
  1171. {
  1172. try
  1173. {
  1174. let simvar = data.type.replace("action_", "").replace("_get", "")
  1175. let onRequestMessageType = "trigger_onRequest_" + simvar
  1176. localConfig.msfs_api.get(simvar)
  1177. .then(data =>
  1178. {
  1179. let triggertopost = findtriggerByMessageType(onRequestMessageType)
  1180. // returned data doesn't have spaces it has underscores
  1181. simvar = simvar.replaceAll(" ", "_")
  1182. triggertopost.parameters.data = data[simvar];
  1183. sr_api.sendMessage(localConfig.DataCenterSocket,
  1184. sr_api.ServerPacket(
  1185. "ChannelData",
  1186. serverConfig.extensionname,
  1187. sr_api.ExtensionPacket(
  1188. triggertopost.messagetype,
  1189. serverConfig.extensionname,
  1190. triggertopost,//{ index: simvarindex, data: data[simvar] },
  1191. serverConfig.channel
  1192. ),
  1193. serverConfig.channel
  1194. ));
  1195. }).catch(error => console.log("ERROR:performAction: Failed to get simvar", error))
  1196. }
  1197. catch (err)
  1198. {
  1199. console.log("ERROR:performAction: Error during get", err.message)
  1200. }
  1201. } else
  1202. {
  1203. try
  1204. {
  1205. action = data.type.replace("action_", "")
  1206. if (action.indexOf(":index") > 0)
  1207. action = action.replace("index", data.data.index)
  1208. localConfig.msfs_api.set(action, data.data.data)
  1209. }
  1210. catch (err)
  1211. {
  1212. console.log("ERROR:performAction ", err.message)
  1213. }
  1214. }
  1215. }
  1216. // ============================================================================
  1217. // FUNCTION: pollMSFS
  1218. // ============================================================================
  1219. /**
  1220. * Poll MSFS for data we are monitoring for
  1221. */
  1222. function pollMSFS ()
  1223. {
  1224. clearTimeout(localConfig.pollMSFSHandle);
  1225. if (serverConfig.msfs2020ennabled == "on")
  1226. postTriggers();
  1227. localConfig.pollMSFSHandle = setTimeout(pollMSFS, serverConfig.msfs2020SimPollInterval * 1000)
  1228. }
  1229. // ============================================================================
  1230. // FUNCTION: findtriggerByMessageType
  1231. // ============================================================================
  1232. /**
  1233. * Sends a list of our current triggers/actions ot the given extension
  1234. * @param {string} to
  1235. */
  1236. function sendTriggersAndActions (to)
  1237. {
  1238. sr_api.sendMessage(localConfig.DataCenterSocket,
  1239. sr_api.ServerPacket("ExtensionMessage",
  1240. serverConfig.extensionname,
  1241. sr_api.ExtensionPacket(
  1242. "TriggerAndActions",
  1243. serverConfig.extensionname,
  1244. triggersandactions,
  1245. "",
  1246. to
  1247. ),
  1248. "",
  1249. to
  1250. )
  1251. )
  1252. }
  1253. // ============================================================================
  1254. // FUNCTION: findtriggerByMessageType
  1255. // ============================================================================
  1256. /**
  1257. * Finds a specific trigger from the given name
  1258. * @param {string} messagetype
  1259. */
  1260. function findtriggerByMessageType (messagetype)
  1261. {
  1262. for (let i = 0; i < triggersandactions.triggers.length; i++)
  1263. {
  1264. if (triggersandactions.triggers[i].messagetype.toLowerCase() == messagetype.toLowerCase()) return triggersandactions.triggers[i];
  1265. }
  1266. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname +
  1267. ".findtriggerByMessageType", "failed to find action", messagetype);
  1268. }
  1269. // ============================================================================
  1270. // FUNCTION: file_log
  1271. // For debug purposes. logs message data to file
  1272. // ============================================================================
  1273. let filestreams = [];
  1274. let basedir = "msfsdata/";
  1275. function file_log (type, data, apidoc = false)
  1276. {
  1277. try
  1278. {
  1279. if (apidoc)
  1280. {
  1281. let filename = serverConfig.extensionname + "_apidoctriggers.json"
  1282. fs.writeFileSync(filename, JSON.stringify(triggersandactions, null, 2), {
  1283. encoding: "utf8",
  1284. flag: "w+",
  1285. });
  1286. }
  1287. else
  1288. {
  1289. //console.log("file_log", type, tags, message)
  1290. var newfile = false;
  1291. var filename = "__noname__";
  1292. var buffer = "\n//#################################\n";
  1293. // sometimes tags are a string, lets create an object for it to log
  1294. if (typeof tags != "object")
  1295. data = { data: data }
  1296. if (!fs.existsSync(basedir))
  1297. {
  1298. newfile = true;
  1299. fs.mkdirSync(basedir, { recursive: true });
  1300. }
  1301. // check if we already have this handler
  1302. if (!filestreams.type)
  1303. {
  1304. filename = type;
  1305. filestreams[type] = fs.createWriteStream(basedir + filename + ".js", { flags: 'a' });
  1306. }
  1307. if (newfile)
  1308. {
  1309. buffer += "let " + type + ";\n";
  1310. buffer += "let message=" + JSON.stringify(data, null, 2) + "\n"
  1311. }
  1312. else
  1313. buffer += "let message=" + JSON.stringify(data, null, 2) + "\n"
  1314. filestreams[type].write(buffer);
  1315. //bad coding but can't end it here (due to async stuff) and it is just debug code (just left as a reminder we have a dangling pointer)
  1316. filestreams[type].end("")
  1317. }
  1318. }
  1319. catch (error)
  1320. {
  1321. console.log("debug file logging crashed", error.message)
  1322. }
  1323. }
  1324. // ============================================================================
  1325. // EXPORTS
  1326. // Note that initialise is mandatory to allow the server to start this extension
  1327. // ============================================================================
  1328. export { initialise, triggersandactions };