homebridge-sunlight-altitude
Version:
Homebridge plugin for contact sensors monitoring sun position, daylight and cloudiness
224 lines (190 loc) • 7.59 kB
JavaScript
const suncalc = require('suncalc');
const request = require('request');
class SunlightAccessory {
constructor(log, config, platformConfig) {
this.accessory = null;
this.registered = null;
this.config = config;
this.platformConfig = platformConfig;
this.log = log;
this.cachedWeatherObj = undefined;
this.lastupdate = 0;
if (this.platformConfig.apikey) {
this.getWeather();
setInterval(() => { this.getWeather(); }, 294997);
}
}
getAccessory() {
return this.accessory;
}
setAccessory(accessory) {
this.accessory = accessory;
this.setAccessoryEventHandlers();
}
hasRegistered() {
return this.registered;
}
initializeAccessory() {
const { config } = this;
const { lowerThreshold, upperThreshold } = config;
const uuid = UUIDGen.generate(config.name);
const accessory = new Accessory(config.name, uuid);
// Add Device Information
accessory.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, 'Krillle')
.setCharacteristic(Characteristic.Model, 'Azimuth ' + lowerThreshold + '-' + upperThreshold)
.setCharacteristic(Characteristic.SerialNumber, '---');
const SensorService = accessory.addService(Service.ContactSensor, config.name);
if (SensorService) {
SensorService.getCharacteristic(Characteristic.ContactSensorState);
}
this.setAccessory(accessory);
return accessory;
}
setRegistered(status) {
this.registered = status;
}
setAccessoryEventHandlers() {
const { log } = this;
this.getAccessory().on('identify', (paired, callback) => {
log(this.getAccessory().displayName, `Identify sensor, paired: ${paired}`);
callback();
});
const SensorService = this.getAccessory().getService(Service.ContactSensor);
if (SensorService) {
SensorService
.getCharacteristic(Characteristic.ContactSensorState)
.on('get', this.getState.bind(this));
SensorService.setCharacteristic(Characteristic.ContactSensorState, this.updateState());
setInterval(() => {
SensorService.setCharacteristic(Characteristic.ContactSensorState, this.updateState());
}, 10007);
}
}
updateState() {
const { config, platformConfig, log } = this;
const { lat, long, apikey } = platformConfig;
const { lowerThreshold, upperThreshold, lowerAltitudeThreshold, upperAltitudeThreshold } = config;
const threshold = [lowerThreshold, upperThreshold];
if (!lat || !long || typeof lat !== 'number' || typeof long !== 'number') {
log('Error: Lat/Long incorrect. Please refer to the README.');
return 0;
}
const sunPos = suncalc.getPosition(Date.now(), lat, long);
let sunPosDegrees = Math.abs((sunPos.azimuth * 180) / Math.PI + 180);
let sunAltitudeDegress = Math.abs((sunPos.altitude * 180) / Math.PI);
if (platformConfig.debugLog) log(`Current azimuth: ${sunPosDegrees}°`);
if (platformConfig.debugLog) log(`Current altitude: ${sunAltitudeDegress}°`);
if (threshold[0] > threshold[1]) {
const tempThreshold = threshold[1];
threshold[1] = threshold[0];
threshold[0] = tempThreshold;
}
let newState;
if (sunPosDegrees >= threshold[0] && sunPosDegrees <= threshold[1]) {
newState = true;
} else {
newState = false;
}
if (threshold[0] < 0 && newState === false) {
sunPosDegrees = -(360 - sunPosDegrees);
if (sunPosDegrees >= threshold[0] && sunPosDegrees <= threshold[1]) {
newState = true;
} else {
newState = false;
}
}
if (threshold[1] > 360 && newState === false) {
sunPosDegrees = 360 + sunPosDegrees;
if (sunPosDegrees >= threshold[0] && sunPosDegrees <= threshold[1]) {
newState = true;
} else {
newState = false;
}
}
if ((lowerAltitudeThreshold > sunAltitudeDegress || upperAltitudeThreshold < sunAltitudeDegress) && newState === false) {
newState = false
}
// Sun is in relevant azimuth range, lets check daylight and clouds
if (newState && apikey) {
let sunState = this.returnSunFromCache();
let cloudState = this.returnCloudinessFromCache();
if (platformConfig.debugLog) log(`Sun state: ${sunState}%, Cloud state: ${cloudState}%`);
newState = sunState > 10 && sunState < 90 && cloudState <= 25;
}
return newState;
}
getState(callback) {
const { platformConfig, log } = this;
const newState = this.updateState();
callback(null, newState);
if (platformConfig.debugLog) log(this.getAccessory().displayName, `getState: ${newState}`);
}
// - - - - - - - - Open Weather functions - - - - - - - -
getWeather() {
const { platformConfig, log } = this;
const { lat, long, apikey } = platformConfig;
// Only fetch new data once per minute
if (!this.cachedWeatherObj || this.lastupdate + 60 < (new Date().getTime() / 1000 | 0)) {
let p = new Promise((resolve, reject) => {
var url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${long}&daily=sunrise,sunset&models=icon_seamless¤t=cloud_cover&timezone=Europe%2FBerlin&forecast_days=1`
// var url = 'http://api.openweathermap.org/data/2.5/weather?appid=' + apikey + '&lat=' + lat + '&lon=' + long;
if (platformConfig.debugLog) log("Checking weather: %s", url);
request(url, function (error, response, responseBody) {
if (error) {
log("HTTP get weather function failed: %s", error.message);
reject(error);
} else {
try {
if (platformConfig.debugLog) log("Server response:", responseBody);
const parsedData = JSON.parse(responseBody);
this.cachedWeatherObj = {
clouds: {
all: parsedData.current.cloud_cover,
},
sys: {
sunrise: (new Date(parsedData.daily.sunrise[0])).getTime() / 1000,
sunset: (new Date(parsedData.daily.sunset[0])).getTime() / 1000,
}
};
// this.cachedWeatherObj = JSON.parse(responseBody);
this.lastupdate = (new Date().getTime() / 1000);
log(`Sun state: ${this.returnSunFromCache()}%, Cloud state: ${this.returnCloudinessFromCache()}%`);
resolve(response.statusCode);
} catch (error2) {
log("Getting Weather failed: %s", error2, responseBody);
reject(error2);
}
}
}.bind(this))
})
}
};
returnCloudinessFromCache() {
var value;
if (this.cachedWeatherObj && this.cachedWeatherObj["clouds"]) {
value = parseFloat(this.cachedWeatherObj["clouds"]["all"]);
}
return value;
};
returnSunFromCache() {
var value;
if (this.cachedWeatherObj && this.cachedWeatherObj["sys"]) {
var sunrise = parseInt(this.cachedWeatherObj["sys"]["sunrise"]);
var sunset = parseInt(this.cachedWeatherObj["sys"]["sunset"]);
var now = Math.round(new Date().getTime() / 1000);
if (now > sunset) {
// It's already dark outside
value = 100;
} else if (now > sunrise) {
// calculate how far though the day (where day is from sunrise to sunset) we are
var intervalLen = (sunset - sunrise);
value = (((now - sunrise) / intervalLen) * 100).toFixed(2);
} else {
value = 0;
}
}
return value;
};
}
module.exports = SunlightAccessory;