Guest User

Untitled

a guest
Nov 15th, 2020
573
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.93 KB | None | 0 0
  1. // Variables used by Scriptable.
  2. // These must be at the very top of the file. Do not edit.
  3. // icon-color: green; icon-glyph: magic;
  4. // Welcome! This script shows basic infos about your Homebridge installation
  5. // All infos shown are based and provided by the Homebridge Config UI X found at https://github.com/oznu/homebridge-config-ui-x
  6. // Thanks to the github user oznu for providing such a nice programm!
  7. // This script does not work if you don't have the Homebridge service (Homebridge Config UI X) running
  8. // This script was developed with Homebridge Config UI X in version 4.32.0 (2020-11-06) and Scriptable app in version 1.6.1 on iOS 14.2
  9. // Maybe you need to update the UI-service OR the Scriptable app OR your iPhone if this script does not work for you
  10.  
  11. // CONFIGURATION //////////////////////
  12. // you must at least configure the next 3 lines to make this script work
  13. const hbServiceMachineBaseUrl = 'http://192.168.1.107'; // location of your system running the hb-service, e.g. http://192.168.178.33:8581
  14. const userName = 'admin'; // username of administrator of the hb-service
  15. const password = 'admin'; // password of administrator of the hb-service
  16.  
  17. const systemGuiName = 'Server'; // name of the system your service is running on
  18. const fileManagerMode = 'ICLOUD'; // default is ICLOUD. If you don't use iCloud Drive use option LOCAL
  19. const temperatureUnitConfig = 'CELSIUS'; // options are CELSIUS or FAHRENHEIT
  20. const requestTimeoutInterval = 1; // in seconds; If requests take longer, the script is stopped. Increase it if it doesn't work or you
  21. const decimalChar = ','; // if you like a dot as decimal separator make the comma to a dot here
  22. const maxLineWidth = 310; // if layout doesn't look good for you,
  23. const normalLineHeight = 35; // try to tweak the (font-)sizes & remove/add spaces below
  24. // CONFIGURATION END //////////////////////
  25.  
  26. const authUrl = hbServiceMachineBaseUrl + '/api/auth/login';
  27. const cpuUrl = hbServiceMachineBaseUrl + '/api/status/cpu';
  28. const hbStatusUrl = hbServiceMachineBaseUrl + '/api/status/homebridge';
  29. const ramUrl = hbServiceMachineBaseUrl + '/api/status/ram';
  30. const uptimeUrl = hbServiceMachineBaseUrl + '/api/status/uptime';
  31. const pluginsUrl = hbServiceMachineBaseUrl + '/api/plugins';
  32. const hbVersionUrl = hbServiceMachineBaseUrl + '/api/status/homebridge-version';
  33. const nodeJsUrl = hbServiceMachineBaseUrl + '/api/status/nodejs';
  34. // logo is downloaded only the first time! It is saved in iCloud and then loaded from there everytime afterwards
  35. const logoUrl = 'https://github.com/homebridge/branding/blob/master/logos/homebridge-outline-black.png?raw=true';
  36.  
  37. const timeFormatter = new DateFormatter();
  38. timeFormatter.dateFormat = 'dd.MM. HH:mm';
  39. const headerFont = Font.boldMonospacedSystemFont(12);
  40. const monospacedHeaderFont = Font.lightMonospacedSystemFont(12);
  41. const infoFont = Font.systemFont(10);
  42. const heavyInfoFont = Font.heavySystemFont(10);
  43. const chartAxisFont = Font.systemFont(7);
  44. const updatedAtFont = Font.boldSystemFont(10);
  45. const fontColorWhite = new Color("#FFFFFF");
  46. const bgColorPurple = new Color("#421367");
  47. const lin= new LinearGradient()
  48. lin.colors=[Color.black(), new Color("1e1e1e"),new Color("242424")]
  49. lin.locations =[0,0.5,1]
  50.  
  51. const chartColor = Color.darkGray()
  52. const iconColor = Color.dynamic(Color.black(), Color.white());
  53. const UNAVAILABLE = 'UNAVAILABLE';
  54.  
  55. class LineChart {
  56. // LineChart by https://kevinkub.de/
  57. // taken from https://gist.github.com/kevinkub/b74f9c16f050576ae760a7730c19b8e2
  58. constructor(width, height, values) {
  59. this.ctx = new DrawContext();
  60. this.ctx.size = new Size(width, height);
  61. this.values = values;
  62. }
  63.  
  64. _calculatePath() {
  65. let maxValue = Math.max(...this.values);
  66. let minValue = Math.min(...this.values);
  67. let difference = maxValue - minValue;
  68. let count = this.values.length;
  69. let step = this.ctx.size.width / (count - 1);
  70. let points = this.values.map((current, index, all) => {
  71. let x = step * index;
  72. let y = this.ctx.size.height - (current - minValue) / difference * this.ctx.size.height;
  73. return new Point(x, y);
  74. });
  75. return this._getSmoothPath(points);
  76. }
  77.  
  78. _getSmoothPath(points) {
  79. let path = new Path();
  80. path.move(new Point(0, this.ctx.size.height));
  81. path.addLine(points[0]);
  82. for (let i = 0; i < points.length - 1; i++) {
  83. let xAvg = (points[i].x + points[i + 1].x) / 2;
  84. let yAvg = (points[i].y + points[i + 1].y) / 2;
  85. let avg = new Point(xAvg, yAvg);
  86. let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y);
  87. let next = new Point(points[i + 1].x, points[i + 1].y);
  88. let cp2 = new Point((xAvg + points[i + 1].x) / 2, points[i + 1].y);
  89. path.addQuadCurve(avg, cp1);
  90. path.addQuadCurve(next, cp2);
  91. }
  92. path.addLine(new Point(this.ctx.size.width, this.ctx.size.height));
  93. path.closeSubpath();
  94. return path;
  95. }
  96.  
  97. configure(fn) {
  98. let path = this._calculatePath();
  99. if (fn) {
  100. fn(this.ctx, path);
  101. } else {
  102. this.ctx.addPath(path);
  103. this.ctx.fillPath(path);
  104. }
  105. return this.ctx;
  106. }
  107. }
  108.  
  109. // WIDGET INIT //////////////////////
  110. let widget = await createWidget();
  111. if (!config.runsInWidget) {
  112. await widget.presentMedium();
  113. }
  114.  
  115. Script.setWidget(widget);
  116. Script.complete();
  117.  
  118. // WIDGET INIT END //////////////////////
  119.  
  120.  
  121. async function createWidget() {
  122. // authenticate against the hb-service
  123. let token = await getAuthToken();
  124.  
  125. let widget = new ListWidget();
  126. //widget.backgroundColor = bgColorPurple;
  127. // widget.backgroundGradient=lin
  128. if (token !== UNAVAILABLE) {
  129. widget.addSpacer(10);
  130. }
  131.  
  132. // LOGO AND HEADER //////////////////////
  133. let titleStack = widget.addStack();
  134. titleStack.centerAlignContent()
  135. //titleStack.size = new Size(maxLineWidth, normalLineHeight);
  136. const logo = await getHbLogo();
  137. const imgWidget = titleStack.addImage(logo);
  138. imgWidget.tintColor = iconColor
  139. imgWidget.imageSize = new Size(40, 30);
  140. titleStack.addSpacer(5)
  141. headerStack= titleStack.addStack()
  142. headerStack.layoutVertically()
  143.  
  144. let headerText = headerStack.addText('Homebridge ');
  145. headerText.font = headerFont;
  146. headerText.size = new Size(60, normalLineHeight);
  147. // headerText.textColor = fontColorWhite;
  148.  
  149. let updatedAt = headerStack.addText('Status:');
  150. updatedAt.font = updatedAtFont;
  151. // updatedAt.textColor = fontColorWhite;
  152. updatedAt.centerAlignText();
  153.  
  154. updatedAt = headerStack.addText(timeFormatter.string(new Date()));
  155. updatedAt.font = updatedAtFont;
  156. // updatedAt.textColor = fontColorWhite;
  157. updatedAt.centerAlignText();
  158.  
  159. // LOGO AND HEADER END //////////////////////
  160.  
  161.  
  162. if (token === UNAVAILABLE) {
  163. // script ends after the next line
  164. return addNotAvailableInfos(widget, titleStack);
  165. }
  166.  
  167. // fetch all the data necessary
  168. let hbStatus = await getHomebridgeStatus(token);
  169. let hbUpToDate = await getHomebridgeUpToDate(token);
  170. let pluginsUpToDate = await getPluginsUpToDate(token);
  171. let nodeJsUpToDate = await getNodeJsUpToDate(token);
  172. let cpuData = await fetchData(token, cpuUrl);
  173. let ramData = await fetchData(token, ramUrl);
  174. let usedRamText = await getUsedRamString(ramData);
  175. let uptimeText = await getUptimeString(token);
  176.  
  177. // STATUS PANEL IN THE HEADER //////////////////////
  178. titleStack.addSpacer(30)
  179. let statusInfo = titleStack.addStack()
  180. //statusInfo.size = new Size(145, 30);
  181. statusInfo.layoutVertically()
  182.  
  183. line=statusInfo.addStack()
  184. //running
  185. item=line.addStack()
  186. item.centerAlignContent()
  187. addImageItem(hbStatus)
  188. item.addSpacer(2)
  189. addTextItem("Running")
  190. //UTd
  191. line.addSpacer()
  192. item=line.addStack()
  193. item.centerAlignContent()
  194. addImageItem(hbUpToDate)
  195. //addImageItem("❌")
  196. item.addSpacer(2)
  197. addTextItem("Homebridge")
  198.  
  199.  
  200. statusInfo.addSpacer(5)
  201. //item.addSpacer()
  202.  
  203. line=statusInfo.addStack()
  204. //PluginsUTD
  205. item=line.addStack()
  206. item.centerAlignContent()
  207. addImageItem(pluginsUpToDate)
  208. item.addSpacer(2)
  209. addTextItem("Plugins")
  210.  
  211. //Node.JS UD
  212. line.addSpacer()
  213. item=line.addStack()
  214. item.centerAlignContent()
  215. addImageItem(nodeJsUpToDate)
  216. item.addSpacer(2)
  217. addTextItem("Node.js")
  218. line.addSpacer(23)
  219.  
  220.  
  221. // .inaddText(hbStatus + 'Running ' + hbUpToDate + 'UTD\n' + pluginsUpToDate + 'Plugins UTD ' + nodeJsUpToDate + 'Node.js UTD');
  222. // statusInfo.font = monospacedHeaderFont;
  223. //
  224. // statusInfo.textColor = fontColorWhite;
  225. //
  226.  
  227. // STATUS PANEL IN THE HEADER END //////////////////////
  228.  
  229.  
  230. widget.addSpacer(10);
  231.  
  232.  
  233. // CHART STACK START //////////////////////
  234. let s= widget.addStack()
  235. let l = s.addStack()
  236.  
  237. let cpuLoadAndRamText= l.addText('CPU Load: ');
  238. cpuLoadAndRamText.font = heavyInfoFont;
  239. // cpuLoadAndRamText.textColor = fontColorWhite;
  240.  
  241.  
  242. cpuLoadAndRamText = l.addText(getAsRoundedString(cpuData.currentLoad, 1) + '%');
  243. cpuLoadAndRamText.font = infoFont;
  244. // cpuLoadAndRamText.textColor = fontColorWhite;
  245. s.addSpacer(70)
  246.  
  247. l=s.addStack()
  248. l.addSpacer(5)
  249. cpuLoadAndRamText= l.addText('RAM Usage: ');
  250. cpuLoadAndRamText.font = heavyInfoFont;
  251. // cpuLoadAndRamText.textColor = fontColorWhite;
  252.  
  253.  
  254. cpuLoadAndRamText = l.addText(usedRamText + '%');
  255. cpuLoadAndRamText.font = infoFont;
  256. // cpuLoadAndRamText.textColor = fontColorWhite;
  257.  
  258.  
  259.  
  260. widget.addSpacer(5)
  261. let chartStack = widget.addStack();
  262. chartStack.size = new Size(maxLineWidth, 30);
  263.  
  264. let minMaxCpuLoadText = chartStack.addText(getMaxString(cpuData.cpuLoadHistory, 2) + '%\n\n' + getMinString(cpuData.cpuLoadHistory, 2) + '%');
  265. minMaxCpuLoadText.size = new Size(20, 10);
  266. minMaxCpuLoadText.font = chartAxisFont;
  267. // minMaxCpuLoadText.textColor = fontColorWhite;
  268.  
  269. let cpuLoadChart = new LineChart(500, 100, cpuData.cpuLoadHistory).configure((ctx, path) => {
  270. ctx.opaque = false;
  271. ctx.setFillColor(chartColor);
  272. ctx.addPath(path);
  273. ctx.fillPath(path);
  274. }).getImage();
  275. let cpuLoadChartImage = chartStack.addImage(cpuLoadChart);
  276. cpuLoadChartImage.imageSize = new Size(110, 25);
  277.  
  278. chartStack.addSpacer(25);
  279.  
  280. let minMaxRamUsageText = chartStack.addText(getMaxString(ramData.memoryUsageHistory, 2) + '%\n\n' + getMinString(ramData.memoryUsageHistory, 2) + '%');
  281. minMaxRamUsageText.size = new Size(20, 10);
  282. minMaxRamUsageText.font = chartAxisFont;
  283. // minMaxRamUsageText.textColor = fontColorWhite;
  284.  
  285. let ramUsageChart = new LineChart(500, 100, ramData.memoryUsageHistory).configure((ctx, path) => {
  286. ctx.opaque = false;
  287. ctx.setFillColor(chartColor);
  288. ctx.addPath(path);
  289. ctx.fillPath(path);
  290. }).getImage();
  291. let ramUsageChartImage = chartStack.addImage(ramUsageChart);
  292. ramUsageChartImage.imageSize = new Size(110, 25);
  293. chartStack.addSpacer()
  294. // CHART STACK END //////////////////////
  295.  
  296.  
  297. widget.addSpacer(3);
  298.  
  299.  
  300. // LOWER PART //////////////////////
  301. let row3Stack = widget.addStack();
  302. row3Stack.size = new Size(maxLineWidth, 30);
  303.  
  304. l = row3Stack.addStack()
  305. cpuTempText = l.addText('CPU Temp: ');
  306. cpuTempText.font = heavyInfoFont;
  307. cpuTempText.size = new Size(150, 30);
  308. // cpuTempText.textColor = fontColorWhite;
  309.  
  310. cpuTempText = l.addText(getTemperatureString(cpuData.cpuTemperature.main));
  311. cpuTempText.font = infoFont;
  312. cpuTempText.size = new Size(150, 30);
  313. // cpuTempText.textColor = fontColorWhite;
  314.  
  315. l.addSpacer()
  316.  
  317.  
  318. l = row3Stack.addStack()
  319.  
  320. let uptimeTitleTextRef = l.addText(' Uptimes: ');
  321. uptimeTitleTextRef.font = heavyInfoFont;
  322. // uptimeTitleTextRef.textColor = fontColorWhite;
  323.  
  324. let uptimeTextRef = l.addText(uptimeText);
  325. uptimeTextRef.font = infoFont;
  326. // uptimeTextRef.textColor = fontColorWhite;
  327. l.addSpacer()
  328. // LOWER PART END //////////////////////
  329.  
  330. widget.addSpacer();
  331.  
  332. // BOTTOM UPDATED TEXT //////////////////////
  333. // let updatedAt = widget.addText('Updated: ' + timeFormatter.string(new Date()));
  334. // updatedAt.font = updatedAtFont;
  335. // updatedAt.textColor = fontColorWhite;
  336. // updatedAt.centerAlignText();
  337. // BOTTOM UPDATED TEXT END //////////////////////
  338. widget.url=hbServiceMachineBaseUrl
  339. return widget;
  340. }
  341.  
  342. async function getAuthToken() {
  343. let req = new Request(authUrl);
  344. req.timeoutInterval = requestTimeoutInterval;
  345. let body = {
  346. "username": userName,
  347. "password": password,
  348. "otp": "string"
  349. };
  350. let headers = {
  351. "accept": "*\/*", "Content-Type": "application/json"
  352. };
  353. req.body = JSON.stringify(body);
  354. req.method = "POST";
  355. req.headers = headers;
  356. let authData;
  357. try {
  358. authData = await req.loadJSON();
  359. log(authData)
  360. } catch (e) {
  361. return UNAVAILABLE;
  362. }
  363. return authData.access_token;
  364. }
  365.  
  366. async function fetchData(token, url) {
  367. let req = new Request(url);
  368. req.timeoutInterval = requestTimeoutInterval;
  369. let headers = {
  370. "accept": "*\/*", "Content-Type": "application/json",
  371. "Authorization": "Bearer " + token
  372. };
  373. req.headers = headers;
  374. return req.loadJSON();
  375. }
  376.  
  377. async function getHomebridgeStatus(token) {
  378. const statusData = await fetchData(token, hbStatusUrl);
  379. return statusData.status === 'up' ? '✅' : '❌';
  380. }
  381.  
  382. async function getHomebridgeUpToDate(token) {
  383. const hbVersionData = await fetchData(token, hbVersionUrl);
  384. log(hbVersionData.updateAvailable)
  385. return hbVersionData.updateAvailable ? '❌' : '✅';
  386. }
  387.  
  388. async function getNodeJsUpToDate(token) {
  389. const nodeJsData = await fetchData(token, nodeJsUrl);
  390. return nodeJsData.updateAvailable ? '❌' : '✅';
  391. }
  392.  
  393. async function getPluginsUpToDate(token) {
  394. const pluginsData = await fetchData(token, pluginsUrl);
  395. for (plugin of pluginsData) {
  396. if (plugin.updateAvailable) {
  397. return '❌';
  398. }
  399. }
  400. return '✅';
  401. }
  402.  
  403. async function getUsedRamString(ramData) {
  404. return getAsRoundedString(100 - 100 * ramData.mem.available / ramData.mem.total, 1);
  405. }
  406.  
  407. async function getUptimeString(token) {
  408. const uptimeData = await fetchData(token, uptimeUrl);
  409. let serverTime = uptimeData.time.uptime;
  410. let processUptime = uptimeData.processUptime;
  411. return systemGuiName + ':\t ' + formatMinutes(serverTime) + '\nUI-Service:\t ' + formatMinutes(processUptime);
  412. }
  413.  
  414. function formatMinutes(value) {
  415. if (value > 60 * 60 * 24) {
  416. return getAsRoundedString(value / 60 / 60 / 24, 1) + 'd';
  417. } else if (value > 60 * 60) {
  418. return getAsRoundedString(value / 60 / 60, 1) + 'h';
  419. } else if (value > 60) {
  420. return getAsRoundedString(value / 60, 0) + 'm';
  421. } else {
  422. return getAsRoundedString(value, 2) + 's';
  423. }
  424. }
  425.  
  426. async function loadImage(imgUrl) {
  427. let req = new Request(imgUrl);
  428. req.timeoutInterval = requestTimeoutInterval;
  429. let image = await req.loadImage();
  430. return image;
  431. }
  432.  
  433. async function getHbLogo() {
  434. // fileManagerMode must be LOCAL if you do not use iCloud drive
  435. let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
  436. let path = getStoredLogoPath();
  437. if (fm.fileExists(path)) {
  438. return fm.readImage(path);
  439. } else {
  440. // logo did not exist -> download it and save it for next time the widget runs
  441. const logo = await loadImage(logoUrl);
  442. saveHbLogo(logo);
  443. return logo;
  444. }
  445. }
  446.  
  447. function saveHbLogo(image) {
  448. // fileManagerMode must be LOCAL if you do not use iCloud drive
  449. let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
  450. let path = getStoredLogoPath();
  451. fm.writeImage(path, image);
  452. }
  453.  
  454. function getStoredLogoPath() {
  455. // fileManagerMode must be LOCAL if you do not use iCloud drive
  456. let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
  457. let dirPath = fm.joinPath(fm.documentsDirectory(), "homebridgeStatus");
  458. if (!fm.fileExists(dirPath)) {
  459. fm.createDirectory(dirPath);
  460. }
  461. return fm.joinPath(dirPath, "hbLogo.png");
  462. }
  463.  
  464.  
  465. function addNotAvailableInfos(widget, titleStack) {
  466. let statusInfo = titleStack.addText(' ');
  467. statusInfo.textColor = fontColorWhite;
  468. statusInfo.size = new Size(150, normalLineHeight);
  469. let errorText = widget.addText(' ❌ UI-Service not reachable!\n 👉🏻 Server started?\n 👉🏻 UI-Service process started?\n 👉🏻 Server-URL ' + hbServiceMachineBaseUrl + ' correct?\n 👉🏻 Are you in the same network?');
  470. errorText.size = new Size(410, 130);
  471. errorText.font = infoFont;
  472. errorText.textColor = fontColorWhite;
  473.  
  474.  
  475. widget.addSpacer(15);
  476. let updatedAt = widget.addText('Updated: ' + timeFormatter.string(new Date()));
  477. updatedAt.font = updatedAtFont;
  478. updatedAt.textColor = fontColorWhite;
  479. updatedAt.centerAlignText();
  480.  
  481. return widget;
  482. }
  483.  
  484. function getAsRoundedString(value, decimals) {
  485. let factor = Math.pow(10, decimals);
  486. return (Math.round((value + Number.EPSILON) * factor) / factor).toFixed(1).replace('.', decimalChar);
  487. }
  488.  
  489. function getMaxString(arrayOfNumbers, decimals) {
  490. let factor = Math.pow(10, decimals);
  491. return (Math.round((Math.max(...arrayOfNumbers) + Number.EPSILON) * factor) / factor).toString().replace('.', decimalChar);
  492. }
  493.  
  494. function getMinString(arrayOfNumbers, decimals) {
  495. let factor = Math.pow(10, decimals);
  496. return (Math.round((Math.min(...arrayOfNumbers) + Number.EPSILON) * factor) / factor).toString().replace('.', decimalChar);
  497. }
  498.  
  499. function getTemperatureString(temperatureInCelsius) {
  500. if (temperatureUnitConfig === 'FAHRENHEIT') {
  501. return getAsRoundedString(convertToFahrenheit(temperatureInCelsius), 1) + '°F';
  502. } else {
  503. return getAsRoundedString(temperatureInCelsius, 1) + '°C';
  504. }
  505. }
  506.  
  507. function convertToFahrenheit(temperatureInCelsius) {
  508. return temperatureInCelsius * 9 / 5 + 32
  509. }
  510.  
  511.  
  512. function addTextItem(text){
  513. t=item.addText(text)
  514. t.font = Font.semiboldMonospacedSystemFont(10);
  515. //t.textColor = fontColorWhite;
  516. }
  517.  
  518. function addImageItem(text){
  519. switch (text){
  520. case "✅" :
  521. name="checkmark.circle.fill"
  522. color=Color.green()
  523. break;
  524. case "❌":
  525. name="exclamationmark.triangle.fill"
  526. color=Color.red()
  527. }
  528. log(name)
  529. image = symbol(name)
  530. i=item.addImage(image)
  531. i.resizable=true
  532. i.imageSize=new Size(13,13)
  533. i.tintColor=color
  534. }
  535.  
  536.  
  537. function symbol(name){
  538. sf=SFSymbol.named(name)
  539. sf.applyFont(Font.heavySystemFont(50))
  540. return sf.image}
  541.  
Advertisement
Add Comment
Please, Sign In to add comment