Last active
June 17, 2024 15:39
-
-
Save fvdm/d2c36c9c26e3df8a56fdc12d943aa469 to your computer and use it in GitHub Desktop.
HomeyScript for saving FritzBox WAN speed counters in Homey variables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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