Closed Ludelag closed 3 years ago
Okay can you show me the stuff below the log window :D? Without password
So the message might be a bit misleading here from my part. A real config is actually only created the first time the credentials are accepted and the widget shows the real GUi.
The url must have http:// in it
Can you reach the config-ui-x via browser using the url you provide in the widget?
Sure,
Here is it:
//Check the readme at https://github.com/lwitzani/homebridgeStatusWidget for setup instructions, troubleshoots and also for updates of course! // // For power users: // I added a configuration mechanism so you don't need to reconfigure it every time you update the script! // Please check the readme for instructions on how to use the persist mechanism for the configuration let configurationFileName = 'Config.json'// change this to an own name e.g. 'configBlack.json' . This name can then be given as a widget parameter in the form 'USE_CONFIG:Config.json' so you don't loose your preferred configuration across script updates (but you will loose it if i have to change the configuration format) const usePersistedConfiguration = true; // false would mean to use the visible configuration below; true means the state saved in iCloud (or locally) will be used const overwritePersistedConfig = false; // if you like your configuration, run the script ONCE with this param to true, then it is saved and can be used via 'CONFIG.json' in widget params //
const CONFIGURATION_JSON_VERSION = 2; // never change this! If i need to change the structure of configuration class, i will increase this counter. Your created config files sadly won't be compatible afterwards. // CONFIGURATION ////////////////////// class Configuration { // you must at least configure the next 3 lines to make this script work or use credentials in parameter when setting up the widget (see the readme on github) // if you don't use credentials, just enter the URL and it should work // as soon as credentials + URL are correct, a configuration is saved and then used. to make changes after that set overwritePersistedConfig to true hbServiceMachineBaseUrl = '192.1.x.xx:8581'; // location of your system running the hb-service, e.g. http://192.168.178.33:8581 userName = 'xxxxx'; // username of administrator of the hb-service password = 'xxxxxxxxxx'; // password of administrator of the hb-service notificationEnabled = true; // set to false to disable all notifications
notificationIntervalInDays = 1; // minimum amount of days between the notification about the same topic; 0 means notification everytime the script is run (SPAM). 1 means you get 1 message per status category per day (maximum of 4 messages per day since there are 4 categories). Can also be something like 0.5 which means in a day you can get up to 8 messages
disableStateBackToNormalNotifications = true; // set to false, if you want to be notified e.g. when Homebridge is running again after it stopped
fileManagerMode = 'ICLOUD'; // default is ICLOUD. If you don't use iCloud Drive use option LOCAL
temperatureUnitConfig = 'CELSIUS'; // options are CELSIUS or FAHRENHEIT
requestTimeoutInterval = 10; // in seconds; If requests take longer, the script is stopped. Increase it if it doesn't work or you
pluginsOrSwUpdatesToIgnore = []; // a string array; enter the exact npm-plugin-names e.g. 'homebridge-fritz' or additionally 'HOMEBRIDGE_UTD' or 'NODEJS_UTD' if you do not want to have them checked for their latest versions
adaptToLightOrDarkMode = true; // if one of the purple or black options is chosen, the widget will adapt to dark/light mode if true
bgColorMode = 'PURPLE_LIGHT'; // default is PURPLE_LIGHT. Other options: PURPLE_DARK, BLACK_LIGHT, BLACK_DARK, CUSTOM (custom colors will be used, see below)
customBackgroundColor1_light = '#3e00fa'; // if bgColorMode CUSTOM is used a LinearGradient is created from customBackgroundColor1_light and customBackgroundColor2_light
customBackgroundColor2_light = '#7a04d4'; // you can use your own colors here; they are saved in the configuration
customBackgroundColor1_dark = '#3e00fa'; // if bgColorMode CUSTOM together with adaptToLightOrDarkMode = true is used, the light and dark custom values are used depending on the active mode
customBackgroundColor2_dark = '#7a04d4';
chartColor_light = '#FFFFFF'; // _light is the default color if adaptToLightOrDarkMode is false
chartColor_dark = '#FFFFFF';
fontColor_light = '#FFFFFF'; // _light the default color if adaptToLightOrDarkMode is false
fontColor_dark = '#FFFFFF';
failIcon = 'โ';
bulletPointIcon = '๐ธ';
decimalChar = ','; // if you like a dot as decimal separator make the comma to a dot here
jsonVersion = CONFIGURATION_JSON_VERSION; // do not change this
enableSiriFeedback = true; // when running script via Siri, she should speak the text that is defined below BUT might be bugged atm, i wrote the dev about it
// logo is downloaded only the first time! It is saved in iCloud and then loaded from there everytime afterwards logoUrl = 'https://github.com/homebridge/branding/blob/master/logos/homebridge-silhouette-round-white.png?raw=true';
// icons:
icon_statusGood = 'checkmark.circle.fill'; // can be any SFSymbol
icon_colorGood = '#' + Color.green().hex; // must have form like '#FFFFFF'
icon_statusBad = 'exclamationmark.triangle.fill'; // can be any SFSymbol
icon_colorBad = '#' + Color.red().hex;// must have form like '#FFFFFF'
icon_statusUnknown = 'questionmark.circle.fill'; // can be any SFSymbol
icon_colorUnknown = '#' + Color.yellow().hex; // must have form like '#FFFFFF'
// internationalization:
status_hbRunning = 'Running';
status_hbUtd = 'UTD';
status_pluginsUtd = 'Plugins UTD '; // maybe add spaces at the end if you see '...' in the widget
status_nodejsUtd = 'Node.js UTD ';
// if you change the descriptions in the status columns, you must adapt the spacers between the columns, so that it looks good again :)
spacer_beforeFirstStatusColumn = 8;
spacer_betweenStatusColumns = 5;
spacer_afterSecondColumn = 0;
title_cpuLoad = 'CPU Load: ';
title_cpuTemp = 'CPU Temp: ';
title_ramUsage = 'RAM Usage: ';
title_uptimes = 'Uptimes:';
title_uiService = 'UI-Service: ';
title_systemGuiName = 'Raspberry Pi: '; // name of the system your service is running on
notification_title = 'Homebridge Status changed:';
notification_expandedButtonText = 'Show me!';
notification_ringTone = 'event'; // all ringtones of Scriptable are possible: default, accept, alert, complete, event, failure, piano_error, piano_success, popup
notifyText_hbNotRunning = 'Your Homebridge instance stopped ๐ฑ';
notifyText_hbNotUtd = 'Update available for Homebridge ๐';
notifyText_pluginsNotUtd = 'Update available for one of your Plugins ๐';
notifyText_nodejsNotUtd = 'Update available for Node.js ๐';
notifyText_hbNotRunning_backNormal = 'Your Homebridge instance is back online ๐';
notifyText_hbNotUtd_backNormal = 'Homebridge is now up to date โ๏ธ';
notifyText_pluginsNotUtd_backNormal = 'Plugins are now up to date โ๏ธ';
notifyText_nodejsNotUtd_backNormal = 'Node.js is now up to date โ๏ธ';
siriGui_title_update_available = 'Available Updates:';
siriGui_title_all_UTD = 'Everything is up to date!';
siriGui_icon_version = 'arrow.right.square.fill'; // can be any SFSymbol
siriGui_icon_version_color = '#' + Color.blue().hex; // must have form like '#FFFFFF'
siri_spokenAnswer_update_available = 'At least one update is available';
siri_spokenAnswer_all_UTD = 'Everything is up to date';
error_noConnectionText = ' ' + this.failIcon + ' UI-Service not reachable!\n ' + this.bulletPointIcon + ' Server started?\n ' + this.bulletPointIcon + ' UI-Service process started?\n ' + this.bulletPointIcon + ' Server-URL ' + this.hbServiceMachineBaseUrl + ' correct?\n ' + this.bulletPointIcon + ' Are you in the same network?';
}
// CONFIGURATION END ////////////////////// let CONFIGURATION = new Configuration(); const noAuthUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/auth/noauth'; const authUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/auth/login'; const cpuUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/cpu'; const hbStatusUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/homebridge'; const ramUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/ram'; const uptimeUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/uptime'; const pluginsUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/plugins'; const hbVersionUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/homebridge-version'; const nodeJsUrl = () => CONFIGURATION.hbServiceMachineBaseUrl + '/api/status/nodejs';
const maxLineWidth = 300; // if layout doesn't look good for you, const normalLineHeight = 35; // try to tweak the (font-)sizes & remove/add spaces below const timeFormatter = new DateFormatter(); timeFormatter.dateFormat = 'dd.MM.yyyy HH:mm:ss'; const headerFont = Font.boldMonospacedSystemFont(12); const infoFont = Font.systemFont(10); const chartAxisFont = Font.systemFont(7); const updatedAtFont = Font.systemFont(7);
const purpleBgGradient_light = createLinearGradient('#421367', '#481367'); const purpleBgGradient_dark = createLinearGradient('#250b3b', '#320d47'); const blackBgGradient_light = createLinearGradient('#707070', '#3d3d3d'); const blackBgGradient_dark = createLinearGradient('#111111', '#222222');
const UNAVAILABLE = 'UNAVAILABLE';
const NOTIFICATION_JSON_VERSION = 1; // never change this! const NOTIFICATION_JSON_FILE_NAME = 'notificationState.json'; // never change this! const HB_LOGO_FILE_NAME = 'hbLogo.png'; // never change this!
const INITIAL_NOTIFICATION_STATE = { 'jsonVersion': NOTIFICATION_JSON_VERSION, 'hbRunning': {'status': true}, 'hbUtd': {'status': true}, 'pluginsUtd': {'status': true}, 'nodeUtd': {'status': true} };
class LineChart { // LineChart by https://kevinkub.de/ // taken from https://gist.github.com/kevinkub/b74f9c16f050576ae760a7730c19b8e2 constructor(width, height, values) { this.ctx = new DrawContext(); this.ctx.size = new Size(width, height); this.values = values; }
_calculatePath() {
let maxValue = Math.max(...this.values);
let minValue = Math.min(...this.values);
let difference = maxValue - minValue;
let count = this.values.length;
let step = this.ctx.size.width / (count - 1);
let points = this.values.map((current, index, all) => {
let x = step * index;
let y = this.ctx.size.height - (current - minValue) / difference * this.ctx.size.height;
return new Point(x, y);
});
return this._getSmoothPath(points);
}
_getSmoothPath(points) {
let path = new Path();
path.move(new Point(0, this.ctx.size.height));
path.addLine(points[0]);
for (let i = 0; i < points.length - 1; i++) {
let xAvg = (points[i].x + points[i + 1].x) / 2;
let yAvg = (points[i].y + points[i + 1].y) / 2;
let avg = new Point(xAvg, yAvg);
let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y);
let next = new Point(points[i + 1].x, points[i + 1].y);
let cp2 = new Point((xAvg + points[i + 1].x) / 2, points[i + 1].y);
path.addQuadCurve(avg, cp1);
path.addQuadCurve(next, cp2);
}
path.addLine(new Point(this.ctx.size.width, this.ctx.size.height));
path.closeSubpath();
return path;
}
configure(fn) {
let path = this._calculatePath();
if (fn) {
fn(this.ctx, path);
} else {
this.ctx.addPath(path);
this.ctx.fillPath(path);
}
return this.ctx;
}
}
// WIDGET INIT ////////////////////// let widget = await createWidget(); if (!config.runsInWidget) { await widget.presentMedium(); }
Script.setWidget(widget); Script.complete();
// WIDGET INIT END //////////////////
async function createWidget() { // fileManagerMode must be LOCAL if you do not use iCloud drive let fm = CONFIGURATION.fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
if (args.widgetParameter) {
// you can either provide as parameter:
// - the config.json file name you want to load the credentials from (must be created before it can be used but highly recommended)
// valid example: 'USE_CONFIG:yourfilename.json' (the 'yourfilename' part can be changed by you)
// this single parameter must start with USE_CONFIG: and end with .json
// - credentials + URL directly (all other changes to the script are lost when you update it e.g. via https://scriptdu.de )
// credentials must be separated by two commas like
// authenticate against the hb-service
let token = await getAuthToken();
if (token === undefined) {
throw('Credentials not valid');
}
let widget = new ListWidget();
handleSettingOfBackgroundColor(widget);
if (token !== UNAVAILABLE) {
widget.addSpacer(10);
}
// LOGO AND HEADER //////////////////////
let titleStack = widget.addStack();
titleStack.size = new Size(maxLineWidth, normalLineHeight);
const logo = await getHbLogo(fm);
const imgWidget = titleStack.addImage(logo);
imgWidget.imageSize = new Size(40, 30);
let headerText = addStyledText(titleStack, ' Homebridge ', headerFont);
headerText.size = new Size(60, normalLineHeight);
// LOGO AND HEADER END //////////////////////
if (token === UNAVAILABLE) {
// script ends after the next line
return addNotAvailableInfos(widget, titleStack);
}
// fetch all the data necessary
let hbStatus = await getHomebridgeStatus(token);
let hbVersionInfos = await getHomebridgeVersionInfos(token);
let hbUpToDate = hbVersionInfos === undefined ? undefined : !hbVersionInfos.updateAvailable;
let pluginVersionInfos = await getPluginVersionInfos(token);
let pluginsUpToDate = pluginVersionInfos === undefined ? undefined : !pluginVersionInfos.updateAvailable;
let nodeJsVersionInfos = await getNodeJsVersionInfos(token);
let nodeJsUpToDate = nodeJsVersionInfos === undefined ? undefined : !nodeJsVersionInfos.updateAvailable;
if (usePersistedConfiguration || overwritePersistedConfig) {
// if here, the configuration seems valid -> save it for next time
log('The valid configuration ' + configurationFileName + ' has been saved. Changes can only be applied if overwritePersistedConfig is set to true. Should be set to false after applying changes again!')
persistObject(fm, CONFIGURATION, pathToConfig);
}
// STATUS PANEL IN THE HEADER ///////////////////
titleStack.addSpacer(CONFIGURATION.spacer_beforeFirstStatusColumn);
let statusInfo = titleStack.addStack();
let firstColumn = statusInfo.addStack();
firstColumn.layoutVertically();
addStatusInfo(firstColumn, hbStatus, CONFIGURATION.status_hbRunning);
firstColumn.addSpacer(5);
addStatusInfo(firstColumn, pluginsUpToDate, CONFIGURATION.status_pluginsUtd);
statusInfo.addSpacer(CONFIGURATION.spacer_betweenStatusColumns);
let secondColumn = statusInfo.addStack();
secondColumn.layoutVertically();
addStatusInfo(secondColumn, hbUpToDate, CONFIGURATION.status_hbUtd);
secondColumn.addSpacer(5);
addStatusInfo(secondColumn, nodeJsUpToDate, CONFIGURATION.status_nodejsUtd);
titleStack.addSpacer(CONFIGURATION.spacer_afterSecondColumn);
// STATUS PANEL IN THE HEADER END ////////////////
widget.addSpacer(10);
if (!config.runsWithSiri) {
await buildUsualGui(widget, token);
} else if (config.runsWithSiri) {
buildSiriGui(widget, hbVersionInfos, pluginVersionInfos, nodeJsVersionInfos);
}
if (CONFIGURATION.notificationEnabled) {
handleNotifications(fm, hbStatus, hbUpToDate, pluginsUpToDate, nodeJsUpToDate);
}
return widget;
}
function buildSiriGui(widget, hbVersionInfos, pluginVersionInfos, nodeJsVersionInfos) { let mainColumns = widget.addStack(); mainColumns.size = new Size(maxLineWidth, 100);
let verticalStack = mainColumns.addStack();
verticalStack.layoutVertically();
if (hbVersionInfos.updateAvailable || pluginVersionInfos.updateAvailable || nodeJsVersionInfos.updateAvailable) {
speakUpdateStatus(true);
addStyledText(verticalStack, CONFIGURATION.siriGui_title_update_available, infoFont);
if (hbVersionInfos.updateAvailable) {
verticalStack.addSpacer(5);
addUpdatableElement(verticalStack, CONFIGURATION.bulletPointIcon + hbVersionInfos.name + ': ', hbVersionInfos.installedVersion, hbVersionInfos.latestVersion);
}
if (pluginVersionInfos.updateAvailable) {
for (plugin of pluginVersionInfos.plugins) {
if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes(plugin.name)) {
continue;
}
if (plugin.updateAvailable) {
verticalStack.addSpacer(5);
addUpdatableElement(verticalStack, CONFIGURATION.bulletPointIcon + plugin.name + ': ', plugin.installedVersion, plugin.latestVersion);
}
}
}
if (nodeJsVersionInfos.updateAvailable) {
verticalStack.addSpacer(5);
addUpdatableElement(verticalStack, CONFIGURATION.bulletPointIcon + nodeJsVersionInfos.name + ': ', nodeJsVersionInfos.currentVersion, nodeJsVersionInfos.latestVersion);
}
} else {
speakUpdateStatus(false);
verticalStack.addSpacer(30);
addStyledText(verticalStack, CONFIGURATION.siriGui_title_all_UTD, infoFont);
}
}
function speakUpdateStatus(updateAvailable) { if (CONFIGURATION.enableSiriFeedback) { if (updateAvailable) { Speech.speak(CONFIGURATION.siri_spokenAnswer_update_available); } else { Speech.speak(CONFIGURATION.siri_spokenAnswer_all_UTD); } } }
async function buildUsualGui(widget, token) { let cpuData = await fetchData(token, cpuUrl()); let ramData = await fetchData(token, ramUrl()); let usedRamText = await getUsedRamString(ramData); let uptimesArray = await getUptimesArray(token); if (cpuData && ramData) { let mainColumns = widget.addStack(); mainColumns.size = new Size(maxLineWidth, 77); mainColumns.addSpacer(4)
// FIRST COLUMN //////////////////////
let firstColumn = mainColumns.addStack();
firstColumn.layoutVertically();
addTitleAboveChartToWidget(firstColumn, CONFIGURATION.title_cpuLoad + getAsRoundedString(cpuData.currentLoad, 1) + '%');
addChartToWidget(firstColumn, cpuData.cpuLoadHistory);
let temperatureString = getTemperatureString(cpuData?.cpuTemperature.main);
if (temperatureString !== 'unknown') {
let cpuTempText = addStyledText(firstColumn, CONFIGURATION.title_cpuTemp + temperatureString, infoFont);
cpuTempText.size = new Size(150, 30);
setTextColor(cpuTempText);
}
// FIRST COLUMN END //////////////////////
mainColumns.addSpacer(11);
// SECOND COLUMN //////////////////////
let secondColumn = mainColumns.addStack();
secondColumn.layoutVertically();
addTitleAboveChartToWidget(secondColumn, CONFIGURATION.title_ramUsage + usedRamText + '%');
addChartToWidget(secondColumn, ramData.memoryUsageHistory);
if (uptimesArray) {
let uptimesStack = secondColumn.addStack();
let upStack = uptimesStack.addStack();
addStyledText(upStack, CONFIGURATION.title_uptimes, infoFont);
let vertPointsStack = upStack.addStack();
vertPointsStack.layoutVertically();
addStyledText(vertPointsStack, CONFIGURATION.bulletPointIcon + CONFIGURATION.title_systemGuiName + uptimesArray[0], infoFont);
addStyledText(vertPointsStack, CONFIGURATION.bulletPointIcon + CONFIGURATION.title_uiService + uptimesArray[1], infoFont);
}
// SECOND COLUMN END//////////////////////
widget.addSpacer(10);
// BOTTOM UPDATED TEXT //////////////////////
let updatedAt = addStyledText(widget, 't: ' + timeFormatter.string(new Date()), updatedAtFont);
updatedAt.centerAlignText();
}
}
function addUpdatableElement(stackToAdd, elementTitle, versionCurrent, versionLatest) { let itemStack = stackToAdd.addStack(); itemStack.addSpacer(17); addStyledText(itemStack, elementTitle, infoFont);
let vertPointsStack = itemStack.addStack();
vertPointsStack.layoutVertically();
let versionStack = vertPointsStack.addStack();
addStyledText(versionStack, versionCurrent, infoFont);
versionStack.addSpacer(3);
addIcon(versionStack, CONFIGURATION.siriGui_icon_version, new Color(CONFIGURATION.siriGui_icon_version_color));
versionStack.addSpacer(3);
addStyledText(versionStack, versionLatest, infoFont);
}
function handleSettingOfBackgroundColor(widget) { if (!CONFIGURATION.adaptToLightOrDarkMode) { switch (CONFIGURATION.bgColorMode) { case "CUSTOM": widget.backgroundGradient = createLinearGradient(CONFIGURATION.customBackgroundColor1_light, CONFIGURATION.customBackgroundColor2_light); break; case "BLACK_LIGHT": widget.backgroundGradient = blackBgGradient_light; break; case "BLACK_DARK": widget.backgroundGradient = blackBgGradient_dark; break; case "PURPLE_DARK": widget.backgroundGradient = purpleBgGradient_dark; break; case "PURPLE_LIGHT": default: widget.backgroundGradient = purpleBgGradient_light; } } else { switch (CONFIGURATION.bgColorMode) { case "CUSTOM": setGradient(widget, createLinearGradient(CONFIGURATION.customBackgroundColor1_light, CONFIGURATION.customBackgroundColor2_light), createLinearGradient(CONFIGURATION.customBackgroundColor1_dark, CONFIGURATION.customBackgroundColor2_dark)); break; case "BLACK_LIGHT": case "BLACK_DARK": setGradient(widget, blackBgGradient_light, blackBgGradient_dark); break; case "PURPLE_DARK": case "PURPLE_LIGHT": default: setGradient(widget, purpleBgGradient_light, purpleBgGradient_dark); } } }
function setGradient(widget, lightOption, darkOption) { if (Device.isUsingDarkAppearance()) { widget.backgroundGradient = darkOption; } else { widget.backgroundGradient = lightOption; } }
function getChartColorToUse() { if (CONFIGURATION.adaptToLightOrDarkMode && Device.isUsingDarkAppearance()) { return new Color(CONFIGURATION.chartColor_dark); } else { return new Color(CONFIGURATION.chartColor_light); } }
function setTextColor(textWidget) { if (CONFIGURATION.adaptToLightOrDarkMode && Device.isUsingDarkAppearance()) { textWidget.textColor = new Color(CONFIGURATION.fontColor_dark); } else { textWidget.textColor = new Color(CONFIGURATION.fontColor_light); } }
function createLinearGradient(color1, color2) { const gradient = new LinearGradient(); gradient.locations = [0, 1]; gradient.colors = [new Color(color1), new Color(color2)]; return gradient; }
function addStyledText(stackToAddTo, text, font) { let textHandle = stackToAddTo.addText(text); textHandle.font = font; setTextColor(textHandle); return textHandle; }
function addTitleAboveChartToWidget(column, titleText) { let cpuLoadTitle = column.addText(titleText); cpuLoadTitle.font = infoFont; setTextColor(cpuLoadTitle); }
function addChartToWidget(column, chartData) { let horizontalStack = column.addStack(); horizontalStack.addSpacer(5); let yAxisLabelsStack = horizontalStack.addStack(); yAxisLabelsStack.layoutVertically();
addStyledText(yAxisLabelsStack, getMaxString(chartData, 2) + '%', chartAxisFont);
yAxisLabelsStack.addSpacer(6);
addStyledText(yAxisLabelsStack, getMinString(chartData, 2) + '%', chartAxisFont);
yAxisLabelsStack.addSpacer(6);
horizontalStack.addSpacer(2);
let chartImage = new LineChart(500, 100, chartData).configure((ctx, path) => {
ctx.opaque = false;
ctx.setFillColor(getChartColorToUse());
ctx.addPath(path);
ctx.fillPath(path);
}).getImage();
let vertChartImageStack = horizontalStack.addStack();
vertChartImageStack.layoutVertically();
let chartImageHandle = vertChartImageStack.addImage(chartImage);
chartImageHandle.imageSize = new Size(100, 25);
let xAxisStack = vertChartImageStack.addStack();
xAxisStack.size = new Size(100, 10);
addStyledText(xAxisStack, 't-10m', chartAxisFont);
xAxisStack.addSpacer(75);
addStyledText(xAxisStack, 't', chartAxisFont);
column.addSpacer(7);
}
function checkIfConfigFileParameterIsProvided(fm, givenParameter) { if (givenParameter.trim().startsWith('USE_CONFIG:') && givenParameter.trim().endsWith('.json')) { configurationFileName = givenParameter.trim().split('USE_CONFIG:')[1]; if (!fm.fileExists(getFilePath(configurationFileName, fm))) { throw('Config file with provided name ' + configurationFileName + ' does not exist!\nCreate it first by running the script once providing the name in variable configurationFileName and maybe with variable overwritePersistedConfig set to true'); } return true; } return false; }
function useCredentialsFromWidgetParameter(givenParameter) { if (givenParameter.includes(',,')) { let credentials = givenParameter.split(',,'); if (credentials.length === 3 && credentials[0].length > 0 && credentials[1].length > 0 && credentials[2].length > 0 && credentials[2].startsWith('http')) { CONFIGURATION.userName = credentials[0].trim(); CONFIGURATION.password = credentials[1].trim(); CONFIGURATION.hbServiceMachineBaseUrl = credentials[2].trim(); return true; } } return false; }
async function getAuthToken() { if (CONFIGURATION.hbServiceMachineBaseUrl === '>enter the ip with the port here<') { throw('Base URL to machine not entered! Edit variable called hbServiceMachineBaseUrl') } let req = new Request(noAuthUrl()); req.timeoutInterval = CONFIGURATION.requestTimeoutInterval; const headers = { 'accept': '\/', 'Content-Type': 'application/json' }; req.method = 'POST'; req.headers = headers; req.body = JSON.stringify({}); let authData; try { authData = await req.loadJSON(); } catch (e) { return UNAVAILABLE; } if (authData.access_token) { // no credentials needed return authData.access_token; }
req = new Request(authUrl());
req.timeoutInterval = CONFIGURATION.requestTimeoutInterval;
let body = {
'username': CONFIGURATION.userName,
'password': CONFIGURATION.password,
'otp': 'string'
};
req.body = JSON.stringify(body);
req.method = 'POST';
req.headers = headers;
try {
authData = await req.loadJSON();
} catch (e) {
return UNAVAILABLE;
}
return authData.access_token;
}
async function fetchData(token, url) { let req = new Request(url); req.timeoutInterval = CONFIGURATION.requestTimeoutInterval; let headers = { 'accept': '\/', 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }; req.headers = headers; let result; try { result = req.loadJSON(); } catch (e) { return undefined; } return result; }
async function getHomebridgeStatus(token) { const statusData = await fetchData(token, hbStatusUrl()); if (statusData === undefined) { return undefined; } return statusData.status === 'up'; }
async function getHomebridgeVersionInfos(token) { if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes('HOMEBRIDGE_UTD')) { log('You configured Homebridge to not be checked for updates. Widget will show that it\'s UTD!'); return {updateAvailable: false}; } const hbVersionData = await fetchData(token, hbVersionUrl()); if (hbVersionData === undefined) { return undefined; } return hbVersionData; }
async function getNodeJsVersionInfos(token) { if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes('NODEJS_UTD')) { log('You configured Node.js to not be checked for updates. Widget will show that it\'s UTD!'); return {updateAvailable: false}; } const nodeJsData = await fetchData(token, nodeJsUrl()); if (nodeJsData === undefined) { return undefined; } nodeJsData.name = 'node.js'; return nodeJsData; }
async function getPluginVersionInfos(token) { const pluginsData = await fetchData(token, pluginsUrl()); if (pluginsData === undefined) { return undefined; } for (plugin of pluginsData) { if (CONFIGURATION.pluginsOrSwUpdatesToIgnore.includes(plugin.name)) { log('You configured ' + plugin.name + ' to not be checked for updates. Widget will show that it\'s UTD!'); continue; } if (plugin.updateAvailable) { return {plugins: pluginsData, updateAvailable: true}; } } return {plugins: pluginsData, updateAvailable: false}; }
async function getUsedRamString(ramData) { if (ramData === undefined) return 'unknown'; return getAsRoundedString(100 - 100 * ramData.mem.available / ramData.mem.total, 2); }
async function getUptimesArray(token) { const uptimeData = await fetchData(token, uptimeUrl()); if (uptimeData === undefined) return undefined;
return [formatSeconds(uptimeData.time.uptime), formatSeconds(uptimeData.processUptime)];
}
function formatSeconds(value) { if (value > 60 60 24) { return getAsRoundedString(value / 60 / 60 / 24, 1) + 'd'; } else if (value > 60 * 60) { return getAsRoundedString(value / 60 / 60, 1) + 'h'; } else if (value > 60) { return getAsRoundedString(value / 60, 1) + 'm'; } else { return getAsRoundedString(value, 1) + 's'; } }
async function loadImage(imgUrl) { let req = new Request(imgUrl); req.timeoutInterval = CONFIGURATION.requestTimeoutInterval; let image = await req.loadImage(); return image; }
async function getHbLogo(fm) { let path = getFilePath(HB_LOGO_FILE_NAME, fm); if (fm.fileExists(path)) { return fm.readImage(path); } else { // logo did not exist -> download it and save it for next time the widget runs const logo = await loadImage(CONFIGURATION.logoUrl); fm.writeImage(path, logo); return logo; } }
function getFilePath(fileName, fm) { let dirPath = fm.joinPath(fm.documentsDirectory(), 'homebridgeStatus'); if (!fm.fileExists(dirPath)) { fm.createDirectory(dirPath); } return fm.joinPath(dirPath, fileName); }
function addNotAvailableInfos(widget, titleStack) { let statusInfo = titleStack.addText(' '); setTextColor(statusInfo); statusInfo.size = new Size(150, normalLineHeight); let errorText = widget.addText(CONFIGURATION.error_noConnectionText); errorText.size = new Size(410, 130); errorText.font = infoFont; setTextColor(errorText);
widget.addSpacer(15);
let updatedAt = widget.addText('t: ' + timeFormatter.string(new Date()));
updatedAt.font = updatedAtFont;
setTextColor(updatedAt);
updatedAt.centerAlignText();
return widget;
}
function getAsRoundedString(value, decimals) { let factor = Math.pow(10, decimals); return (Math.round((value + Number.EPSILON) * factor) / factor).toString().replace('.', CONFIGURATION.decimalChar); }
function getMaxString(arrayOfNumbers, decimals) { let factor = Math.pow(10, decimals); return (Math.round((Math.max(...arrayOfNumbers) + Number.EPSILON) * factor) / factor).toString().replace('.', CONFIGURATION.decimalChar); }
function getMinString(arrayOfNumbers, decimals) { let factor = Math.pow(10, decimals); return (Math.round((Math.min(...arrayOfNumbers) + Number.EPSILON) * factor) / factor).toString().replace('.', CONFIGURATION.decimalChar); }
function getTemperatureString(temperatureInCelsius) { if (temperatureInCelsius === undefined || temperatureInCelsius < 0) return 'unknown';
if (CONFIGURATION.temperatureUnitConfig === 'FAHRENHEIT') {
return getAsRoundedString(convertToFahrenheit(temperatureInCelsius), 1) + 'ยฐF';
} else {
return getAsRoundedString(temperatureInCelsius, 1) + 'ยฐC';
}
}
function convertToFahrenheit(temperatureInCelsius) { return temperatureInCelsius * 9 / 5 + 32; }
function addStatusIcon(widget, statusBool) { let name = ''; let color; if (statusBool === undefined) { name = CONFIGURATION.icon_statusUnknown; color = new Color(CONFIGURATION.icon_colorUnknown); } else if (statusBool) { name = CONFIGURATION.icon_statusGood; color = new Color(CONFIGURATION.icon_colorGood); } else { name = CONFIGURATION.icon_statusBad; color = new Color(CONFIGURATION.icon_colorBad); } addIcon(widget, name, color); }
function addStatusInfo(lineWidget, statusBool, shownText) { let itemStack = lineWidget.addStack(); addStatusIcon(itemStack, statusBool); itemStack.addSpacer(2); let text = itemStack.addText(shownText); text.font = Font.semiboldMonospacedSystemFont(10); setTextColor(text); }
function handleNotifications(fm, hbRunning, hbUtd, pluginsUtd, nodeUtd) { let path = getFilePath(NOTIFICATION_JSON_FILE_NAME, fm); let state = getPersistedObject(fm, path, NOTIFICATION_JSON_VERSION, INITIAL_NOTIFICATION_STATE, true); let now = new Date(); let shouldUpdateState = false; if (shouldNotify(hbRunning, state.hbRunning.status, state.hbRunning.lastNotified)) { state.hbRunning.status = hbRunning; state.hbRunning.lastNotified = now; shouldUpdateState = true; scheduleNotification(CONFIGURATION.notifyText_hbNotRunning); } else if (hbRunning && !state.hbRunning.status) { state.hbRunning.status = hbRunning; state.hbRunning.lastNotified = undefined; shouldUpdateState = true; if (!CONFIGURATION.disableStateBackToNormalNotifications) { scheduleNotification(CONFIGURATION.notifyText_hbNotRunning_backNormal); } }
if (shouldNotify(hbUtd, state.hbUtd.status, state.hbUtd.lastNotified)) {
state.hbUtd.status = hbUtd;
state.hbUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification(CONFIGURATION.notifyText_hbNotUtd);
} else if (hbUtd && !state.hbUtd.status) {
state.hbUtd.status = hbUtd;
state.hbUtd.lastNotified = undefined;
shouldUpdateState = true;
if (!CONFIGURATION.disableStateBackToNormalNotifications) {
scheduleNotification(CONFIGURATION.notifyText_hbNotUtd_backNormal);
}
}
if (shouldNotify(pluginsUtd, state.pluginsUtd.status, state.pluginsUtd.lastNotified)) {
state.pluginsUtd.status = pluginsUtd;
state.pluginsUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification(CONFIGURATION.notifyText_pluginsNotUtd);
} else if (pluginsUtd && !state.pluginsUtd.status) {
state.pluginsUtd.status = pluginsUtd;
state.pluginsUtd.lastNotified = undefined;
shouldUpdateState = true;
if (!CONFIGURATION.disableStateBackToNormalNotifications) {
scheduleNotification(CONFIGURATION.notifyText_pluginsNotUtd_backNormal);
}
}
if (shouldNotify(nodeUtd, state.nodeUtd.status, state.nodeUtd.lastNotified)) {
state.nodeUtd.status = nodeUtd;
state.nodeUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification(CONFIGURATION.notifyText_nodejsNotUtd);
} else if (nodeUtd && !state.nodeUtd.status) {
state.nodeUtd.status = nodeUtd;
state.nodeUtd.lastNotified = undefined;
shouldUpdateState = true;
if (!CONFIGURATION.disableStateBackToNormalNotifications) {
scheduleNotification(CONFIGURATION.notifyText_nodejsNotUtd_backNormal);
}
}
if (shouldUpdateState) {
persistObject(fm, state, path);
}
}
function shouldNotify(currentBool, boolFromLastTime, lastNotifiedDate) { return (!currentBool && (boolFromLastTime || isTimeToNotifyAgain(lastNotifiedDate))); }
function isTimeToNotifyAgain(dateToCheck) { if (dateToCheck === undefined) return true;
let dateInThePast = new Date(dateToCheck);
let now = new Date();
let timeBetweenDates = parseInt((now.getTime() - dateInThePast.getTime()) / 1000); // seconds
return timeBetweenDates > CONFIGURATION.notificationIntervalInDays * 24 * 60 * 60;
}
function scheduleNotification(text) { let not = new Notification(); not.title = CONFIGURATION.notification_title; not.body = text; not.addAction(CONFIGURATION.notification_expandedButtonText, CONFIGURATION.hbServiceMachineBaseUrl, false); not.sound = CONFIGURATION.notification_ringTone; not.schedule(); }
function getPersistedObject(fm, path, versionToCheckAgainst, initialObjectToPersist, createIfNotExisting) { if (fm.fileExists(path)) { let raw, persistedObject; try { raw = fm.readString(path); persistedObject = JSON.parse(raw); } catch (e) { // file corrupted -> remove it fm.remove(path); }
if (persistedObject && (persistedObject.jsonVersion === undefined || persistedObject.jsonVersion < versionToCheckAgainst)) {
// the version of the json file is outdated -> remove it and recreate it
log('Unfortunately, the configuration structure changed and your old config is not compatible anymore. It is now removed and a new one is created with the initial configuration. ')
fm.remove(path);
} else {
return persistedObject;
}
}
if (createIfNotExisting) {
// create a new state json
persistObject(fm, initialObjectToPersist, path);
}
return initialObjectToPersist;
}
function persistObject(fm, object, path) { let raw = JSON.stringify(object); fm.writeString(path, raw); }
function addIcon(widget, name, color) { let sf = SFSymbol.named(name); sf.applyFont(Font.heavySystemFont(50)); let iconImage = sf.image; let imageWidget = widget.addImage(iconImage); imageWidget.resizable = true; imageWidget.imageSize = new Size(13, 13); imageWidget.tintColor = color; }
Yes, I can reach via URL
Wow thatโs like the whole script...i just meant the code that was covered by the log window since this is where the url is configured. You still have not http:// in front of the ip as i mentioned in the last message. this is problem...
Sorry, I will copy right, in the mind time I don't know if the double factor has security has something to do?
Yes, if you have 2FA activated, then it probably wonโt work. Itโs not specifically designed to support this
I will enable it, and let you know works or not.
There is already an issue about that. Just read through the open and closed issues
![Uploading 100146713...]
It works !!! There were two issues, one the double factor and the second was the missing of the "http://" I've written only the IP, now is all correct ! Thank you for your time and for a wonderful widget
Glad it works ๐๐ป In case you wonder: The temperature is not shown for you, because your system does not provide this info. Will close the issue now
Thank you
Good afternoon,
I have been following all instructions, created the script, but appears to me the message UI-service not reachable. See Pic attached, also appear a in Log configuration iphoneconfig.json has been loaded and is used! but I can't see anything in the widget.
How can I fix it?