Skip to content

Commit

Permalink
- added support for notifications when some status changed
Browse files Browse the repository at this point in the history
- supported: Homebridge stopped, Homebridge Update available, Plugin Update available, Node.js Update available
- new screenshots
- added changelog
- some filemanager usage refactoring
  • Loading branch information
Lukas Witzani committed Nov 16, 2020
1 parent e86b94f commit 9d3ef86
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 39 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
I try to list the changes i make in this changelog:

#16.11.2020 20:39
- added support for notifications when some status changed
- supported: Homebridge stopped, Homebridge Update available, Plugin Update available, Node.js Update available
- new screenshots
- added changelog
- some filemanager usage refactoring

#15.11.2020 14:26
- overhaul of the status icons, now not using emoji anymore but SFSymbols
- more handling when some requests return undefined
- new screenshots


#15.11.2020 11:25
- fixed the black text color when user uses light mode (now text is always white)
- added unknown status if API requests return undefined
- now user can choose between default purple background and a black background
- now user can set the icons used at a central spot


#14.11.2020 21:44
- added possibility to switch the file manager to local via a variable


#14.11.2020 21:26
- added support to show temperature in Fahrenheit
- more version infos


#14.11.2020 20:48
- just added some infos about the versions of all the systems


#14.11.2020 20:36
- fixed a critical bug (forgot to include the logic for node.js UTD)


#14.11.2020 18:16
- initial commit of the first version of the script
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![](widget_purple.jpg)
![](images/widget_purple.jpg)

# Homebridge Status Widget
- Script for the iOS App Scriptable that shows a small summary of your Homebridge instance
Expand All @@ -18,12 +18,31 @@
- e.g. the systemGuiName, the name of your system running the Homebridge Config UI X (the hb-service)
- e.g. the timeout could be increased if your system does not respond within 1 second
- e.g. set the temperatureUnitConfig to 'FAHRENHEIT' to use °F instead of °C
- if your homebridge-config-ui-x instance is not reached within the specified timeout (currently 1sec) the following screen is shown: ![](notAvailable_purple.jpg)
- if your homebridge-config-ui-x instance is not reached within the specified timeout (currently 1sec) the following screen is shown: ![](images/notAvailable_purple.jpg)

# Notifications
- the widget now can notify you when a status has changed
- you will get a notification if:
- your Homebridge stopped running
- there is an update available for Homebridge
- there is an update available for one of your plugins
- there is an update available for node.js
- disable notifications by setting notificationEnabled to false
- edit the variable notificationIntervalInDays to lengthen or shorten the time between getting the same notification (e.g. plugin update available) again
- 0 means you get a notification every time the script runs (not recommended)
- 1 means you get each possible notification to a maximum of 1 time per day
- 0.5 means you get each possible notification to a maximum of 2 times per day
- Open a notification to reveal the "Show me!" button which takes you directly to Homebridge Config UI X
- Here are some screenshots:
![](images/notification_plugin_update.jpeg)
![](images/notification_homebridge_update.jpeg)
![](images/notification_homebridge_stopped.jpg)
![](images/notification_homebridge_stopped_extended.jpg)

# Styling
- at the top of the script there is a variable bgColorMode that you can set to 'BLACK' to use the black variant which looks as the following:
![](widget_black.jpg)
![](notAvailable_black.jpg)
![](images/widget_black.jpg)
![](images/notAvailable_black.jpg)
- you can also change the icons failIcon = ❌ and bulletPointIcon = 🔸 by providing any other emoji

# Infos shown in the widget
Expand All @@ -38,10 +57,11 @@
- Uptime for the hb-service (Homebridge Config UI X)

# Troubleshoot
- triple check the credentials (2FA currently not supported)
- consider increasing the requestTimeoutInterval variable
- if some error occurs always check that you have the matching versions
- the Scriptable app 1.6.1
- Homebridge Config UI X 4.32.0 (2020-11-06)
- Homebridge 1.1.6
- iOS 14.2
- if your Homebridge Config UI X is reachable and the authentication process succeeded but the further API requests take to long or fail you will get a screen similar to ![](unknown.jpg)
- if your Homebridge Config UI X is reachable and the authentication process succeeded but the further API requests take to long or fail you will get a screen similar to ![](images/unknown.jpg)
178 changes: 144 additions & 34 deletions homebridgeStatusWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const hbServiceMachineBaseUrl = '>enter the ip with the port here<'; // location
const userName = '>enter username here<'; // username of administrator of the hb-service
const password = '>enter password here<'; // password of administrator of the hb-service

