itemutils/groups.js

/**
 * Copyright (c) 2021 Florian Hotze
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */

const { items } = require('openhab');

/**
 * @typedef {object} Item
 * @private
 */

/**
 * Group Utilities
 *
 * Provides a number of utilities for groups.
 * @memberof itemutils
 */
class GroupUtils {
  /**
   * Creates an instance of groupUtils. Don't use this constructor, instead call {@link getGroup}.
   * @param {Item} groupItem Item from 'openhab'
   * @hideconstructor
   */
  constructor (groupItem) {
    if (groupItem.type !== 'GroupItem') throw new Error('Supplied Item must be a group Item!');
    this.jsItem = groupItem;
  }

  /**
   * Members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * @type {Item[]}
   */
  get members () {
    return this.jsItem.members;
  }

  /**
   * Names of members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * @type {string[]}
   */
  get membersNames () {
    return this.members.map(item => item.name);
  }

  /**
   * Labels of members / children / direct descendents of the current group Item (as returned by 'getMembers()') as a concatenated string
   * @type {string}
   */
  get membersLabelsString () {
    return this.members.map(item => item.label).join(', ');
  }

  /**
   * Minimum state item of members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * Filters for items not {@link isUninitialized}.
   * @type {Item}
   */
  get membersMin () {
    return this.members.filter(item => !item.isUninitialized).reduce((min, item) => parseFloat(item.state) < parseFloat(min.state) ? item : min);
  }

  /**
   * Maximum state item of members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * Filters for items not {@link isUninitialized}.
   * @type {Item}
   */
  get membersMax () {
    return this.members.filter(item => !item.isUninitialized).reduce((min, item) => parseFloat(item.state) > parseFloat(min.state) ? item : min);
  }

  /**
   * Summarized value of members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * Filters for items of type Number, Dimmer & Rollershutter in calculation and not {@link isUninitialized}.
   * @type {number}
   */
  get membersSum () {
    return this.members.filter(item => (item.type === 'NumberItem' || item.type === 'DimmerItem' || item.type === 'RollershutterItem') && !item.isUninitialized).reduce((sum, item) => sum + parseFloat(item.state), 0);
  }

  /**
   * Average value of members / children / direct descendents of the current group Item (as returned by 'getMembers()')
   * Filters for items of type Number, Dimmer & Rollershutter in calculation and not {@link isUninitialized}.
   * @type {number}
   */
  get membersAvg () {
    const numbers = this.members.filter(item => (item.type === 'NumberItem' || item.type === 'DimmerItem' || item.type === 'RollershutterItem') && !item.isUninitialized);
    return numbers.reduce((avg, item) => { return avg + parseFloat(item.state) / numbers.length; }, 0);
  }

  /**
   * All descendents of the current group Item (as returned by 'getAllMembers()')
   * @type {Item[]}
   */
  get descendents () {
    return this.jsItem.descendents;
  }

  /**
   * Names of all descendents of the current group item (as returned by 'getAllMembers()'). Must be a group item.
   * @type {string[]}
   */
  get descendentsNames () {
    return this.descendents.map(item => item.name);
  }

  /**
   * Labels of all descendents of the current group item (as returned by 'getAllMembers()'). Must be a group item.
   * @type {string}
   */
  get descendentsLabelsString () {
    return this.descendents.map(item => item.label).join(', ');
  }

  /**
   * Minimum state item of all descendents of the current group Item (as returned by 'getAllMembers()')
   * Filters for items not {@link isUninitialized}.
   * @type {Item}
   */
  get descendentsMin () {
    return this.descendents.filter(item => !item.isUninitialized).reduce((min, item) => parseFloat(item.state) < parseFloat(min.state) ? item : min);
  }

  /**
   * Maximum state item of all descendents of the current group Item (as returned by 'getAllMembers()')
   * Filters for items not {@link isUninitialized}.
   * @type {Item}
   */
  get descendentsMax () {
    return this.descendents.filter(item => !item.isUninitialized).reduce((min, item) => parseFloat(item.state) > parseFloat(min.state) ? item : min);
  }

  /**
   * Summarized value of all descendents of the current group Item (as returned by 'getAllMembers()')
   * Filters for items of type Number, Dimmer & Rollershutter in calculation and not {@link isUninitialized}.
   * @type {number}
   */
  get descendentsSum () {
    return this.descendents.filter(item => (item.type === 'NumberItem' || item.type === 'DimmerItem' || item.type === 'RollershutterItem') && !item.isUninitialized).reduce((sum, item) => sum + parseFloat(item.state), 0);
  }

  /**
   * Average value of all descendents of the current group Item (as returned by 'getAllMembers()')
   * Filters for items of type Number, Dimmer & Rollershutter in calculation and not {@link isUninitialized}.
   * @type {number}
   */
  get descendentsAvg () {
    const numbers = this.descendents.filter(item => (item.type === 'NumberItem' || item.type === 'DimmerItem' || item.type === 'RollershutterItem') && !item.isUninitialized);
    return numbers.reduce((avg, item) => { return avg + parseFloat(item.state) / numbers.length; }, 0);
  }

  /**
   * Count the number of members / children / direct descendents of the current group Item (as returned by 'getMembers()') matching a comparison
   * @param {function} compareFunc comparison function
   * @returns {number} number of matches
   * @example
   * itemutils.getGroup('group').membersCount(item => item.state === 'ON');
   */
  membersCount (compareFunc) {
    // @ts-ignore
    return this.members.filter(compareFunc).length;
  }

  /**
   * Count the number of all descendents of the current group Item (as returned by 'getAllMembers()') matching a comparison
   * @param {function} compareFunc comparison function
   * @returns {number} number of matches
   * @example
   * itemutils.getGroup('group').descendentsCount(item => item.state === 'ON');
   */
  descendentsCount (compareFunc) {
    // @ts-ignore
    return this.descendents.filter(compareFunc).length;
  }
}

/**
 * Gets an instance of groupUtils.
 * @memberof itemutils
 * @param {string} groupName the name of the group
 * @returns {GroupUtils} {@link itemutils.GroupUtils}
 */
function getGroupUtils (groupName) {
  return new GroupUtils(items.getItem(groupName));
}

module.exports = {
  getGroupUtils
};