UNPKG

homebridge-fujitsu-airstage

Version:

A Homebridge plugin to control devices that use the Fujitsu Airstage API.

1,025 lines (816 loc) 31.4 kB
'use strict'; const api = require('./api'); const constants = require('./../constants'); class Client { constructor( localDevices, temperatureScalePreference, userAgent = null ) { this.localDevices = localDevices; this.temperatureScalePreference = temperatureScalePreference; this._apiClient = new api.Client( userAgent || null ); this.resetDeviceCache(); this._setDeviceIdToLocalDeviceMap(); } getTemperatureScale(callback) { if (constants.TEMPERATURE_SCALES.includes(this.temperatureScalePreference) === false) { return callback('Invalid temperature scale: ' + this.temperatureScalePreference, null); } callback(null, this.temperatureScalePreference); } setTemperatureScale(scale, callback) { if (constants.TEMPERATURE_SCALES.includes(scale) === false) { return callback('Invalid temperature scale: ' + scale, null); } this.temperatureScalePreference = scale; callback(null, this.temperatureScalePreference); } getName(deviceId, callback) { const displayName = this._getDisplayNameForDeviceId(deviceId); if (displayName === null) { return callback('Could not determine name for device ID: ' + deviceId, null); } callback(null, displayName); } getModel(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_MODEL, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } callback(null, parameterValue); }).bind(this) ); } getPowerState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_ON_OFF, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setPowerState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_ON_OFF, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_ON_OFF] ); } callback(null, result); }).bind(this) ); } getOperationMode(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_OPERATION_MODE, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToOperationMode(parameterValue); callback(null, result); }).bind(this) ); } setOperationMode(deviceId, operationMode, callback) { const parameterValue = this._operationModeToParameterValue(operationMode); this.setParameter( deviceId, constants.PARAMETER_OPERATION_MODE, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (operationMode === constants.OPERATION_MODE_DRY) { // These things automatically happen on the device side // when turning on dry mode, so let's update // them in the local device cache: // - The fan speed is set to "AUTO" this._setDeviceParameterCache( deviceId, { 'parameters': [ { 'name': constants.PARAMETER_FAN_SPEED, 'value': constants.PARAMETER_FAN_SPEED_AUTO } ] } ); } if (device.parameters) { result = this._parameterValueToOperationMode( device.parameters[constants.PARAMETER_OPERATION_MODE] ); } callback(null, result); }).bind(this) ); } getIndoorTemperature(deviceId, scale, callback) { this.getParameter( deviceId, constants.PARAMETER_INDOOR_TEMPERATURE, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } if (parameterValue !== null) { result = this._indoorIntValueToTemperature(parameterValue); if (scale === constants.TEMPERATURE_SCALE_FAHRENHEIT) { result = this._celsiusToFahrenheit(result); } } callback(null, result); }).bind(this) ); } getTargetTemperature(deviceId, scale, callback) { this.getParameter( deviceId, constants.PARAMETER_SET_TEMPERATURE, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._intValueToTemperature(parameterValue); if (scale === constants.TEMPERATURE_SCALE_FAHRENHEIT) { result = this._celsiusToFahrenheit(result); } callback(null, result); }).bind(this) ); } setTargetTemperature(deviceId, temperature, scale, callback) { let intValue = null; if (scale === constants.TEMPERATURE_SCALE_FAHRENHEIT) { temperature = this._fahrenheitToCelsius(temperature); } else if (scale === constants.TEMPERATURE_SCALE_CELSIUS) { temperature = this._getClosestValidTemperature( temperature, constants.TEMPERATURE_SCALE_CELSIUS ); } intValue = this._temperatureToIntValue(temperature); this.setParameter( deviceId, constants.PARAMETER_SET_TEMPERATURE, intValue.toString(), (function(error, device) { let result = null; let parameterValue = null; if (error) { return callback(error, null); } if (device.parameters) { parameterValue = device.parameters[constants.PARAMETER_SET_TEMPERATURE]; result = this._intValueToTemperature(parameterValue); if (scale === constants.TEMPERATURE_SCALE_FAHRENHEIT) { result = this._celsiusToFahrenheit(result); } } callback(null, result); }).bind(this) ); } getTemperatureDelta(deviceId, scale, callback) { this.getIndoorTemperature(deviceId, scale, (function(error, indoorTemperature) { if (error) { return callback(error, null); } this.getTargetTemperature(deviceId, scale, (function(error, targetTemperature) { let temperatureDelta = null; if (error) { return callback(error, null); } temperatureDelta = (indoorTemperature - targetTemperature); callback(null, temperatureDelta); }).bind(this)); }).bind(this)); } getFanSpeed(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_FAN_SPEED, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToFanSpeed(parameterValue); callback(null, result); }).bind(this) ); } setFanSpeed(deviceId, fanSpeed, callback) { const parameterValue = this._fanSpeedToParameterValue(fanSpeed); this.setParameter( deviceId, constants.PARAMETER_FAN_SPEED, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToFanSpeed( device.parameters[constants.PARAMETER_FAN_SPEED] ); } callback(null, result); }).bind(this) ); } getAirflowVerticalDirection(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_AIRFLOW_VERTICAL_DIRECTION, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._validateAirflowVerticalDirectionValue( parseInt(parameterValue) ); callback(null, result); }).bind(this) ); } setAirflowVerticalDirection(deviceId, value, callback) { const parameterValue = this._validateAirflowVerticalDirectionValue(value).toString(); this.setParameter( deviceId, constants.PARAMETER_AIRFLOW_VERTICAL_DIRECTION, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._validateAirflowVerticalDirectionValue( parseInt(device.parameters[constants.PARAMETER_AIRFLOW_VERTICAL_DIRECTION]) ); } callback(null, result); }).bind(this) ); } getAirflowVerticalSwingState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_AIRFLOW_VERTICAL_SWING, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setAirflowVerticalSwingState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_AIRFLOW_VERTICAL_SWING, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_AIRFLOW_VERTICAL_SWING] ); } callback(null, result); }).bind(this) ); } getPowerfulState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_POWERFUL, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setPowerfulState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_POWERFUL, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_POWERFUL] ); } callback(null, result); }).bind(this) ); } getEconomyState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_ECONOMY, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setEconomyState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_ECONOMY, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_ECONOMY] ); } callback(null, result); }).bind(this) ); } getEnergySavingFanState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_ENERGY_SAVING_FAN, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setEnergySavingFanState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_ENERGY_SAVING_FAN, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_ENERGY_SAVING_FAN] ); } callback(null, result); }).bind(this) ); } getMinimumHeatState(deviceId, callback) { this.getParameter( deviceId, constants.PARAMETER_MINIMUM_HEAT, (function(error, parameterValue) { let result = null; if (error) { return callback(error, null); } result = this._parameterValueToToggle(parameterValue); callback(null, result); }).bind(this) ); } setMinimumHeatState(deviceId, toggle, callback) { const parameterValue = this._toggleToParameterValue(toggle); this.setParameter( deviceId, constants.PARAMETER_MINIMUM_HEAT, parameterValue, (function(error, device) { let result = null; if (error) { return callback(error, null); } if (toggle === constants.TOGGLE_ON) { // These things automatically happen on the device side // when turning on minimum heat, so let's update // them in the local device cache: // - The power state is set to "ON" // - The fan speed is set to "AUTO" // - The operation mode is set to "HEAT" // - The temperature is set to 10 degrees (C) this._setDeviceParameterCache( deviceId, { 'parameters': [ { 'name': constants.PARAMETER_ON_OFF, 'value': constants.PARAMETER_ON }, { 'name': constants.PARAMETER_FAN_SPEED, 'value': constants.PARAMETER_FAN_SPEED_AUTO }, { 'name': constants.PARAMETER_OPERATION_MODE, 'value': constants.PARAMETER_OPERATION_MODE_HEAT }, { 'name': constants.PARAMETER_SET_TEMPERATURE, 'value': '100' } ] } ); } else if (toggle === constants.TOGGLE_OFF) { // These things automatically happen on the device side // when turning off minimum heat, so let's update // them in the local device cache: // - The power state is set to "OFF" this._setDeviceParameterCache( deviceId, { 'parameters': [ { 'name': constants.PARAMETER_ON_OFF, 'value': constants.PARAMETER_OFF } ] } ); } if (device.parameters) { result = this._parameterValueToToggle( device.parameters[constants.PARAMETER_MINIMUM_HEAT] ); } callback(null, result); }).bind(this) ); } getParameter(deviceId, name, callback) { this.getDevice(deviceId, function(error, device) { let result = null; if (error) { return callback(error, null); } if (device.parameters) { result = device.parameters[name]; } if (result === null || result === undefined) { return callback('Parameter not available: ' + name, null); } callback(null, result); }); } setParameter(deviceId, name, value, callback) { const deviceSubId = '0'; const hostname = this._getHostnameForDeviceId(deviceId); if (hostname === null) { return callback('No hostname for device ID: ' + deviceId, null); } const postSetParamValue = {}; postSetParamValue[name] = value; this._apiClient.postSetParam( hostname, deviceId, deviceSubId, postSetParamValue, (function(result) { if (result.error) { return callback(result.error, null); } let deviceParameters = null; if (result.response.value) { const device = { 'parameters': [] }; Object.keys(result.response.value).forEach(function(parameterName) { device.parameters.push({ 'name': parameterName, 'value': result.response.value[parameterName] }); }, this); deviceParameters = this._setDeviceParameterCache(deviceId, device); } callback(null, { 'parameters': deviceParameters }); }).bind(this) ); } getDevices(limit, callback) { const devicesFromCache = this._getDevicesFromCache(); const isCacheHit = ( devicesFromCache.parameters && Object.keys(devicesFromCache.parameters).length > 0 ); if (isCacheHit) { return callback(null, devicesFromCache); } this._getDevicesFromApi(callback); } getDevice(deviceId, callback) { const deviceFromCache = this._getDeviceFromCache(deviceId); const isCacheHit = ( deviceFromCache.parameters !== null ); if (isCacheHit) { return callback(null, deviceFromCache); } this._getDeviceFromApi(deviceId, callback); } resetDeviceCache(deviceId = null) { if (deviceId === null) { this._deviceParameterCache = {}; } else { if (deviceId in this._deviceParameterCache) { delete this._deviceParameterCache[deviceId]; } } } refreshDeviceCache(callback) { this._getDevicesFromApi( (function(error, result) { if (error) { return callback(error, null); } callback(null, result); }).bind(this) ); } _setDeviceIdToLocalDeviceMap() { this._deviceIdToLocalDeviceMap = {}; this.localDevices.forEach(function(localDevice) { const deviceId = this._getDeviceIdFromLocalDevice(localDevice); this._deviceIdToLocalDeviceMap[deviceId] = localDevice; }, this); } _getDeviceIds() { const deviceIds = []; this.localDevices.forEach((function(localDevice) { const deviceId = this._getDeviceIdFromLocalDevice(localDevice); deviceIds.push(deviceId); }).bind(this)); return deviceIds; } _getDeviceIdFromLocalDevice(localDevice) { return localDevice.macAddress.replaceAll(':', '').toUpperCase(); } _getHostnameForDeviceId(deviceId) { let hostname = null; if (deviceId in this._deviceIdToLocalDeviceMap) { hostname = this._deviceIdToLocalDeviceMap[deviceId].hostname; } return hostname; } _getDisplayNameForDeviceId(deviceId) { let displayName = null; if (deviceId in this._deviceIdToLocalDeviceMap) { displayName = this._deviceIdToLocalDeviceMap[deviceId].displayName; } return displayName; } _getDeviceParameterCache(deviceId) { let result = null; if (deviceId === null) { result = this._deviceParameterCache; } else if (deviceId in this._deviceParameterCache) { result = this._deviceParameterCache[deviceId]; } return result; } _setDeviceParameterCache(deviceId, device) { if ((deviceId in this._deviceParameterCache) === false) { this._deviceParameterCache[deviceId] = {}; } if (device && 'parameters' in device) { device.parameters.forEach(function(parameter) { let parameterName = null; let parameterValue = null; if (parameter.name) { parameterName = parameter.name; } if (parameter.value !== constants.PARAMETER_NOT_AVAILABLE) { parameterValue = parameter.value; } if (parameterName) { this._deviceParameterCache[deviceId][parameterName] = parameterValue; } }, this); } return this._getDeviceParameterCache(deviceId); } _getDevicesFromCache() { return this._getDeviceFromCache(null); } _getDevicesFromApi(callback) { const deviceIds = this._getDeviceIds(); this._getGivenDevicesFromApi(deviceIds, callback); } _getGivenDevicesFromApi(deviceIds, callback) { const deviceId = deviceIds.shift(); if (deviceId === undefined) { const deviceParameters = this._getDeviceParameterCache(null); return callback(null, { 'parameters': deviceParameters }); } this._getDeviceFromApi( deviceId, (function(error, result) { if (error) { return callback(error, null); } this._getGivenDevicesFromApi(deviceIds, callback); }).bind(this) ); } _getDeviceFromCache(deviceId) { return { 'parameters' : this._getDeviceParameterCache(deviceId) }; } _getDeviceFromApi(deviceId, callback) { const deviceSubId = '0'; const hostname = this._getHostnameForDeviceId(deviceId); if (hostname === null) { return callback('No hostname for device ID: ' + deviceId, null); } let device = null; let deviceParameters = null; // For some reason, the response gets truncated when `iu_model` is // included in the request list. Therefore, let's request all parameters // besides `iu_model` first, then request `iu_model` after that this._apiClient.postGetParam( hostname, deviceId, deviceSubId, constants.PARAMETER_NAMES_BESIDES_MODEL, (function(result) { if (result.error) { return callback(result.error, null); } if (result.response.value) { device = { 'parameters': [] }; Object.keys(result.response.value).forEach(function(parameterName) { device.parameters.push({ 'name': parameterName, 'value': result.response.value[parameterName] }); }, this); deviceParameters = this._setDeviceParameterCache(deviceId, device); } this._apiClient.postGetParam( hostname, deviceId, deviceSubId, constants.PARAMETER_NAMES_ONLY_MODEL, (function(result) { if (result.error) { return callback(result.error, null); } if (result.response.value) { if (device === null) { device = { 'parameters': [] }; } Object.keys(result.response.value).forEach(function(parameterName) { device.parameters.push({ 'name': parameterName, 'value': result.response.value[parameterName] }); }, this); deviceParameters = this._setDeviceParameterCache(deviceId, device); } callback(null, { 'parameters': deviceParameters }); }).bind(this) ); }).bind(this) ); } _getClosestValidTemperature(temperature, scale) { let closestTemperature = null; if (scale === constants.TEMPERATURE_SCALE_FAHRENHEIT) { closestTemperature = constants.VALID_FAHRENHEIT_VALUES.reduce(function(x, y) { return (Math.abs(y - temperature) < Math.abs(x - temperature) ? y : x); }); } else if (scale === constants.TEMPERATURE_SCALE_CELSIUS) { closestTemperature = constants.VALID_CELSIUS_VALUES.reduce(function(x, y) { return (Math.abs(y - temperature) < Math.abs(x - temperature) ? y : x); }); } return closestTemperature; } _temperatureToIntValue(temperature) { return (temperature * 10); } _indoorIntValueToTemperature(intValue) { return ((intValue - 5000) / 100); } _intValueToTemperature(intValue) { return (intValue / 10); } _fahrenheitToCelsius(fahrenheit) { const closestFahrenheit = this._getClosestValidTemperature( fahrenheit, constants.TEMPERATURE_SCALE_FAHRENHEIT ); return constants.FAHRENHEIT_TO_CELSIUS_MAP[closestFahrenheit]; } _celsiusToFahrenheit(celsius) { const closestCelsius = this._getClosestValidTemperature( celsius, constants.TEMPERATURE_SCALE_CELSIUS ); return constants.CELSIUS_TO_FAHRENHEIT_MAP[closestCelsius]; } _operationModeToParameterValue(operationMode) { return constants.OPERATION_MODE_TO_PARAMETER_VALUE_MAP[operationMode]; } _parameterValueToOperationMode(parameterValue) { return constants.PARAMETER_VALUE_TO_OPERATION_MODE_MAP[parameterValue]; } _fanSpeedToParameterValue(fanSpeed) { return constants.FAN_SPEED_TO_PARAMETER_VALUE_MAP[fanSpeed]; } _parameterValueToFanSpeed(parameterValue) { return constants.PARAMETER_VALUE_TO_FAN_SPEED_MAP[parameterValue]; } _toggleToParameterValue(toggle) { return constants.TOGGLE_TO_PARAMETER_VALUE_MAP[toggle]; } _parameterValueToToggle(parameterValue) { return constants.PARAMETER_VALUE_TO_TOGGLE_MAP[parameterValue]; } _validateAirflowVerticalDirectionValue(value) { if (value < constants.MIN_AIRFLOW_VERTICAL_DIRECTION) { value = constants.MIN_AIRFLOW_VERTICAL_DIRECTION; } else if (value > constants.MAX_AIRFLOW_VERTICAL_DIRECTION) { value = constants.MAX_AIRFLOW_VERTICAL_DIRECTION; } return value; } } module.exports = Client;