const notificationEnabled = true; // set to false to disable all notifications
const 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
const systemGuiName = 'Raspberry Pi'; // name of the system your service is running on
const fileManagerMode = 'ICLOUD'; // default is ICLOUD. If you don't use iCloud Drive use option LOCAL
const bgColorMode = 'PURPLE'; // default is PURPLE. Second option is BLACK
Expand Down Expand Up @@ -40,19 +42,21 @@ const headerFont = Font.boldMonospacedSystemFont(12);
const infoFont = Font.systemFont(10);
const chartAxisFont = Font.systemFont(7);
const updatedAtFont = Font.systemFont(7);
const fontColorWhite = new Color("#FFFFFF");
const bgColorPurple = new Color("#421367");
const bgColorBrighterPurple = new Color("#481367");
const fontColorWhite = new Color('FFFFFF');
const bgColorPurple = new Color('#421367');
const bgColorBrighterPurple = new Color('#481367');
const purpleBgGradient = new LinearGradient();
purpleBgGradient.locations = [0, 1];
purpleBgGradient.colors = [bgColorPurple, bgColorBrighterPurple];
const blackBgGradient = new LinearGradient();
blackBgGradient.locations = [0, 1];
blackBgGradient.colors = [new Color("111111"), new Color("222222")];
blackBgGradient.colors = [new Color('111111'), new Color('222222')];

const chartColor = new Color("#FFFFFF");
const chartColor = new Color('#FFFFFF');
const UNAVAILABLE = 'UNAVAILABLE';

const NOTIFICATION_JSON_VERSION = 1; // never change this!

