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(); } });