UNPKG

klf-200-api

Version:

This module provides a wrapper to the socket API of a Velux KLF-200 interface. You will need at least firmware 0.2.0.0.71 on your KLF interface for this library to work.

1,206 lines 66.9 kB
"use strict"; import debugModule from "debug"; import { setImmediate } from "timers/promises"; import { CommandOriginator, CommandStatus, LimitationType, LockTime, ParameterActive, PriorityLevel, RunStatus, StatusReply, StatusType, convertPosition, convertPositionRaw, } from "./KLF200-API/GW_COMMAND.js"; import { GW_COMMAND_REMAINING_TIME_NTF } from "./KLF200-API/GW_COMMAND_REMAINING_TIME_NTF.js"; import { GW_COMMAND_RUN_STATUS_NTF } from "./KLF200-API/GW_COMMAND_RUN_STATUS_NTF.js"; import { GW_COMMAND_SEND_REQ } from "./KLF200-API/GW_COMMAND_SEND_REQ.js"; import { GW_CS_SYSTEM_TABLE_UPDATE_NTF } from "./KLF200-API/GW_CS_SYSTEM_TABLE_UPDATE_NTF.js"; import { GW_ERROR, GW_ERROR_NTF } from "./KLF200-API/GW_ERROR_NTF.js"; import { GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF } from "./KLF200-API/GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF.js"; import { GW_GET_ALL_NODES_INFORMATION_NTF } from "./KLF200-API/GW_GET_ALL_NODES_INFORMATION_NTF.js"; import { GW_GET_ALL_NODES_INFORMATION_REQ } from "./KLF200-API/GW_GET_ALL_NODES_INFORMATION_REQ.js"; import { GW_GET_LIMITATION_STATUS_REQ } from "./KLF200-API/GW_GET_LIMITATION_STATUS_REQ.js"; import { GW_GET_NODE_INFORMATION_NTF } from "./KLF200-API/GW_GET_NODE_INFORMATION_NTF.js"; import { GW_GET_NODE_INFORMATION_REQ } from "./KLF200-API/GW_GET_NODE_INFORMATION_REQ.js"; import { GatewayState, GatewaySubState } from "./KLF200-API/GW_GET_STATE_CFM.js"; import { GW_GET_STATE_REQ } from "./KLF200-API/GW_GET_STATE_REQ.js"; import { GW_LIMITATION_STATUS_NTF } from "./KLF200-API/GW_LIMITATION_STATUS_NTF.js"; import { GW_NODE_INFORMATION_CHANGED_NTF } from "./KLF200-API/GW_NODE_INFORMATION_CHANGED_NTF.js"; import { GW_NODE_STATE_POSITION_CHANGED_NTF } from "./KLF200-API/GW_NODE_STATE_POSITION_CHANGED_NTF.js"; import { GW_SESSION_FINISHED_NTF } from "./KLF200-API/GW_SESSION_FINISHED_NTF.js"; import { GW_SET_LIMITATION_REQ } from "./KLF200-API/GW_SET_LIMITATION_REQ.js"; import { GW_SET_NODE_NAME_REQ } from "./KLF200-API/GW_SET_NODE_NAME_REQ.js"; import { GW_SET_NODE_ORDER_AND_PLACEMENT_REQ } from "./KLF200-API/GW_SET_NODE_ORDER_AND_PLACEMENT_REQ.js"; import { GW_SET_NODE_VARIATION_REQ } from "./KLF200-API/GW_SET_NODE_VARIATION_REQ.js"; import { GW_STATUS_REQUEST_NTF } from "./KLF200-API/GW_STATUS_REQUEST_NTF.js"; import { GW_STATUS_REQUEST_REQ } from "./KLF200-API/GW_STATUS_REQUEST_REQ.js"; import { ActuatorType, } from "./KLF200-API/GW_SYSTEMTABLE_DATA.js"; import { GW_WINK_SEND_REQ } from "./KLF200-API/GW_WINK_SEND_REQ.js"; import { GW_COMMON_STATUS, GW_INVERSE_STATUS, GatewayCommand } from "./KLF200-API/common.js"; import { Component } from "./utils/PropertyChangedEvent.js"; import { TypedEvent } from "./utils/TypedEvent.js"; const debug = debugModule(`klf-200-api:products`); /** * Each product that is registered at the KLF-200 interface will be created * as an instance of the Product class. * * @export * @class Product */ export class Product extends Component { Connection; _name; /** * NodeID is an Actuator index in the system table, to get information from. It must be a * value from 0 to 199. * * @type {number} * @memberof Product */ NodeID; _TypeID; /** * Indicates the node type, ex. Window, Roller shutter, Light etc. * * @readonly * @type {ActuatorType} * @memberof Product */ get TypeID() { return this._TypeID; } _SubType; /** * Details the node type and depends on the TypeID property. * * @readonly * @type {number} * @memberof Product */ get SubType() { return this._SubType; } _order; _placement; _velocity; /** * Velocity the node is operated with. * * @readonly * @type {Velocity} * @memberof Product */ get Velocity() { return this._velocity; } _nodeVariation; _PowerSaveMode; /** * The power mode of the node. * * @readonly * @type {PowerSaveMode} * @memberof Product */ get PowerSaveMode() { return this._PowerSaveMode; } _SerialNumber; /** * The serial number of the product. * * @readonly * @type {Buffer} * @memberof Product */ get SerialNumber() { return this._SerialNumber; } _ProductType; /** * Type of the product, eg. KMG, KMX. * * @readonly * @type {number} * @memberof Product */ get ProductType() { return this._ProductType; } _state; _currentPositionRaw; _targetPositionRaw; _fp1CurrentPositionRaw; _fp2CurrentPositionRaw; _fp3CurrentPositionRaw; _fp4CurrentPositionRaw; _remainingTime; _timeStamp; _ProductAlias; /** * Contains the position values to move the product to a special position. * The special position is defined by the alias value. * * E.g. for a window the alias ID for secured ventilation if 0xD803. * To move a product into secured ventilation position you have to read * the value of the alias for the alias ID 0xD803 and set the * raw target position to that value. Different types of windows * may return different raw positions. * * @type {ActuatorAlias[]} * @memberof Product */ get ProductAlias() { return this._ProductAlias; } _runStatus = RunStatus.ExecutionCompleted; _statusReply = StatusReply.Unknown; /** * Creates an instance of Product. You shouldn't create instances * of the [[Product]] class by yourself. Instead, use the [[Products]] class * to read all installed products from the KLF-200. * * @param {IConnection} Connection The connection object that handles the communication to the KLF interface. * @param {(GW_GET_NODE_INFORMATION_NTF | GW_GET_ALL_NODES_INFORMATION_NTF)} frame Notification frame that is used to set the properties of the Product class instance. * @memberof Product */ constructor(Connection, frame) { super(); this.Connection = Connection; this.NodeID = frame.NodeID; this._name = frame.Name; this._TypeID = frame.ActuatorType; this._SubType = frame.ActuatorSubType; this._order = frame.Order; this._placement = frame.Placement; this._velocity = frame.Velocity; this._nodeVariation = frame.NodeVariation; this._PowerSaveMode = frame.PowerSaveMode; this._SerialNumber = frame.SerialNumber; this._ProductType = frame.ProductType; this._state = frame.OperatingState; this._currentPositionRaw = frame.CurrentPosition; this._targetPositionRaw = frame.TargetPosition; this._fp1CurrentPositionRaw = frame.FunctionalPosition1CurrentPosition; this._fp2CurrentPositionRaw = frame.FunctionalPosition2CurrentPosition; this._fp3CurrentPositionRaw = frame.FunctionalPosition3CurrentPosition; this._fp4CurrentPositionRaw = frame.FunctionalPosition4CurrentPosition; this._remainingTime = frame.RemainingTime; this._timeStamp = frame.TimeStamp; this._ProductAlias = frame.ActuatorAliases; this._limitationMinRaw = new Array(17).fill(0); this._limitationMaxRaw = new Array(17).fill(0xc800); this._limitationOriginator = new Array(17).fill(CommandOriginator.User); this._limitationTimeRaw = new Array(17).fill(LockTime.lockTimeTolockTimeValueForLimitation(Infinity)); this.Connection.on(async (frame) => { debug(`Calling onNotificationHandler for GW_NODE_INFORMATION_CHANGED_NTF, GW_NODE_STATE_POSITION_CHANGED_NTF, GW_COMMAND_RUN_STATUS_NTF, GW_COMMAND_REMAINING_TIME_NTF, GW_GET_NODE_INFORMATION_NTF, GW_STATUS_REQUEST_NTF added in Product constructor.`); await this.onNotificationHandler(frame); }, [ GatewayCommand.GW_NODE_INFORMATION_CHANGED_NTF, GatewayCommand.GW_NODE_STATE_POSITION_CHANGED_NTF, GatewayCommand.GW_COMMAND_RUN_STATUS_NTF, GatewayCommand.GW_COMMAND_REMAINING_TIME_NTF, GatewayCommand.GW_GET_NODE_INFORMATION_NTF, GatewayCommand.GW_STATUS_REQUEST_NTF, ]); } /** * Name of the product. * * @readonly * @type {string} * @memberof Product */ get Name() { return this._name; } /** * Renames the product. * * @param {string} newName New name of the product. * @returns {Promise<void>} * @memberof Product */ async setNameAsync(newName) { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_SET_NODE_NAME_REQ(this.NodeID, newName))); if (confirmationFrame.Status === GW_COMMON_STATUS.SUCCESS) { this._name = newName; return Promise.resolve(); } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * String representation of the TypeID and SubType. * * @readonly * @type {string} * @memberof Product */ get Category() { switch (this.TypeID) { case ActuatorType.VenetianBlind: return "Interior venetian blind"; case ActuatorType.RollerShutter: switch (this.SubType) { case 1: return "Adjustable slats roller shutter"; case 2: return "Roller shutter with projection"; default: return "Roller shutter"; } case ActuatorType.Awning: return "Vertical exterior awning"; case ActuatorType.WindowOpener: switch (this.SubType) { case 1: return "Window opener with integrated rain sensor"; default: return "Window opener"; } case ActuatorType.GarageOpener: return "Garage door opener"; case ActuatorType.Light: return "Light"; case ActuatorType.GateOpener: return "Gate opener"; case ActuatorType.Lock: switch (this.SubType) { case 1: return "Window lock"; default: return "Door lock"; } case ActuatorType.Blind: return "Vertical interior blind"; case ActuatorType.DualShutter: return "Dual roller shutter"; case ActuatorType.OnOffSwitch: return "On/Off switch"; case ActuatorType.HorizontalAwning: return "Horizontal awning"; case ActuatorType.ExternalVentianBlind: return "Exterior venetion blind"; case ActuatorType.LouvreBlind: return "Louvre blind"; case ActuatorType.CurtainTrack: return "Curtain track"; case ActuatorType.VentilationPoint: switch (this.SubType) { case 1: return "Air inlet"; case 2: return "Air transfer"; case 3: return "Air outlet"; default: return "Ventilation point"; } case ActuatorType.ExteriorHeating: return "Exterior heating"; case ActuatorType.SwingingShutter: switch (this.SubType) { case 1: return "Swinging shutter with independent handling of the leaves"; default: return "Swinging shutter"; } default: return `${this.TypeID.toString()}.${this.SubType.toString()}`; } } /** * Defines the variation of a product. * * @readonly * @type {NodeVariation} * @memberof Product */ get NodeVariation() { return this._nodeVariation; } /** * Sets the variation of a product to a new value. * * @param {NodeVariation} newNodeVariation New value for the variation of the product. * @returns {Promise<void>} * @memberof Product */ async setNodeVariationAsync(newNodeVariation) { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_SET_NODE_VARIATION_REQ(this.NodeID, newNodeVariation))); if (confirmationFrame.Status === GW_COMMON_STATUS.SUCCESS) { this._nodeVariation = newNodeVariation; return Promise.resolve(); } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Sets the order and placement of the product. * * @param {number} newOrder The new order value of the product. * @param {number} newPlacement The new placement value of the product. * @returns {Promise<void>} * @memberof Product */ async setOrderAndPlacementAsync(newOrder, newPlacement) { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_SET_NODE_ORDER_AND_PLACEMENT_REQ(this.NodeID, newOrder, newPlacement))); if (confirmationFrame.Status === GW_COMMON_STATUS.SUCCESS) { this._order = newOrder; this._placement = newPlacement; return Promise.resolve(); } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * The order in which the products should be displayed by a client application. * * @readonly * @type {number} * @memberof Product */ get Order() { return this._order; } /** * Sets a new value for the order number of the product. * * @param {number} newOrder New value for the order property. * @returns {Promise<void>} * @memberof Product */ async setOrderAsync(newOrder) { return this.setOrderAndPlacementAsync(newOrder, this._placement); } /** * The placement of the product. Either a house index or a room index number. * * @readonly * @type {number} * @memberof Product */ get Placement() { return this._placement; } /** * Sets a new value for the placement of the product. * * @param {number} newPlacement New value for the placement property. * @returns {Promise<void>} * @memberof Product */ async setPlacementAsync(newPlacement) { return this.setOrderAndPlacementAsync(this._order, newPlacement); } /** * Current operating state of the product. * * @readonly * @type {NodeOperatingState} * @memberof Product */ get State() { return this._state; } /** * Raw value of the current position of the product. * * @readonly * @type {number} * @memberof Product */ get CurrentPositionRaw() { return this._currentPositionRaw; } /** * Raw value of the target value for the position of the product. * * @readonly * @type {number} * @memberof Product */ get TargetPositionRaw() { return this._targetPositionRaw; } /** * Raw value of the current position of the functional paramter 1. * * @readonly * @type {number} * @memberof Product */ get FP1CurrentPositionRaw() { return this._fp1CurrentPositionRaw; } /** * Raw value of the current position of the functional paramter 2. * * @readonly * @type {number} * @memberof Product */ get FP2CurrentPositionRaw() { return this._fp2CurrentPositionRaw; } /** * Raw value of the current position of the functional paramter 3. * * @readonly * @type {number} * @memberof Product */ get FP3CurrentPositionRaw() { return this._fp3CurrentPositionRaw; } /** * Raw value of the current position of the functional paramter 4. * * @readonly * @type {number} * @memberof Product */ get FP4CurrentPositionRaw() { return this._fp4CurrentPositionRaw; } /** * Remaining time in seconds to reach the desired target position. * * @readonly * @type {number} * @memberof Product */ get RemainingTime() { return this._remainingTime; } /** * Timestamp of the last change to any of the properties. * * @readonly * @type {Date} * @memberof Product */ get TimeStamp() { return this._timeStamp; } /** * The current run status of the product. * * @readonly * @type {RunStatus} * @memberof Product */ get RunStatus() { return this._runStatus; } /** * Additional status information, e.g. that opening a window is overruled by the rain sensor. * * @readonly * @type {StatusReply} * @memberof Product */ get StatusReply() { return this._statusReply; } /** * The current position of the product in percent. * * The value is derived from the raw value and depending on * the type ID it is inverted, so that 100% means e.g. * window is fully open, roller shutter is fully closed, * light is at full power etc. * * @readonly * @type {number} * @memberof Product */ get CurrentPosition() { return convertPositionRaw(this._currentPositionRaw, this.TypeID); } /** * Sets the product to a new position as raw value. * * @param {number} newPosition New position value as raw value. * @param PriorityLevel The priority level for the run command. * @param CommandOriginator The command originator for the run command. * @param ParameterActive The parameter that should be returned in the notifications. MP or FP1-FP16. * @param FunctionalParameters Additional functional paramters can be set during the command. * @param PriorityLevelLock Flag if the priority level lock should be used. * @param PriorityLevels Up to 8 priority levels. * @param LockTime Lock time for the priority levels in seconds (multiple of 30 or Infinity). * @returns {Promise<number>} * @memberof Product */ async setTargetPositionRawAsync(newPosition, PriorityLevel = 3, CommandOriginator = 1, ParameterActive = 0, FunctionalParameters = [], PriorityLevelLock = 0, PriorityLevels = [], LockTime = Infinity) { try { const req = new GW_COMMAND_SEND_REQ(this.NodeID, newPosition, PriorityLevel, CommandOriginator, ParameterActive, FunctionalParameters, PriorityLevelLock, PriorityLevels, LockTime); const confirmationFrame = await this.Connection.sendFrameAsync(req); if (confirmationFrame.CommandStatus === CommandStatus.CommandAccepted) { return confirmationFrame.SessionID; } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Sets the product to a new position in percent. * * @param {number} newPosition New position value in percent. * @param PriorityLevel The priority level for the run command. * @param CommandOriginator The command originator for the run command. * @param ParameterActive The parameter that should be returned in the notifications. MP or FP1-FP16. * @param FunctionalParameters Additional functional paramters can be set during the command. * @param PriorityLevelLock Flag if the priority level lock should be used. * @param PriorityLevels Up to 8 priority levels. * @param LockTime Lock time for the priority levels in seconds (multiple of 30 or Infinity). * @returns {Promise<number>} * @memberof Product */ async setTargetPositionAsync(newPosition, PriorityLevel = 3, CommandOriginator = 1, ParameterActive = 0, FunctionalParameters = [], PriorityLevelLock = 0, PriorityLevels = [], LockTime = Infinity) { try { return await this.setTargetPositionRawAsync(convertPosition(newPosition, this.TypeID), PriorityLevel, CommandOriginator, ParameterActive, FunctionalParameters, PriorityLevelLock, PriorityLevels, LockTime); } catch (error) { return Promise.reject(error); } } /** * The target position in percent. * * @readonly * @type {number} * @memberof Product */ get TargetPosition() { return convertPositionRaw(this._targetPositionRaw, this.TypeID); } _limitationOriginator; /** * A read only array of the limitation originators. * @readonly * @type {CommandOriginator[]} * @memberof Product */ get LimitationOriginator() { return Array.from(this._limitationOriginator); } /** * Returns the limitation originator for a functional parameter. * You have to call {@link refreshLimitationAsync} to get the latest values first. * * @param functionalParameter Paramter for which the limitation originator should be returned. * @returns The limitation originator. */ getLimitationOriginator(functionalParameter) { return this._limitationOriginator[functionalParameter]; } _limitationTimeRaw; /** * A read only array of the limitation time raw values. * @readonly * @type {number[]} * @memberof Product */ get LimitationTimeRaw() { return Array.from(this._limitationTimeRaw); } /** * Returns the raw value of the limitation time for a functional parameter. * You have to call {@link refreshLimitationAsync} to get the latest values first. * * @param functionalParameter Parameter for which the limitation time raw value should be returned. * @returns The raw limitation time value. */ getLimitationTimeRaw(functionalParameter) { return this._limitationTimeRaw[functionalParameter]; } /** * Returns the limitation time in seconds for a functional parameter. * You have to call {@link refreshLimitationAsync} to get the latest values first. * * @param functionalParameter Parameter for which the limitation time should be returned. * @returns The limitation time in seconds or Infinity. */ getLimitationTime(functionalParameter) { return LockTime.lockTimeValueToLockTimeForLimitation(this.getLimitationTimeRaw(functionalParameter)); } _limitationMinRaw; /** * A read only array of the raw limitations' min values. * * @readonly * @type {number[]} * @memberof Product */ get LimitationMinRaw() { return Array.from(this._limitationMinRaw); } /** * The minimum value (raw) of a limitation of the product. * * @readonly * @param functionalParameter Parameter for which the limitation should be returned. * @type {number} * @memberof Product */ getLimitationMinRaw(functionalParameter) { return this._limitationMinRaw[functionalParameter]; } _limitationMaxRaw; /** * A read only array of the raw limitations' max values. * * @readonly * @type {number[]} * @memberof Product */ get LimitationMaxRaw() { return Array.from(this._limitationMaxRaw); } /** * The maximum value (raw) of a limitation of the product. * * @readonly * @param functionalParameter Parameter for which the limitation should be returned. * @type {number} * @memberof Product */ getLimitationMaxRaw(functionalParameter) { return this._limitationMaxRaw[functionalParameter]; } /** * Returns a tuple of min and max values for the limitation of the provided parameter. * * @param functionalParameter Parameter for which the limitations should be returned. * @returns A tuple of the min and max values as percentage in the range [0, 1]. * The first value of the tuple corresponds always to the min raw value * and the second value corresponds always to the max raw value. */ getLimitations(functionalParameter) { const limitationMin = convertPositionRaw(this.getLimitationMinRaw(functionalParameter), this.TypeID); const limitationMax = convertPositionRaw(this.getLimitationMaxRaw(functionalParameter), this.TypeID); return [limitationMin, limitationMax]; } /** * The minimum value of a limitation of the product. * * @readonly * @param functionalParameter Parameter for which the limitation should be returned. * @type {number} * @memberof Product */ getLimitationMin(functionalParameter) { return this.getLimitations(functionalParameter)[0]; } /** * The maximum value of a limitation of the product. * * @readonly * @param functionalParameter Parameter for which the limitation should be returned. * @type {number} * @memberof Product */ getLimitationMax(functionalParameter) { return this.getLimitations(functionalParameter)[1]; } /** * Stops the product at the current position. * * @param PriorityLevel The priority level for the run command. * @param CommandOriginator The command originator for the run command. * @param ParameterActive The parameter that should be returned in the notifications. MP or FP1-FP16. * @param FunctionalParameters Additional functional paramters can be set during the command. * @param PriorityLevelLock Flag if the priority level lock should be used. * @param PriorityLevels Up to 8 priority levels. * @param LockTime Lock time for the priority levels in seconds (multiple of 30 or Infinity). * @returns {Promise<number>} * @memberof Product */ async stopAsync(PriorityLevel = 3, CommandOriginator = 1, ParameterActive = 0, FunctionalParameters = [], PriorityLevelLock = 0, PriorityLevels = [], LockTime = Infinity) { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_COMMAND_SEND_REQ(this.NodeID, 0xd200, PriorityLevel, CommandOriginator, ParameterActive, FunctionalParameters, PriorityLevelLock, PriorityLevels, LockTime))); if (confirmationFrame.CommandStatus === CommandStatus.CommandAccepted) { return confirmationFrame.SessionID; } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Let the product "wink". Its main intention is to identify a product. * * Winking depends on the product, e.g. a window moves the handle * a little bit. * * @param EnableWink If false wink will be stopped. * @param WinkTime Wink time in seconds (up to 253) or 254 for manufactor defined or 255 for infinite time. * @param PriorityLevel The priority level for the run command. * @param CommandOriginator The command originator for the run command. * @returns {Promise<number>} * @memberof Product */ async winkAsync(EnableWink = true, WinkTime = 254, PriorityLevel = 3, CommandOriginator = 1) { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_WINK_SEND_REQ(this.NodeID, EnableWink, WinkTime, PriorityLevel, CommandOriginator))); if (confirmationFrame.Status === GW_INVERSE_STATUS.SUCCESS) { return confirmationFrame.SessionID; } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Refresh the data of this product and read the attributes from the gateway. * * This method re-reads the data from the KLF-200. If the product hasn't sent * its recent data to the KLF-200, call [requestStatusAsync](Products.requestStatusAsync) first. * * @returns {Promise<void>} * @memberof Product */ async refreshAsync() { try { const confirmationFrame = (await this.Connection.sendFrameAsync(new GW_GET_NODE_INFORMATION_REQ(this.NodeID))); if (confirmationFrame.Status === GW_COMMON_STATUS.SUCCESS) { return Promise.resolve(); } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } setupWaitForLimitationFinished(sessionID, limitationType, parameterActive, resolve, reject) { const limitationTypes = []; if (Array.isArray(limitationType)) { limitationTypes.push(...limitationType); } else { limitationTypes.push(limitationType); } // Listen to notifications: const dispose = this.Connection.on(async (frame) => { try { debug(`Calling handler for GW_LIMITATION_STATUS_NTF, GW_SESSION_FINISHED_NTF in Product.setupWaitForLimitationFinished.`); if (frame instanceof GW_LIMITATION_STATUS_NTF && frame.SessionID === sessionID) { if (frame.NodeID !== this.NodeID) { throw new Error(`Unexpected node ID: ${frame.NodeID}`); } if (frame.ParameterID !== parameterActive) { throw new Error(`Unexpected parameter ID: ${frame.ParameterID}`); } if (limitationTypes.indexOf(LimitationType.MinimumLimitation) !== -1) { if (frame.LimitationValueMin !== this._limitationMinRaw[frame.ParameterID]) { this._limitationMinRaw[frame.ParameterID] = frame.LimitationValueMin; await this.propertyChanged("LimitationMinRaw"); } } if (limitationTypes.indexOf(LimitationType.MaximumLimitation) !== -1) { if (frame.LimitationValueMax !== this._limitationMaxRaw[frame.ParameterID]) { this._limitationMaxRaw[frame.ParameterID] = frame.LimitationValueMax; await this.propertyChanged("LimitationMaxRaw"); } } if (frame.LimitationOriginator !== this._limitationOriginator[frame.ParameterID]) { this._limitationOriginator[frame.ParameterID] = frame.LimitationOriginator; await this.propertyChanged("LimitationOriginator"); } if (frame.LimitationTime !== this._limitationTimeRaw[frame.ParameterID]) { this._limitationTimeRaw[frame.ParameterID] = frame.LimitationTime; await this.propertyChanged("LimitationTimeRaw"); } } else if (frame instanceof GW_SESSION_FINISHED_NTF && frame.SessionID === sessionID) { dispose?.dispose(); resolve(); } } catch (error) { dispose?.dispose(); reject(error); } }, [GatewayCommand.GW_LIMITATION_STATUS_NTF, GatewayCommand.GW_SESSION_FINISHED_NTF]); return dispose; } /** * Refreshes the limitation data for the provided limitation type of a parameter. * * @param limitationType The limitation type for which the data should be refreshed. * @param parameterActive Parameter for which the limitation should be refreshed. * @returns Promise<void> */ async refreshLimitationAsync(limitationType, parameterActive = ParameterActive.MP) { try { // Setup the event handlers first to prevent a race condition // where we don't see the events. let resolve, reject; const waitForLimitationFinishedPromise = new Promise((res, rej) => { resolve = res; reject = rej; }); const frameToSend = new GW_GET_LIMITATION_STATUS_REQ(this.NodeID, limitationType, parameterActive); this.setupWaitForLimitationFinished(frameToSend.SessionID, limitationType, parameterActive, resolve, reject); const confirmationFrame = await this.Connection.sendFrameAsync(frameToSend); if (confirmationFrame.Status === GW_INVERSE_STATUS.SUCCESS) { await waitForLimitationFinishedPromise; } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Sets a new limitation with raw values. * * @param minValue Raw min value of the limitation. * @param maxValue Raw max value of the limitation. * @param parameterActive Parameter for which the limitation should be set. * @param limitationTime Limitation time. * @param commandOriginator Command Originator. * @param priorityLevel Priority Level. * @returns Promise<void> */ async setLimitationRawAsync(minValue, maxValue, parameterActive = ParameterActive.MP, limitationTime = 253, // Unlimited time commandOriginator = CommandOriginator.SAAC, priorityLevel = PriorityLevel.ComfortLevel2) { try { // Setup the event handlers first to prevent a race condition // where we don't see the events. let resolve, reject; const waitForLimitationFinishedPromise = new Promise((res, rej) => { resolve = res; reject = rej; }); const frameToSend = new GW_SET_LIMITATION_REQ(this.NodeID, minValue, maxValue, limitationTime, priorityLevel, commandOriginator, parameterActive); this.setupWaitForLimitationFinished(frameToSend.SessionID, [LimitationType.MinimumLimitation, LimitationType.MaximumLimitation], parameterActive, resolve, reject); const confirmationFrame = await this.Connection.sendFrameAsync(frameToSend); if (confirmationFrame.Status === GW_INVERSE_STATUS.SUCCESS) { await waitForLimitationFinishedPromise; } else { return Promise.reject(new Error(confirmationFrame.getError())); } } catch (error) { return Promise.reject(error); } } /** * Sets a new limitation. * * @param minValue Min value of the limitation in the range [0, 1]. * @param maxValue Max value of the limitation in the range [0, 1]. * @param parameterActive Parameter for which the limitation should be set. * @param limitationTime Limitation time in seconds. Must be a multiple of 30. * @param commandOriginator Command Originator. * @param priorityLevel Priority Level. * @returns Promise<void> */ async setLimitationAsync(minValue, maxValue, parameterActive = ParameterActive.MP, limitationTime = Infinity, // Unlimited time commandOriginator = CommandOriginator.SAAC, priorityLevel = PriorityLevel.ComfortLevel2) { try { if (minValue > maxValue) { throw new Error(`Parameter minValue (${minValue}) must be less than or equal to parameter maxValue (${maxValue}).`); } if (minValue < 0 || minValue > 1) { throw new Error("Parameter minValue must be between 0 and 1."); } if (maxValue < 0 || maxValue > 1) { throw new Error("Parameter maxValue must be between 0 and 1."); } let rawMinValue = convertPosition(minValue, this.TypeID); let rawMaxValue = convertPosition(maxValue, this.TypeID); const rawLimitationTime = LockTime.lockTimeTolockTimeValueForLimitation(limitationTime); if (rawMinValue > rawMaxValue) { // Based on the actuator type the min/max values have to be swapped [rawMinValue, rawMaxValue] = [rawMaxValue, rawMinValue]; } return this.setLimitationRawAsync(rawMinValue, rawMaxValue, parameterActive, rawLimitationTime, commandOriginator, priorityLevel); } catch (error) { return Promise.reject(error); } } /** * Clears the limitation for the parameter. * * @param parameterActive Parameter for which the limitation should be set. * @param commandOriginator Command Originator. * @param priorityLevel Priority Level. * @returns Promise<void> */ async clearLimitationAsync(parameterActive = ParameterActive.MP, commandOriginator = CommandOriginator.SAAC, priorityLevel = PriorityLevel.ComfortLevel2) { return this.setLimitationRawAsync(0xd400, 0xd400, parameterActive, 255, commandOriginator, priorityLevel); } async onNotificationHandler(frame) { if (typeof this === "undefined") return; if (frame instanceof GW_NODE_INFORMATION_CHANGED_NTF) { await this.onNodeInformationChanged(frame); } else if (frame instanceof GW_NODE_STATE_POSITION_CHANGED_NTF) { await this.onNodeStatePositionChanged(frame); } else if (frame instanceof GW_COMMAND_RUN_STATUS_NTF) { await this.onRunStatus(frame); } else if (frame instanceof GW_COMMAND_REMAINING_TIME_NTF) { await this.onRemainingTime(frame); } else if (frame instanceof GW_GET_NODE_INFORMATION_NTF) { await this.onGetNodeInformation(frame); } else if (frame instanceof GW_STATUS_REQUEST_NTF) { await this.onStatusRequest(frame); } } async onNodeInformationChanged(frame) { if (frame.NodeID === this.NodeID) { if (frame.Name !== this._name) { this._name = frame.Name; await this.propertyChanged("Name"); } if (frame.NodeVariation !== this._nodeVariation) { this._nodeVariation = frame.NodeVariation; await this.propertyChanged("NodeVariation"); } if (frame.Order !== this._order) { this._order = frame.Order; await this.propertyChanged("Order"); } if (frame.Placement !== this._placement) { this._placement = frame.Placement; await this.propertyChanged("Placement"); } } } async onNodeStatePositionChanged(frame) { if (frame.NodeID === this.NodeID) { if (frame.OperatingState !== this._state) { this._state = frame.OperatingState; await this.propertyChanged("State"); } if (frame.CurrentPosition !== this._currentPositionRaw) { this._currentPositionRaw = frame.CurrentPosition; await this.propertyChanged("CurrentPositionRaw"); await this.propertyChanged("CurrentPosition"); } if (frame.TargetPosition !== this._targetPositionRaw) { this._targetPositionRaw = frame.TargetPosition; await this.propertyChanged("TargetPositionRaw"); await this.propertyChanged("TargetPosition"); } if (frame.FunctionalPosition1CurrentPosition !== this._fp1CurrentPositionRaw) { this._fp1CurrentPositionRaw = frame.FunctionalPosition1CurrentPosition; await this.propertyChanged("FP1CurrentPositionRaw"); } if (frame.FunctionalPosition2CurrentPosition !== this._fp2CurrentPositionRaw) { this._fp2CurrentPositionRaw = frame.FunctionalPosition2CurrentPosition; await this.propertyChanged("FP2CurrentPositionRaw"); } if (frame.FunctionalPosition3CurrentPosition !== this._fp3CurrentPositionRaw) { this._fp3CurrentPositionRaw = frame.FunctionalPosition3CurrentPosition; await this.propertyChanged("FP3CurrentPositionRaw"); } if (frame.FunctionalPosition4CurrentPosition !== this._fp4CurrentPositionRaw) { this._fp4CurrentPositionRaw = frame.FunctionalPosition4CurrentPosition; await this.propertyChanged("FP4CurrentPositionRaw"); } if (frame.RemainingTime !== this._remainingTime) { this._remainingTime = frame.RemainingTime; await this.propertyChanged("RemainingTime"); } // if (frame.TimeStamp.valueOf() !== this._timeStamp.valueOf()) { // this._timeStamp = frame.TimeStamp; // await this.propertyChanged("TimeStamp"); // } } } async onRunStatus(frame) { if (frame.NodeID === this.NodeID) { switch (frame.NodeParameter) { case ParameterActive.MP: if (frame.ParameterValue !== this._currentPositionRaw) { this._currentPositionRaw = frame.ParameterValue; await this.propertyChanged("CurrentPositionRaw"); await this.propertyChanged("CurrentPosition"); } break; case ParameterActive.FP1: if (frame.ParameterValue !== this._fp1CurrentPositionRaw) { this._fp1CurrentPositionRaw = frame.ParameterValue; await this.propertyChanged("FP1CurrentPositionRaw"); } break; case ParameterActive.FP2: if (frame.ParameterValue !== this._fp2CurrentPositionRaw) { this._fp2CurrentPositionRaw = frame.ParameterValue; await this.propertyChanged("FP2CurrentPositionRaw"); } break; case ParameterActive.FP3: if (frame.ParameterValue !== this._fp3CurrentPositionRaw) { this._fp3CurrentPositionRaw = frame.ParameterValue; await this.propertyChanged("FP3CurrentPositionRaw"); } break; case ParameterActive.FP4: if (frame.ParameterValue !== this._fp4CurrentPositionRaw) { this._fp4CurrentPositionRaw = frame.ParameterValue; await this.propertyChanged("FP4CurrentPositionRaw"); } break; default: break; } if (frame.RunStatus !== this._runStatus) { this._runStatus = frame.RunStatus; await this.propertyChanged("RunStatus"); } if (frame.StatusReply !== this._statusReply) { this._statusReply = frame.StatusReply; await this.propertyChanged("StatusReply"); } } } async onRemainingTime(frame) { if (frame.NodeID === this.NodeID && frame.NodeParameter === ParameterActive.MP && frame.RemainingTime !== this._remainingTime) { this._remainingTime = frame.RemainingTime; await this.propertyChanged("RemainingTime"); } } async onGetNodeInformation(frame) { if (frame.NodeID === this.NodeID) { if (frame.Order !== this._order) { this._order = frame.Order; await this.propertyChanged("Order"); } if (frame.Placement !== this._placement) { this._placement = frame.Placement; await this.propertyChanged("Placement"); } if (frame.Name !== this._name) { this._name = frame.Name; await this.propertyChanged("Name"); } if (frame.Velocity !== this._velocity) { this._velocity = frame.Velocity; await this.propertyChanged("Velocity"); } if (frame.ActuatorType !== this._TypeID) { this._TypeID = frame.ActuatorType; await this.propertyChanged("TypeID"); } if (frame.ActuatorSubType !== this._SubType) { this._SubType = frame.ActuatorSubType; await this.propertyChanged("SubType"); } if (frame.ProductType !== this._ProductType) { this._ProductType = frame.ProductType; await this.propertyChanged("ProductType"); } if (frame.NodeVariation !== this._nodeVariation) { this._nodeVariation = frame.NodeVariation; await this.propertyChanged("NodeVariation"); } if (frame.PowerSaveMode !== this._PowerSaveMode) { this._PowerSaveMode = frame.PowerSaveMode; await this.propertyChanged("PowerSaveMode"); } if (!frame.SerialNumber.equals(this._SerialNumber)) { this._SerialNumber = frame.SerialNumber; await this.propertyChanged("SerialNumber"); } if (frame.OperatingState !== this._state) { this._state = frame.OperatingState; await this.propertyChanged("State"); } if (frame.CurrentPosition !== this._currentPositionRaw) { this._currentPositionRaw = frame.CurrentPosition; await this.propertyChanged("CurrentPositionRaw"); await this.propertyChanged("CurrentPosition"); } if (frame.TargetPosition !== this._targetPositionRaw) { this._targetPositionRaw = frame.TargetPosition; await this.propertyChanged("TargetPositionRaw"); await this.propertyChanged("TargetPosition"); } if (frame.FunctionalPosition1CurrentPosition !== this._fp1CurrentPositionRaw) { this._fp1CurrentPositionRaw = frame.FunctionalPosition1CurrentPosition; await this.propertyChanged("FP1CurrentPositionRaw"); } if (frame.FunctionalPosition2CurrentPosition !== this._fp2CurrentPositionRaw) { this._fp2CurrentPositionRaw = frame.FunctionalPosition2CurrentPosition; await this.propertyChanged("FP2CurrentPositionRaw"); } if (frame.FunctionalPosition3CurrentPosition !== this._fp3CurrentPositionRaw) { this._fp3CurrentPositionRaw = frame.FunctionalPosition3CurrentPosition; await this.propertyChanged("FP3CurrentPositionRaw"); } if (frame.FunctionalPosition4CurrentPosition !== this._fp4CurrentPositionRaw) { this._fp4CurrentPositionRaw = frame.FunctionalPosition4CurrentPosition; await this.propertyChanged("FP4CurrentPositionRaw"); } if (frame.RemainingTime !== this._remainingTime) { this._remainingTime = frame.RemainingTime; await this.propertyChanged("RemainingTime"); } if (frame.TimeStamp.valueOf() !== this._timeStamp.valueOf()) { this._timeStamp = frame.TimeStamp; await this.propertyChanged("TimeStamp"); } if ( // If length differ, then they can't be equal anymore this._ProductAlias.length !== frame.ActuatorAliases.length || // Check if some current elements are missing in new frame elements this._ProductAlias.some((v1) => !frame.ActuatorAliases.some((v2) => v1.AliasType === v2.AliasType && v1.AliasValue === v2.AliasValue)) || // Check if some new frame elements are missing in current elements frame.ActuatorAliases.some((v1) => !this._ProductAlias.some((v2) => v1.AliasType === v2.AliasType && v1.AliasValue === v2.AliasValue))) { this._ProductAlias = frame.ActuatorAliases; await this.propertyChanged("ProductAlias"); } } } async onStatusRequest(frame) { if (frame.NodeID === this.NodeID) { if (this._runStatus !== frame.RunStatus) { this._runStatus = frame.RunStatus; await this.propertyChanged("RunStatus"); } if (this._statusReply !== frame.StatusReply) { this._statusReply = frame.StatusReply; await this.propertyChanged("StatusReply"); } switch (frame.StatusType) { case StatusType.RequestMainInfo: if (this._targetPositionRaw !== frame.TargetPosition) { this._targetPositionRaw = frame.Tar