class LineChart {
// LineChart by https://kevinkub.de/
// taken from https://gist.github.com/kevinkub/b74f9c16f050576ae760a7730c19b8e2
Expand Down Expand Up @@ -120,6 +124,9 @@ Script.complete();


async function createWidget() {
// fileManagerMode must be LOCAL if you do not use iCloud drive
let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();

// authenticate against the hb-service
let token = await getAuthToken();

Expand All @@ -138,7 +145,7 @@ async function createWidget() {
// LOGO AND HEADER //////////////////////
let titleStack = widget.addStack();
titleStack.size = new Size(maxLineWidth, normalLineHeight);
const logo = await getHbLogo();
const logo = await getHbLogo(fm);
const imgWidget = titleStack.addImage(logo);
imgWidget.imageSize = new Size(40, 30);

Expand Down Expand Up @@ -178,7 +185,7 @@ async function createWidget() {
let secondLine = statusInfo.addStack();
secondLine.addSpacer(15);
addStatusInfo(secondLine, pluginsUpToDate, 'Plugins UTD');
secondLine.addSpacer();
secondLine.addSpacer(9);

addStatusInfo(secondLine, nodeJsUpToDate, 'Node.js UTD');
// STATUS PANEL IN THE HEADER END ////////////////
Expand Down Expand Up @@ -262,22 +269,25 @@ async function createWidget() {
updatedAt.centerAlignText();
// BOTTOM UPDATED TEXT END //////////////////

if (notificationEnabled) {
handleNotifications(fm, hbStatus, hbUpToDate, pluginsUpToDate, nodeJsUpToDate);
}
return widget;
}

async function getAuthToken() {
let req = new Request(authUrl);
req.timeoutInterval = requestTimeoutInterval;
let body = {
"username": userName,
"password": password,
"otp": "string"
'username': userName,
'password': password,
'otp': 'string'
};
let headers = {
"accept": "*\/*", "Content-Type": "application/json"
'accept': '*\/*', 'Content-Type': 'application/json'
};
req.body = JSON.stringify(body);
req.method = "POST";
req.method = 'POST';
req.headers = headers;
let authData;
try {
Expand All @@ -292,8 +302,8 @@ async function fetchData(token, url) {
let req = new Request(url);
req.timeoutInterval = requestTimeoutInterval;
let headers = {
"accept": "*\/*", "Content-Type": "application/json",
"Authorization": "Bearer " + token
'accept': '*\/*', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
};
req.headers = headers;
let result;
Expand Down Expand Up @@ -374,38 +384,26 @@ async function loadImage(imgUrl) {
return image;
}

async function getHbLogo() {
// fileManagerMode must be LOCAL if you do not use iCloud drive
let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
let path = getStoredLogoPath();
async function getHbLogo(fm) {
let path = getStoredLogoPath(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(logoUrl);
saveHbLogo(logo);
fm.writeImage(path, logo);
return logo;
}
}

function saveHbLogo(image) {
// fileManagerMode must be LOCAL if you do not use iCloud drive
let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
let path = getStoredLogoPath();
fm.writeImage(path, image);
}

function getStoredLogoPath() {
// fileManagerMode must be LOCAL if you do not use iCloud drive
let fm = fileManagerMode === 'LOCAL' ? FileManager.local() : FileManager.iCloud();
let dirPath = fm.joinPath(fm.documentsDirectory(), "homebridgeStatus");
function getStoredLogoPath(fm) {
let dirPath = fm.joinPath(fm.documentsDirectory(), 'homebridgeStatus');
if (!fm.fileExists(dirPath)) {
fm.createDirectory(dirPath);
}
return fm.joinPath(dirPath, "hbLogo.png");
return fm.joinPath(dirPath, 'hbLogo.png');
}


function addNotAvailableInfos(widget, titleStack) {
let statusInfo = titleStack.addText(' ');
statusInfo.textColor = fontColorWhite;
Expand Down Expand Up @@ -441,7 +439,7 @@ function getMinString(arrayOfNumbers, decimals) {
}

function getTemperatureString(temperatureInCelsius) {
if (temperatureInCelsius === undefined) return 'unknown';
if (temperatureInCelsius === undefined || temperatureInCelsius < 0) return 'unknown';

if (temperatureUnitConfig === 'FAHRENHEIT') {
return getAsRoundedString(convertToFahrenheit(temperatureInCelsius), 1) + '°F';
Expand All @@ -451,7 +449,7 @@ function getTemperatureString(temperatureInCelsius) {
}

function convertToFahrenheit(temperatureInCelsius) {
return temperatureInCelsius * 9 / 5 + 32
return temperatureInCelsius * 9 / 5 + 32;
}

function addStatusIcon(widget, statusBool) {
Expand Down Expand Up @@ -484,4 +482,116 @@ function addStatusInfo(lineWidget, statusBool, shownText) {
let text = itemStack.addText(shownText);
text.font = Font.semiboldMonospacedSystemFont(10);
text.textColor = fontColorWhite;
}

function handleNotifications(fm, hbRunning, hbUtd, pluginsUtd, nodeUtd) {
let path = getStoredNotificationStatePath(fm);
let state = getNotificationState(fm, path);
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('Your Homebridge instance stopped 😱');
}
if (shouldNotify(hbUtd, state.hbUtd.status, state.hbUtd.lastNotified)) {
state.hbUtd.status = hbUtd;
state.hbUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification('Update available for Homebridge 😎');
}
if (shouldNotify(pluginsUtd, state.pluginsUtd.status, state.pluginsUtd.lastNotified)) {
state.pluginsUtd.status = pluginsUtd;
state.pluginsUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification('Update available for one of your Plugins 😎');
}
if (shouldNotify(nodeUtd, state.nodeUtd.status, state.nodeUtd.lastNotified)) {
state.nodeUtd.status = nodeUtd;
state.nodeUtd.lastNotified = now;
shouldUpdateState = true;
scheduleNotification('Update available for Node.js 😎');
}

if (shouldUpdateState) {
saveNotificationState(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 > notificationIntervalInDays * 24 * 60 * 60;
}

function scheduleNotification(text) {
let not = new Notification();
not.title = 'Homebridge Status changed:'
not.body = text;
not.addAction('Show me!', hbServiceMachineBaseUrl, false)
not.sound = 'event';
not.schedule();
}

function getNotificationState(fm, path) {
if (fm.fileExists(path)) {
let raw, savedState;
try {
raw = fm.readString(path);
savedState = JSON.parse(raw);
} catch (e) {
// file corrupted -> remove it
fm.remove(path);
}

if (savedState && savedState.jsonVersion === undefined || savedState.jsonVersion < NOTIFICATION_JSON_VERSION) {
// the version of the json file is outdated -> remove it and recreate it
fm.remove(path);
} else {
return savedState;
}
}
// create a new state json
let state = {
'jsonVersion': NOTIFICATION_JSON_VERSION,
'hbRunning': {
'status': true,
'lastNotified': undefined
},
'hbUtd': {
'status': true,
'lastNotified': undefined
},
'pluginsUtd': {
'status': true,
'lastNotified': undefined
},
'nodeUtd': {
'status': true,
'lastNotified': undefined
}
};
saveNotificationState(fm, state, path);
return state;
}

function saveNotificationState(fm, state, path) {
let raw = JSON.stringify(state);
fm.writeString(path, raw);
}

function getStoredNotificationStatePath(fm) {
let dirPath = fm.joinPath(fm.documentsDirectory(), 'homebridgeStatus');
if (!fm.fileExists(dirPath)) {
fm.createDirectory(dirPath);
}
return fm.joinPath(dirPath, 'notificationState.json');
}
File renamed without changes
File renamed without changes
Binary file added images/notification_homebridge_stopped.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/notification_homebridge_update.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/notification_plugin_update.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
File renamed without changes

0 comments on commit 9d3ef86

Please sign in to comment.