homebridge-accessory-bandwidth-quality
Version:
A bandwidth quality accessory plugin for Homebridge: https://github.com/nfarina/homebridge
204 lines (160 loc) • 8.29 kB
JavaScript
/* jshint asi: true, esversion: 6, node: true, laxbreak: true, laxcomma: true, undef: true, unused: true */
const FastSpeedtest = require('fast-speedtest-api')
, NodeCache = require('node-cache')
, debug = require('debug')('bandwidth-quality')
, moment = require('moment')
, os = require('os')
, underscore = require('underscore')
module.exports = function (homebridge) {
const Characteristic = homebridge.hap.Characteristic
, Service = homebridge.hap.Service
, qualities = { excellent: 0.90, good: 0.75, fair: 0.67, inferior: 0.50 }
, units = [ 'bps', 'Kbps', 'Mbps', 'Gbps', 'Bps', 'KBps', 'MBps', 'GBps' ]
homebridge.registerAccessory("homebridge-accessory-bandwidth-quality", "bandwidth-quality", BandwidthQuality)
function BandwidthQuality(log, config) {
if (!(this instanceof BandwidthQuality)) return new BandwidthQuality(log, config)
let nominal, oopsP, quality
this.log = log
this.config = config || { accessory: 'bandwidth-quality' }
if (!(this.config.nominal && this.config.token)) throw new Error('Missing configuration')
nominal = this.config.nominal
if (typeof nominal === 'number') nominal = { download: { unit: 'Mbps', value: nominal } }
else if (nominal.unit) nominal = { download: nominal }
else if (nominal.units) nominal = { download: { unit: nominal.units, value: nominal.value } }
oopsP = !nominal.download
underscore.keys(nominal).forEach((key) => {
const pair = nominal[key]
oopsP |= (units.indexOf(pair.unit) === -1) || (!Number.isInteger(pair.value)) || (pair.value <= 0)
})
this.config.nominal = nominal
quality = this.config.quality || qualities
underscore.keys(quality).forEach((key) => {
oopsP |= (isNaN(parseFloat(quality[key]))) || (quality[key] <= 0.0) || (quality[key] > 1.0)
})
oopsP |= (quality.excellent <= quality.good) || (quality.good <= quality.fair) || (quality.fair <= quality.inferior)
quality.poor = 0.0
this.config.quality = quality
let lower = 500, upper = 1.0, ranges = {}
for (let q of [ 'excellent', 'good', 'fair', 'inferior', 'poor' ]) {
const key = Characteristic.AirQuality[q.toUpperCase()]
, high = { excellent: 700, good: 1100, fair: 1600, inferior: 2100, poor: 5000 }
ranges[key] = { quality : { lower: quality[q], upper: upper, delta: upper - quality[q] }
, ppm : { lower: lower, upper: high[q], delta: high[q] - lower }
}
upper = quality[q]
lower = high[q] + 1
}
this.config.ranges = ranges
debug('config', JSON.stringify(this.config, null, 2))
if (oopsP) throw new Error('Invalid configuration')
this.name = this.config.name
this.options = underscore.defaults(this.config.options || {}, { ttl: 600, verbose: false })
if (this.options.ttl < 300) this.options.ttl = 600
debug('options', this.options)
this.speedtest = new FastSpeedtest({ token: this.config.token, verbose: this.options.verbose })
this.cache = new NodeCache({ stdTTL: this.options.ttl })
this.statusFault = Characteristic.StatusFault.NO_FAULT
}
BandwidthQuality.prototype =
{ fetchQuality: function (callback) {
const self = this
self._fetchQuality((err, result) => {
self.statusFault = (err || (!result)) ? Characteristic.StatusFault.GENERAL_FAULT : Characteristic.StatusFault.NO_FAULT
if (callback) callback(err, result)
})
}
, _fetchQuality: function (callback) {
const self = this
self.cache.get('bandwidth-quality', (err, result) => {
if (err || result) return callback(err, result)
self.speedtest.getSpeed().then((result) => {
if (!result) return callback()
const download = FastSpeedtest.UNITS[self.config.nominal.download.unit](result)
, nominal = self.config.nominal.download.value
, quality = download >= Math.round(nominal * self.config.quality.excellent) ? Characteristic.AirQuality.EXCELLENT
: download >= Math.round(nominal * self.config.quality.good) ? Characteristic.AirQuality.GOOD
: download >= Math.round(nominal * self.config.quality.fair) ? Characteristic.AirQuality.FAIR
: download >= Math.round(nominal * self.config.quality.inferior) ? Characteristic.AirQuality.INFERIOR
: Characteristic.AirQuality.POOR
, actual = (download >= nominal) ? 1.0 : (download / nominal)
, humidity = actual * 100
, range = self.config.ranges[quality]
, ppm = Math.round(range.ppm.lower + ((range.ppm.delta * (range.quality.upper - actual)) / range.quality.delta))
self.historyService.addEntry({ time: moment().unix(), ppm, humidity })
result = { quality, download, ppm, humidity }
self.cache.set('bandwidth-quality', result)
debug('getSpeed', result)
callback(null, result)
}).catch((err) => {
self.log.error('FastSpeedtest error: ' + err.toString())
return callback(err)
})
})
}
, getAirQuality: function (callback) {
this.fetchQuality((err, result) => {
callback(err, result && result.quality)
})
}
, getStatusFault: function (callback) {
callback(null, this.statusFault)
}
, getDownloadSpeed: function (callback) {
this.fetchQuality((err, result) => {
callback(err, result && result.download)
})
}
, getEveAirQuality: function (callback) {
this.fetchQuality((err, result) => {
callback(err, result && result.ppm)
})
}
, getCurrentRelativeHumidity: function (callback) {
this.fetchQuality((err, result) => {
callback(err, result && result.humidity)
})
}
, getServices: function () {
const CommunityTypes = require('hap-nodejs-community-types')(homebridge, {
units: { DownloadSpeed: this.config.nominal.download.unit }
})
, FakeGatoHistoryService = require('fakegato-history')(homebridge)
require('pkginfo')(module, [ 'name', 'author', 'version' ])
this.qualityService = new Service.AirQualitySensor('Bandwidth Quality')
this.qualityService.addCharacteristic(CommunityTypes.DownloadSpeed)
this.informationService = new Service.AccessoryInformation()
.setCharacteristic(Characteristic.Identify, module.exports.name)
.setCharacteristic(Characteristic.Manufacturer, module.exports.author.name)
.setCharacteristic(Characteristic.Model, "Bandwidth Quality Monitor")
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.SerialNumber, '1539641092069')
.setCharacteristic(Characteristic.FirmwareRevision, module.exports.version)
this.qualityService
.getCharacteristic(Characteristic.AirQuality)
.on('get', this.getAirQuality.bind(this))
this.qualityService
.getCharacteristic(Characteristic.StatusFault)
.on('get', this.getStatusFault.bind(this))
this.qualityService
.getCharacteristic(CommunityTypes.DownloadSpeed)
.on('get', this.getDownloadSpeed.bind(this))
this.qualityService
.getCharacteristic(CommunityTypes.EveAirQuality)
.on('get', this.getEveAirQuality.bind(this))
this.qualityService
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getCurrentRelativeHumidity.bind(this))
this.displayName = this.name
this.historyService = new FakeGatoHistoryService('room', this, {
storage : 'fs',
disableTimer : true,
length : Math.pow(2, 14),
path : homebridge.user.cachedAccessoryPath(),
filename : os.hostname().split(".")[0] + '_bandwidth-quality_persist.json'
})
setTimeout(this.fetchQuality.bind(this), 1 * 1000)
setInterval(this.fetchQuality.bind(this), this.options.ttl * 1000)
return [ this.informationService, this.qualityService, this.historyService ]
}
}
}