homebridge-flume-water-sensor
Version:
Not production ready! Flume water sensor (using the leak and light sensor services) for Homebridge. As of Aug 5, 2020 this is actively in development so it's super buggy! I welcome your help debugging.
220 lines (188 loc) • 8.33 kB
JavaScript
const fetch = require('node-fetch');
const packageJson = require('./package.json');
var jwtDecode = require('jwt-decode');
var Service, Characteristic;
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory('homebridge-flume-water-sensor', 'Flume', Flume);
}
function Flume (log, config, api) {
this.log = log;
this.config = config;
log("Starting homebridge-flume-water-sensor.");
// extract name from config
this.name = config.name;
// pull in initial variables
this.username = config["username"];
this.password = config["password"];
this.client_id = config["client_id"];
this.client_secret = config["client_secret"];
if (config["handicap"] != null) {
this.handicap = config["handicap"];
} else {
this.handicap = 0;
}
if (config["polling_minutes"] != null) {
this.interval = parseInt(config["polling_minutes"]) * 60 * 1000;
} else {
this.interval = 1 * 60 * 1000;
}
this.debug = config["debug"] || true;
if (!this.username) throw new Error("You must provide a value for username.");
if (!this.password) throw new Error("You must provide a value for password.");
if (!this.client_id) throw new Error("You must provide a value for client_id.");
if (!this.client_secret) throw new Error("You must provide a value for client_secret.");
this.expires_in = Date.now();
this.device_id = "";
this.device_usage = 0.00;
log("User variables loaded.");
if (api) {
this.api = api;
this.api.on('didFinishLaunching', this.fetchDevices.bind(this));
this.timer = setInterval(this.fetchDevices.bind(this), this.interval);
log("Update interval set.");
}
log("Initial load of the accessory started.");
// get the Valve service if it exists, otherwise create a new Valve service
this.informationService = this.accessory.getService(Service.Valve) ||
this.accessory.addService(Service.Valve);
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Flume Water Monitor")
.setCharacteristic(Characteristic.Model, "001")
.setCharacteristic(Characteristic.SerialNumber, this.device_id)
.setCharacteristic(Characteristic.Name, accessory.context.device.name)
.setCharacteristic(Characteristic.Active, false)
.setCharacteristic(Characteristic.InUse, false)
.setCharacteristic(Characteristic.ValveType, "3")
.setCharacteristic(Characteristic.StatusFault, false);
log("Starting position is water is Off");
log("Load of the accessory complete.");
}
Flume.prototype.getAccessToken = function() {
var now = Date.now();
log("Getting the access token.");
if (!this.access_token) {
return fetch('https://api.flumetech.com/oauth/token', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: '{"grant_type":"password","client_id":' + this.client_id + ',"client_secret":' + this.client_secret + ',"username":' + this.username + ',"password":' + this.password + '}'
})
.then(res => {
if (res.ok) {
return res.json();
log("res was okay.");
} else {
log("ERROR! Unable to retrieve new token: " + res.statusText);
}
})
.then(json => {
this.access_token = json.access_token;
this.refresh_token = json.refresh_token;
this.expires_in = now + json.expires_in;
this.log("Token received: " + this.access_token);
this.log("Refresh token received: " + this.refresh_token);
this.log("Token expires at: " + new Date(this.expires_in));
return this.access_token;
});
} else if (now > this.expires_in) {
return fetch('https://api.flumetech.com/oauth/token', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: '{"grant_type":"refresh_token","client_id":' + this.client_id + ',"client_secret":' + this.client_secret + ',"refresh_token":' + this.refresh_token + '}'
})
.then(res => {
if (res.ok) {
return res.json();
} else {
log("ERROR! Unable to retrieve new token: " + res.statusText);
}
})
.then(json => {
this.access_token = json.access_token;
this.expires_in = now + json.expires_in;
this.log("Token received: " + this.access_token);
this.log("Refresh token received: " + this.refresh_token);
this.log("Token expires at: " + new Date(this.expires_in));
return this.access_token;
});
}
this.log("Getting User ID from JWT.")
var token = this.access_token;
var decoded = jwt_decode(token);
// or perhaps: decoded = jwt.decode(config["access_token"], verify=False)
this.log(decoded);
this.user_id = decoded.user_id; // or perhaps: decoded["user_id"]
this.log("User ID: " + this.user_id);
this.log("Device ID: " + this.device_id);
return fetch('https://api.flumetech.com/users/' + this.user_id + '/devices', {
method: 'GET',
headers: {
'authorization': '' + this.access_token + ''
},
})
.then(res => {
if (res.ok) {
return res.json();
} else {
log("ERROR! Unable to retrieve devices: " + res.statusText);
}
})
.then(json => {
this.device_id = json.id;
this.log("User ID: " + this.user_id);
this.log("Device ID: " + this.device_id);
return this.device_id;
});
}
Flume.prototype.fetchDevices = function() {
this.log("Fetching current devices and statuses.");
// alternative approach: var previousminute = (datetime.datetime.now() - datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S');
var currentminute = (datetime.datetime.now() - datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S');
this.log("Token: " + this.access_token);
this.log("User ID: " + this.user_id);
this.log("Device ID: " + this.device_id);
this.getAccessToken()
.then(token => fetch('https://api.flumetech.com/users/' + this.user_id + '/devices/' + this.device_id + '/query', {
method: 'POST',
headers: {
'content-type': 'application/json',
'authorization': 'Bearer ' + this.access_token
},
body: '{"queries":[{"request_id":"currentusage","bucket":"MIN","since_datetime": currentminute,"operation":"SUM","units":"GALLONS"}]}'
// alternative approach might be payload = '{"queries":[{"request_id":"perminute","bucket":"MIN","since_datetime":"' + previousminute() + '","until_datetime":"' + currentminute() + '","group_multiplier":"1","operation":"SUM","sort_direction":"ASC","units":"GALLONS"}]}'
// as seen at https://github.com/ScriptBlock/flumecli/blob/master/flumecli.py
})
.then(res => {
if (res.ok) {
return res.json();
this.device_usage = value;
} else {
log("ERROR! Unable to retrieve devices: " + res.statusText);
}
}));
log("Updating the status");
this.updateState(accessory);
log("Updated the status");
}
Flume.prototype.updateState = function(accessory) {
var fresh = Date.now() - Date.parse(accessory.context.time + ".000Z") < 60 * 60 * 1000;
log("Device Usage & Handicap: ");
log(this.device_usage);
log(this.handicap);
if (this.device_usage > this.handicap) {
log("Water is On");
accessory.getService(Service.Valve)
.setCharacteristic(Characteristic.Active, true)
.setCharacteristic(Characteristic.InUse, true);
} else {
log("Water is Off");
accessory.getService(Service.Valve)
.setCharacteristic(Characteristic.Active, false)
.setCharacteristic(Characteristic.InUse, false);
}
}