456 lines
15 KiB
JavaScript
456 lines
15 KiB
JavaScript
const log = Java.type('org.slf4j.LoggerFactory').getLogger('js.equipmentMgr');
|
|
log.info('Load equipmentMgr');
|
|
|
|
const {
|
|
lib,
|
|
timer,
|
|
watch
|
|
} = require('../utils');
|
|
|
|
class Equipment {
|
|
constructor(equipmentItem) {
|
|
|
|
// Fetch item if provided equipmentItem is a string
|
|
if (typeof equipmentItem === 'string' || equipmentItem instanceof String) {
|
|
this.equipmentItem = items[equipmentItem];
|
|
} else {
|
|
this.equipmentItem = equipmentItem;
|
|
}
|
|
|
|
// Throw error if item is not existing
|
|
if (this.equipmentItem === null) {
|
|
throw(`Item ${equipmentItem} not existing`);
|
|
}
|
|
|
|
// Throw error if item is not an equipment type
|
|
if (this.equipmentItem.semantics.semanticType != 'Equipment') {
|
|
throw(`Item ${this.equipmentItem.name} is not an equipment type`);
|
|
}
|
|
|
|
log.info(`Initialization of equipment ${this.equipmentItem.name} with type ${this.constructor.name}`);
|
|
|
|
// Initialization of properties
|
|
this.name = this.equipmentItem.name;
|
|
this.conf = new Object();
|
|
this.savedControlPoints = new Object();
|
|
|
|
this.timers = new timer.Timer();
|
|
|
|
let metadata = equipmentItem.getMetadata('eMgr');
|
|
if (metadata != null) {
|
|
for (const [key, value] of Object.entries(metadata.configuration)) {
|
|
log.debug(`Set configuration ${key} for ${this.name} with value ${value.toString()}`);
|
|
this.conf[key] = value;
|
|
}
|
|
}
|
|
|
|
// Check if equipment has LowBat item
|
|
if (this.hasProperty('LowBat')) {
|
|
watches.add({
|
|
item: this.getPropertyItemName('LowBat'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.#notifyLowBat(); },
|
|
alertRepeat: 'PT23H'
|
|
});
|
|
}
|
|
|
|
// Check if equipment has Unreach item
|
|
if (this.hasProperty('Unreach')) {
|
|
watches.add({
|
|
item: this.getPropertyItemName('Unreach'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.#notifyUnreach(); },
|
|
alertDelay: 'PT15M',
|
|
alertRepeat: 'PT1H'
|
|
});
|
|
}
|
|
}
|
|
|
|
getValue(propertyName, defaultValue = '') {
|
|
let valueItemName = this.name + '_' + propertyName;
|
|
let returnValue = defaultValue;
|
|
|
|
if (items[valueItemName] == null) { // Return default value if item is missing
|
|
log.warn('Item ' + valueItemName + ' is missing');
|
|
} else if (items[valueItemName]['state'] == 'NULL') { // Return default value if item state is null
|
|
log.warn('Item ' + valueItemName + ' is unset')
|
|
} else { // Return value from item
|
|
if (items[valueItemName].quantityState == null && items[valueItemName].numericState == null) {
|
|
returnValue = items[valueItemName]['state'];
|
|
} else if (items[valueItemName].quantityState == null) {
|
|
returnValue = items[valueItemName]['numericState']
|
|
} else {
|
|
returnValue = items[valueItemName].quantityState;
|
|
}
|
|
}
|
|
|
|
log.debug(`Return value for property ${propertyName} for ${this.name}: ${returnValue}`);
|
|
return returnValue;
|
|
}
|
|
|
|
getPropertyItemName(propertyName) {
|
|
if (this.hasProperty(propertyName)) {
|
|
return items[this.name + '_' + propertyName].name;
|
|
}
|
|
}
|
|
|
|
hasProperty(propertyName) {
|
|
if (items[this.name + '_' + propertyName] == null) {
|
|
log.debug(`Equipment ${this.name} has no property ${propertyName}`)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
get controlPoints() {
|
|
log.debug('Fetch control points for ' + this.name);
|
|
return items[this.name].descendents.filter(element => { return (element.semantics.pointType == 'Control') });
|
|
}
|
|
|
|
saveControlPoints() {
|
|
for (let controlPoint of this.controlPoints) {
|
|
log.debug('Save ' + controlPoint.name + ' with state ' + controlPoint.state);
|
|
}
|
|
}
|
|
|
|
#notifyLowBat() {
|
|
log.warn(`${this.name} has a low battery level.`);
|
|
}
|
|
|
|
#notifyUnreach() {
|
|
log.warn(`${this.name} is offline.`);
|
|
}
|
|
|
|
gc() {
|
|
log.info('Denitialization of equipment ' + this.name);
|
|
|
|
// Cancel all timers
|
|
this.timers.cancelAll();
|
|
}
|
|
}
|
|
|
|
class Awning extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class Car extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class CCTV extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
// Watch
|
|
watches.add({
|
|
item: 'AP_ResidentsAreHome',
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.setHomeMode('ON'); }
|
|
});
|
|
watches.add({
|
|
item: 'AP_ResidentsAreHome',
|
|
targetState: 'OFF',
|
|
alertFunc: () => { this.setHomeMode('OFF'); },
|
|
alertDelay: 'PT1M'
|
|
});
|
|
}
|
|
|
|
setHomeMode(mode) {
|
|
switch (mode) {
|
|
case 'ON':
|
|
items[this.getPropertyItemName('HomeMode')].sendCommandIfDifferent('ON');
|
|
break;
|
|
case 'OFF':
|
|
items[this.getPropertyItemName('HomeMode')].sendCommandIfDifferent('OFF');
|
|
break;
|
|
default:
|
|
log.info(mode + ' is not a valid mode for HomeMode');
|
|
}
|
|
}
|
|
}
|
|
|
|
class EnergyMeter extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
|
|
if (this.hasProperty('ConsumptionTotal')) {
|
|
log.info(`Create rule to trigger calculateConsumption function`);
|
|
|
|
this.consumptionTotalItem = items[this.getPropertyItemName('ConsumptionTotal')];
|
|
rules.JSRule({
|
|
id: String(utils.randomUUID()),
|
|
name: 'Trigger calculateConsumption function for ' + this.equipmentItem.name,
|
|
triggers: [triggers.ItemStateChangeTrigger(this.consumptionTotalItem)],
|
|
execute: (event) => { this.#calculateConsumptionValues(event) },
|
|
});
|
|
} else {
|
|
log.info(`Item ${this.equipmentItem.name} has no consumptionTotal item`)
|
|
}
|
|
}
|
|
|
|
#calculateConsumptionValues() {
|
|
log.debug('Calculate EnergyMeter consumption values');
|
|
|
|
let startOfHour = time.toZDT().withMinute(0).withSecond(0).withNano(0);
|
|
let startOfDay = startOfHour.withHour(0);
|
|
let startOfWeek = startOfDay.minusDays(time.toZDT().dayOfWeek().value() - 1);
|
|
let startOfMonth = startOfDay.withDayOfMonth(1);
|
|
let startOfYear = startOfDay.withMonth(1).withDayOfMonth(1);
|
|
|
|
let consumptionTotal = Quantity(this.consumptionTotalItem.state);
|
|
|
|
if (this.hasProperty('ConsumptionHour')) {
|
|
let consumptionHour = consumptionTotal.subtract((this.consumptionTotalItem.history.minimumSince(startOfHour).state));
|
|
items[this.getPropertyItemName('ConsumptionHour')].postUpdate(consumptionHour);
|
|
}
|
|
|
|
if (this.hasProperty('ConsumptionToday')) {
|
|
let consumptionToday = consumptionTotal.subtract((this.consumptionTotalItem.history.minimumSince(startOfDay).state));
|
|
items[this.getPropertyItemName('ConsumptionToday')].postUpdate(consumptionToday);
|
|
}
|
|
|
|
if (this.hasProperty('ConsumptionWeek')) {
|
|
let consumptionWeek = consumptionTotal.subtract((this.consumptionTotalItem.history.minimumSince(startOfWeek).state));
|
|
items[this.getPropertyItemName('ConsumptionWeek')].postUpdate(consumptionWeek);
|
|
}
|
|
|
|
if (this.hasProperty('ConsumptionMonth')) {
|
|
let consumptionMonth = consumptionTotal.subtract((this.consumptionTotalItem.history.minimumSince(startOfMonth).state));
|
|
items[this.getPropertyItemName('ConsumptionMonth')].postUpdate(consumptionMonth);
|
|
}
|
|
|
|
if (this.hasProperty('ConsumptionYear')) {
|
|
let consumptionYear = consumptionTotal.subtract((this.consumptionTotalItem.history.minimumSince(startOfYear).state));
|
|
items[this.getPropertyItemName('ConsumptionYear')].postUpdate(consumptionYear);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class Doorbell extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
watches.add({
|
|
item: this.getPropertyItemName('State'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { items.getItem("OF_Alexa_Desk_TTS").sendCommand('Es hat geklingelt'); items.getItem("LR_Alexa_TV_TTS").sendCommand('Es hat geklingelt'); }
|
|
});
|
|
}
|
|
}
|
|
|
|
class Irrigation extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
// Initialization of properties
|
|
this.valves = new Object();
|
|
|
|
// Add on/off watch rule
|
|
watches.add({
|
|
item: this.getPropertyItemName('State'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.#irrigationOn(); },
|
|
endAlertFunc: () => { this.#irrigationOff(); },
|
|
});
|
|
|
|
// Fetch irrigation valves and initialize as subequipment
|
|
for (let valve of this.equipmentItem.members.filter(item => { return item.tags.includes('IrrigationValve'); } )) {
|
|
try {
|
|
this.valves[valve.name] = new IrrigationValve(valve);
|
|
} catch (error) {
|
|
log.error(error);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
gc() {
|
|
log.info('Deinitialization of irrigation valves for ' + this.name);
|
|
|
|
// Delete all watchObjects
|
|
for (let valve of Object.keys(this.valves)) {
|
|
this.valves[valve].gc();
|
|
}
|
|
|
|
// Call function to deinitialize irrigation
|
|
super.gc();
|
|
}
|
|
|
|
#irrigationOn() {
|
|
log.info(`Irrigation ${this.name} received command on`);
|
|
|
|
// Fetch valves with AutoMode enabled
|
|
let autoValves = Object.keys(this.valves).filter(element => { return this.valves[element].getValue('AutoMode', 'OFF') == 'ON'} );
|
|
|
|
// Skip if no valves with AutoMode enabled are available
|
|
if (autoValves.length == 0) {
|
|
log.info('No valves with AutoMode enabled available');
|
|
this.stateItem.sendCommandIfDifferent('OFF');
|
|
return;
|
|
}
|
|
|
|
let totalDuration = 0;
|
|
|
|
for (let i = 0; i < autoValves.length; i++) {
|
|
let currentValve = autoValves[i];
|
|
let nextValve = autoValves[(i + 1)];
|
|
let duration = time.toZDT(this.valves[currentValve].getValue('Duration', 'PT30S')).getMillisFromNow();
|
|
|
|
// calculate start and endtimes for valve
|
|
let startTime = time.toZDT(totalDuration)
|
|
totalDuration = totalDuration + duration;
|
|
let endTime = time.toZDT(totalDuration);
|
|
|
|
// Set timers for valve
|
|
this.valves[currentValve].timers.create(
|
|
'autoOn',
|
|
startTime,
|
|
() => {
|
|
this.valves[currentValve].stateItem.sendCommandIfDifferent('ON');
|
|
}
|
|
);
|
|
|
|
this.valves[currentValve].timers.create(
|
|
'autoOff',
|
|
endTime,
|
|
() => {
|
|
this.valves[currentValve].stateItem.sendCommandIfDifferent('OFF');
|
|
}
|
|
);
|
|
|
|
// Stop irrigation after the last valve
|
|
if (nextValve == undefined) {
|
|
log.info('No further valves with autoMode enabled available');
|
|
this.timers.create(
|
|
'autoOff',
|
|
endTime.plusSeconds(5),
|
|
() => {
|
|
log.debug(`Switch irrigation ${this.name} to off`)
|
|
this.stateItem.sendCommandIfDifferent('OFF');
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#irrigationOff() {
|
|
log.info(`Irrigation ${this.name} received command off`);
|
|
|
|
this.timers.cancel('autoOff')
|
|
|
|
for (let valve of Object.keys(this.valves)) {
|
|
this.valves[valve].timers.cancel('autoOn');
|
|
this.valves[valve].timers.cancel('autoOff');
|
|
this.valves[valve].stateItem.sendCommandIfDifferent('OFF')
|
|
}
|
|
}
|
|
}
|
|
|
|
class IrrigationValve extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
watches.add({
|
|
item: this.getPropertyItemName('State'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.stateItem.sendCommand('OFF'); },
|
|
alertDelay: 'PT30M'
|
|
});
|
|
}
|
|
}
|
|
|
|
class Light extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class Lightbulb extends Light {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class PresenceSensor extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class TowelRadiator extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
|
|
watches.add({
|
|
item: this.getPropertyItemName('State'),
|
|
targetState: 'ON',
|
|
alertFunc: () => { this.stateItem.sendCommand('OFF'); },
|
|
alertDelay: 'PT59M'
|
|
});
|
|
}
|
|
}
|
|
|
|
class VoiceAssistant extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class WeatherService extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
class Window extends Equipment {
|
|
constructor(equipmentItem) {
|
|
super(equipmentItem);
|
|
}
|
|
}
|
|
|
|
|
|
const watches = new watch.Watch();
|
|
|
|
// Initialization of eMgr
|
|
const eMgr = new Object();
|
|
cache.shared.put('eMgr', eMgr);
|
|
|
|
// Initialization of equipment items
|
|
for (let equipmentItem of items.getItems().filter(element => { return (element.semantics.isEquipment == true) && (element.semantics.equipment == null) })) {
|
|
|
|
// Get equipmentType
|
|
let equipmentType = equipmentItem.tags.filter(element => { return !(element == 'Equipment') } );
|
|
|
|
// Set default equipmentType if item does not provide a type
|
|
if (equipmentType == '') {
|
|
equipmentType = 'Equipment';
|
|
}
|
|
|
|
// Initialize equipment class
|
|
try {
|
|
eMgr[equipmentItem.name] = eval(`new ${equipmentType}(equipmentItem)`);
|
|
} catch (error) {
|
|
log.warn('Equipment ' + equipmentType + ' is not defined');
|
|
eMgr[equipmentItem.name] = new Equipment(equipmentItem);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
require('@runtime').lifecycleTracker.addDisposeHook(() => {
|
|
log.info('Deinitialization of equipmentMgr');
|
|
|
|
watches.deleteAll();
|
|
|
|
for (let equipmentItemName of Object.keys(eMgr)) {
|
|
eMgr[equipmentItemName].gc();
|
|
}
|
|
});
|
|
|