Skip to content

Instantly share code, notes, and snippets.

@fvdm
Last active June 17, 2024 15:39
Show Gist options
  • Save fvdm/d2c36c9c26e3df8a56fdc12d943aa469 to your computer and use it in GitHub Desktop.
Save fvdm/d2c36c9c26e3df8a56fdc12d943aa469 to your computer and use it in GitHub Desktop.
HomeyScript for saving FritzBox WAN speed counters in Homey variables
/**
* Request bandwidth monitor stats from Fritz!Box
*
* Values are converted to megabits per second
* The user must have permission to access the Fritz!Box settings
* Set the existing number-variable names in the `saveMap` object keys
*
* @see {@link https://gist.github.com/fvdm/d2c36c9c26e3df8a56fdc12d943aa469}
*/
// Fritz!Box login
const ip = '192.168.188.1';
const username = 'homey';
const password = 'top-secret';
const timeout = 2000;
const decimals = 3;
const seconds = parseInt( args[0], 10 ) || 10; // range per 5 sec
// Homey variable name : data property
const saveMap = {
'WAN speed max down': 'Newmax_ds',
'WAN speed max up': 'Newmax_us',
'WAN speed now down': 'seconds_average_down',
'WAN speed now up': 'seconds_average_up',
};
// Basics
const crypto = ( this.constructor.constructor( 'return process.mainModule.require' )() )( 'crypto' );
const uri = '/upnp/control/wancommonifconfig1';
const url = `http://${ip}:49000${uri}`;
const body = '<?xml version="1.0" encoding="utf-8" standalone="no"?><s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><u:X_AVM-DE_GetOnlineMonitor xmlns:u="urn:dslforum-org:service:WANCommonInterfaceConfig:1"><NewSyncGroupIndex>0</NewSyncGroupIndex></u:X_AVM-DE_GetOnlineMonitor></s:Body></s:Envelope>';
const headers = {
'Connection': 'keep-alive',
'Content-Type': 'text/xml; charset="utf-8"',
'SOAPAction': 'urn:dslforum-org:service:WANCommonInterfaceConfig:1#X_AVM-DE_GetOnlineMonitor',
};
// Calculate array stats
function calc ( arr, seconds, stat ) {
arr = arr.slice( 0, seconds / 5 + 1 );
return stat === 'max' ? _.max( arr ) : _.round( _.mean( arr ), decimals );
}
// MD5 hash
function md5 ( str ) {
return crypto.createHash( 'md5' ).update( str ).digest( 'hex' );
}
// Request auth digest
const unauth = await Promise.race( [
fetch( url, { method: 'POST', headers, body } ),
wait( timeout )
] );
// Parse digest
const details = { username, uri, nc: '00000001' };
await unauth.headers.get( 'WWW-Authenticate' )
.replace( /^Digest /, '' ).split( ',' )
.forEach( param => {
param = param.split( '=' );
param[1] = param[1].replace( /^"([^"]+)"$/, '$1' );
details[param[0]] = param[1];
} )
;
// Extend digest
const HA1 = md5( `${username}:${details.realm}:${password}` );
const HA2 = md5( `POST:${uri}` );
details.cnonce = md5( Date.now().toString() );
details.response = md5( [HA1, details.nonce, details.nc, details.cnonce, details.qop, HA2].join( ':' ) );
// Build new digest
let digest = [];
for ( let key in details ) {
digest.push( `${key}="${details[key]}"` );
}
digest = digest.join( ',' );
// Request data
const res = await Promise.race( [
fetch( url, {
method: 'POST',
headers: {
'Authorization': 'Digest '+ digest,
...headers,
},
body,
} ),
wait( timeout )
] );
// Parse data
const xml = await res.text();
const data = {};
[...xml.matchAll( /<(Newmax_[du]s|New[du]s_current_bps)>([\d,]+)<\/\1>/g )].forEach( re => {
const key = re[1].replace( /_bps$/, '_mbps' );
let val = re[2].split( ',' );
val.forEach( ( el, i ) => {
val[i] = _.round( parseInt( el, 10 ) * 8 / 1024 / 1024, decimals );
} );
data[key] = val.length >= 2 ? val : val[0];
} );
data.current_down = data.Newds_current_mbps[0];
data.current_up = data.Newus_current_mbps[0];
data.seconds_average_down = calc( data.Newds_current_mbps, seconds );
data.seconds_average_up = calc( data.Newus_current_mbps, seconds );
data.seconds_max_down = calc( data.Newds_current_mbps, seconds, 'max' );
data.seconds_max_up = calc( data.Newus_current_mbps, seconds, 'max' );
log( data );
// Load variables
const vars = await Homey.logic.getVariables();
function getVar ( name ) {
const key = _.findKey( vars, o => o.name === name );
return vars[key];
};
// Only update the better data
let dataKey, theVar, save = [];
for ( let varKey in saveMap ) {
dataKey = saveMap[varKey];
theVar = getVar( varKey );
log( varKey );
if ( !!~varKey.indexOf( 'WAN speed max' ) && data[dataKey] > theVar.value ) {
log( ' update' );
save.push( Homey.logic.updateVariable( {
id: theVar.id,
variable: { value: data[dataKey] },
} ) );
}
else if ( !!~varKey.indexOf( 'WAN speed now' ) && data[dataKey] !== theVar.value ) {
log( ' update' );
save.push( Homey.logic.updateVariable( {
id: theVar.id,
variable: { value: data[dataKey] },
} ) );
}
}
await Promise.all( save );
// Done
return true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment