items/item-history.js

/**
 * @typedef {import('@js-joda/core').ZonedDateTime} time.ZonedDateTime
 * @private
 */
const time = require('../time');
const utils = require('../utils');
/**
 * @typedef {import('../quantity').Quantity} Quantity
 * @private
 */
const { getQuantity, QuantityError } = require('../quantity');
const PersistenceExtensions = Java.type('org.openhab.core.persistence.extensions.PersistenceExtensions');

/**
 * Class representing an openHAB HistoricItem
 *
 * @memberof items
 * @hideconstructor
 */
class HistoricItem {
  /**
   * @param {*} rawHistoricItem {@link https://www.openhab.org/javadoc/latest/org/openhab/core/persistence/historicitem org.openhab.core.persistence.HistoricItem}
   */
  constructor (rawHistoricItem) {
    this.rawHistoricItem = rawHistoricItem;
    /**
     * Raw Java Item state
     * @type {HostState}
     */
    this.rawState = rawHistoricItem.getState();
  }

  /**
   * String representation of the Item state.
   * @type {string}
   */
  get state () {
    return this.rawState.toString();
  }

  /**
   * Numeric representation of Item state, or `null` if state is not numeric
   * @type {number|null}
   */
  get numericState () {
    const numericState = parseFloat(this.rawState.toString());
    return isNaN(numericState) ? null : numericState;
  }

  /**
   * Item state as {@link Quantity} or `null` if state is not Quantity-compatible or Quantity would be unit-less (without unit)
   * @type {Quantity|null}
   */
  get quantityState () {
    try {
      const qty = getQuantity(this.rawState.toString());
      return (qty !== null && qty.symbol !== null) ? qty : null;
    } catch (e) {
      if (e instanceof QuantityError) {
        return null;
      } else {
        throw Error('Failed to create "quantityState": ' + e);
      }
    }
  }

  /**
   * Timestamp of persisted Item.
   * @type {time.ZonedDateTime}
   */
  get timestamp () {
    return utils.javaZDTToJsZDT(this.rawHistoricItem.getTimestamp());
  }
}

function _ZDTOrNull (result) {
  return result === null ? null : time.ZonedDateTime.parse(result.toString());
}

function _decimalOrNull (result) {
  return result === null ? null : result.toBigDecimal();
}

function _historicItemOrNull (result) {
  if (result === null) return null;
  return new HistoricItem(result);
}

function _javaIterableOfJavaHistoricItemsToJsArrayOfHistoricItems (result) {
  if (result === null) return null;

  const historicItems = [];
  result.forEach((hi) => {
    const historicItem = _historicItemOrNull(hi);
    if (historicItem !== null) historicItems.push(historicItem);
  });
  return historicItems;
}

/**
 * Class representing the historic state of an openHAB Item.
 * If the Item receives its state from a binding that supports units of measurement, the returned state is in the according base unit, otherwise there is no unit conversion happening.
 * Wrapping the {@link https://www.openhab.org/javadoc/latest/org/openhab/core/persistence/extensions/persistenceextensions PersistenceExtensions}.
 *
 * Be warned: This class can throw several exceptions from the underlying Java layer. It is recommended to wrap the methods of this class inside a try_catch block!
 *
 * @memberOf items
 * @hideconstructor
 */
class ItemHistory {
  constructor (rawItem) {
    this.rawItem = rawItem;
  }

  /**
   * Gets the average value of the state of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  averageBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.averageBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the average value of the state of a given Item since a certain point in time.
   *
   * @example
   * var yesterday = new Date(new Date().getTime() - (24 * 60 * 60 * 1000));
   * var item = items.getItem('KitchenDimmer');
   * console.log('KitchenDimmer average since yesterday', item.history.averageSince(yesterday));
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  averageSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.averageSince(this.rawItem, ...arguments));
  }

  /**
   * Checks if the state of a given Item has changed between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {boolean}
   */
  changedBetween (begin, end, serviceId) {
    return PersistenceExtensions.changedBetween(this.rawItem, ...arguments);
  }

  /**
   * Checks if the state of a given Item has changed since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {boolean}
   */
  changedSince (timestamp, serviceId) {
    return PersistenceExtensions.changedSince(this.rawItem, ...arguments);
  }

  /**
   * Gets the number of available historic data points of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {number}
   */
  countBetween (begin, end, serviceId) {
    return PersistenceExtensions.countBetween(this.rawItem, ...arguments);
  }

  /**
   * Gets the number of available historic data points of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {number}
   */
  countSince (timestamp, serviceId) {
    return PersistenceExtensions.countSince(this.rawItem, ...arguments);
  }

  /**
   * Gets the number of changes in historic data points of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {number}
   */
  countStateChangesBetween (begin, end, serviceId) {
    return PersistenceExtensions.countStateChangesBetween(this.rawItem, ...arguments);
  }

  /**
   * Gets the number of changes in historic data points of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {number}
   */
  countStateChangesSince (timestamp, serviceId) {
    return PersistenceExtensions.countStateChangesSince(this.rawItem, ...arguments);
  }

