To check the connectivity, I use the logreader binding to trigger a connection test.
In the logreader binding, I connected a string item ‘Logfile_newest_custom_log’ to the lastCustomEvent channel.
Then with a JavaScript rule I set a switch ‘VirtualSwitch_OpenHabCloudConnected’ to ON or OFF:
rules.JSRule({
name: 'OpenhabLog_Watcher',
triggers: [triggers.ChannelEventTrigger('logreader:reader:27b968b02e:newCustomEvent')],
execute: () => {
const lastEvent = items.getItem('Logfile_newest_custom_log').state;
if (lastEvent.includes('Disconnected from the openHAB Cloud service')) {
items.getItem('VirtualSwitch_OpenHabCloudConnected').sendCommand('OFF');
}
if (lastEvent.includes('Connected to the openHAB Cloud service')) {
items.getItem('VirtualSwitch_OpenHabCloudConnected').sendCommand('ON');
}
},
});
With another rule and UPNP functionality, I check a) if I can get the public IP from my router and b) if a specific IP service can be requested:
// Needs to be determined with upnpc -s
const LOCAL_UPNP = { PORT: 56688, URL: 'http://192.168.1.1' };
/**
* Get the current external IP from the router via UPnP
* @returns {string|null} The current public IP address or null if retrieval fails.
*/
const getIpLocally = function () {
// SOAP request for UPnP WANIPConnection service
const soapEnvelope = `
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1" />
</s:Body>
</s:Envelope>
`;
// Headers for the SOAP request
const headers = {
'Content-Type': 'text/xml; charset="utf-8"',
'SOAPAction': '"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"',
'Connection': 'Close',
};
// UPnP service URL (adjust IP, port, and control path as needed)
const upnpUrl = `${LOCAL_UPNP.URL}:${LOCAL_UPNP.PORT}/upnp/control/WANIPConn1`;
/**
* Extracts the public IP address from a UPnP SOAP response using regex.
* @param {string} xml The SOAP response XML.
* @returns {string|null} The public IP address or null if extraction fails.
*/
const extractIpFromUpnpResponse = function (xml) {
try {
// Regex to match the IP address in the SOAP response
const ipRegex = /<NewExternalIPAddress>([\d.]+)<\/NewExternalIPAddress>/;
const match = xml.match(ipRegex);
if (match && match[1]) {
return match[1];
}
console.warn('No IP found in UPnP response.');
} catch (e) {
console.error(`Error parsing UPnP response: ${e}`);
}
return null;
};
try {
console.debug('Fetching public IP via UPnP...');
const response = actions.HTTP.sendHttpPostRequest(upnpUrl, 'text/xml', soapEnvelope, headers, 10000);
if (response) {
const ip = extractIpFromUpnpResponse(response);
if (ip) {
console.debug(`Public IP fetched via UPnP: ${ip}`);
return ip;
}
console.warn('Failed to get external IP locally');
}
} catch (e) {
console.error(`Error fetching IP via UPnP: ${e}`);
}
return null;
};
const IP_SERVICES = [
{ name: 'ipwho.de', url: 'https://4.ipwho.de/ip' },
{ name: 'myip.wtf', url: 'https://myip.wtf/text' },
{ name: 'ipapi.is', url: 'https://api.ipapi.is/ip' },
];
/**
* Shuffles a list
* @param {Object} arr The list that should be shuffled
* @returns {Object} The shuffled list
*/
const shuffle = function (arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
};
let currentlyOffline = false;
rules.JSRule({
name: 'Online_Watcher',
triggers: [triggers.ItemStateChangeTrigger('VirtualSwitch_OpenHabCloudConnected')],
execute: (event) => {
if (event.newState === 'OFF') {
let result = null;
const url = shuffle(IP_SERVICES)[0].url;
try {
// Double check connectivity
result = getIpLocally();
if (result === null) {
result = actions.HTTP.sendHttpGetRequest(url, httpHeaderHashMap, 10000);
}
} catch (e) {
console.warn(e);
}
if (result === null) {
currentlyOffline = true;
sendNotificationLaMetric({
text: 'No online connection!',
type: 'alert',
icon: 'i29040', //Network-Offline
sound: 'sound_picker_track_245_eerie',
});
console.warn('ONLINE-STATE: Currently offline!');
}
}
if (event.newState === 'ON' && currentlyOffline) {
currentlyOffline = false;
sendNotificationLaMetric({
text: 'Online connection is back again!',
type: 'info',
icon: 'i29041', //Network-Online
sound: 'sound_picker_track_402_reveal',
});
console.info('ONLINE-STATE: Online again!');
}
},
});
If internet is gone, I send a notification to my Lametric Time with a custom function. But you could also store the information in a switch item instead.