  /**
   * Gets the difference value of the state of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  deltaBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.deltaBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the difference value of the state of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  deltaSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.deltaSince(this.rawItem, ...arguments));
  }

  /**
   * Gets the standard deviation of the state of the given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  deviationBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.deviationBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the standard deviation of the state of the given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  deviationSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.deviationSince(this.rawItem, ...arguments));
  }

  /**
   * Gets the evolution rate of the state of a given Item since a certain point in time.
   *
   * @deprecated Replaced by evolutionRateSince and evolutionRateBetween.
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  evolutionRate (timestamp, serviceId) {
    console.warn('"evolutionRate" is deprecated and will be removed in the future. Use "evolutionRateSince" instead.');
    return _decimalOrNull(PersistenceExtensions.evolutionRate(this.rawItem, ...arguments));
  }

  /**
   * Gets the evolution rate of the state of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  evolutionRateBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.evolutionRate(this.rawItem, ...arguments));
  }

  /**
   * Gets the evolution rate of the state of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  evolutionRateSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.evolutionRate(this.rawItem, ...arguments));
  }

  /**
   * Retrieves the {@link HistoricItems} for for a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {HistoricItem[]}
   */
  getAllStatesBetween (begin, end, serviceId) {
    return _javaIterableOfJavaHistoricItemsToJsArrayOfHistoricItems(PersistenceExtensions.getAllStatesBetween(this.rawItem, ...arguments));
  }

  /**
   * Retrieves the {@link HistoricItems} for for a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {HistoricItem[]}
   */
  getAllStatesSince (timestamp, serviceId) {
    return _javaIterableOfJavaHistoricItemsToJsArrayOfHistoricItems(PersistenceExtensions.getAllStatesSince(this.rawItem, ...arguments));
  }

  /**
   * Retrieves the historic state for a given Item at a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item
   */
  historicState (timestamp, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.historicState(this.rawItem, ...arguments));
  }

  /**
   * Query the last update time of a given Item.
   *
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(time.ZonedDateTime | null)}
   */
  lastUpdate (serviceId) {
    return _ZDTOrNull(PersistenceExtensions.lastUpdate(this.rawItem, ...arguments));
  }

  /**
   * Retrieves the historic Item state for a given Item at the current point in time.
   *
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(string | null)} state
   */
  latestState (serviceId) {
    const result = this.historicState(time.ZonedDateTime.now(), ...arguments);
    return (result === null) ? null : result.state;
  }

  /**
   * Gets the state with the maximum value of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item or null
   */
  maximumBetween (begin, end, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.maximumBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the state with the maximum value of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item or null
   */
  maximumSince (timestamp, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.maximumSince(this.rawItem, ...arguments));
  }

  /**
   * Gets the state with the minimum value of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item or null
   */
  minimumBetween (begin, end, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.minimumBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the state with the minimum value of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item or null
   */
  minimumSince (timestamp, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.minimumSince(this.rawItem, ...arguments));
  }

  /**
   * Persists the state of a given Item.
   *
   * Tells the persistence service to store the current state of the Item, which is then performed asynchronously.
   * This has the side effect, that if the Item state changes shortly after `.persist` has been called, the new state will be persisted.
   * To work around that side effect, you might add `java.lang.Thread.sleep` to your code:
   * @example
   * items.MyItem.history.persist(); // Tell persistence to store the current Item state
   * java.lang.Thread.sleep(100); // Wait 100 ms to make sure persistence has enough time to store the current Item state
   * items.MyItem.postUpdate(0); // Now set the Item state to a new value
   *
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   */
  persist (serviceId) {
    PersistenceExtensions.persist(this.rawItem, ...arguments);
  }

  /**
   * Returns the previous state of a given Item.
   *
   * @param {boolean} [skipEqual] optional, if true, skips equal state values and searches the first state not equal the current state
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(HistoricItem | null)} historic item or null
   */
  previousState (skipEqual, serviceId) {
    return _historicItemOrNull(PersistenceExtensions.previousState(this.rawItem, ...arguments));
  }

  /**
   * Gets the sum of the states of a given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  sumBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.sumBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the sum of the states of a given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  sumSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.sumSince(this.rawItem, ...arguments));
  }

  /**
   * Checks if the state of a given Item has been updated between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {boolean}
   */
  updatedBetween (begin, end, serviceId) {
    return PersistenceExtensions.updatedBetween(this.rawItem, ...arguments);
  }

  /**
   * Checks if the state of a given Item has been updated since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {boolean}
   */
  updatedSince (timestamp, serviceId) {
    return PersistenceExtensions.updatedSince(this.rawItem, ...arguments);
  }

  /**
   * Gets the variance of the state of the given Item between two certain points in time.
   *
   * @param {(time.ZonedDateTime | Date)} begin begin
   * @param {(time.ZonedDateTime | Date)} end end
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  varianceBetween (begin, end, serviceId) {
    return _decimalOrNull(PersistenceExtensions.varianceBetween(this.rawItem, ...arguments));
  }

  /**
   * Gets the variance of the state of the given Item since a certain point in time.
   *
   * @param {(time.ZonedDateTime | Date)} timestamp
   * @param {string} [serviceId] Optional persistence service ID, if omitted, the default persistence service will be used.
   * @returns {(number | null)}
   */
  varianceSince (timestamp, serviceId) {
    return _decimalOrNull(PersistenceExtensions.varianceSince(this.rawItem, ...arguments));
  }
}

module.exports = {
  ItemHistory,
  HistoricItem
};