homebridge-smartthings-v2
Version:
SmartThings plugin for HomeBridge
509 lines (480 loc) • 21.1 kB
JavaScript
var Characteristic;
var CommunityTypes;
module.exports = class Transforms {
constructor(platform, char) {
this.accessories = platform;
this.platform = platform.mainPlatform;
this.client = platform.client;
Characteristic = char;
CommunityTypes = platform.CommunityTypes;
this.log = platform.log;
}
transformStatus(val) {
val = val.toLowerCase() || undefined;
switch (val) {
case "online":
case "active":
return true;
default:
return false;
}
}
transformAttributeState(attr, val, charName) {
switch (attr) {
case "switch":
return (val === 'on');
case "door":
switch (val) {
case "open":
return Characteristic.TargetDoorState.OPEN;
case "opening":
return charName && charName === "Target Door State" ? Characteristic.TargetDoorState.OPEN : Characteristic.TargetDoorState.OPENING;
case "closed":
return Characteristic.TargetDoorState.CLOSED;
case "closing":
return charName && charName === "Target Door State" ? Characteristic.TargetDoorState.CLOSED : Characteristic.TargetDoorState.CLOSING;
default:
return charName && charName === "Target Door State" ? Characteristic.TargetDoorState.OPEN : Characteristic.TargetDoorState.STOPPED;
}
case "fanMode":
switch (val) {
case "low":
return CommunityTypes.FanOscilationMode.LOW;
case "medium":
return CommunityTypes.FanOscilationMode.MEDIUM;
case "high":
return CommunityTypes.FanOscilationMode.HIGH;
default:
return CommunityTypes.FanOscilationMode.SLEEP;
}
case "lock":
switch (val) {
case "locked":
return Characteristic.LockCurrentState.SECURED;
case "unlocked":
return Characteristic.LockCurrentState.UNSECURED;
default:
return Characteristic.LockCurrentState.UNKNOWN;
}
case "button":
switch (val) {
case "pushed":
return Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS;
case "held":
return Characteristic.ProgrammableSwitchEvent.LONG_PRESS;
case "double":
return Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS;
default:
return null;
}
case "supportedButtonValues":
var validValues = [];
if (typeof val === "string") {
for (const v of JSON.parse(val)) {
switch (v) {
case "pushed":
validValues.push(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
continue;
case "double":
validValues.push(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS);
continue;
case "held":
validValues.push(Characteristic.ProgrammableSwitchEvent.LONG_PRESS);
continue;
default:
validValues.push(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
validValues.push(Characteristic.ProgrammableSwitchEvent.LONG_PRESS);
continue;
}
}
} else {
validValues.push(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
validValues.push(Characteristic.ProgrammableSwitchEvent.LONG_PRESS);
}
return validValues;
case "fanState":
return (val === "off") ? Characteristic.CurrentFanState.IDLE : Characteristic.CurrentFanState.BLOWING_AIR;
case "valve":
return (val === "open") ? Characteristic.InUse.IN_USE : Characteristic.InUse.NOT_IN_USE;
case "mute":
return (val === 'muted');
case "smoke":
return (val === "clear") ? Characteristic.SmokeDetected.SMOKE_NOT_DETECTED : Characteristic.SmokeDetected.SMOKE_DETECTED;
case "carbonMonoxide":
return (val === "clear") ? Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL : Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL;
case "carbonDioxideMeasurement":
switch (charName) {
case "Carbon Dioxide Detected":
return (val < 2000) ? Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL : Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL;
default:
return parseInt(val);
}
case "tamper":
return (val === "detected") ? Characteristic.StatusTampered.TAMPERED : Characteristic.StatusTampered.NOT_TAMPERED;
case "acceleration":
case "motion":
return (val === "active");
case "water":
return (val === "dry") ? Characteristic.LeakDetected.LEAK_NOT_DETECTED : Characteristic.LeakDetected.LEAK_DETECTED;
case "contact":
return (val === "closed") ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
case "presence":
return (val === "present");
case "battery":
if (charName === "Status Low Battery") {
return (val < 20) ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
} else {
return Math.round(val);
}
case "batteryStatus":
return (val === "USB Cable") ? Characteristic.ChargingState.CHARGING : Characteristic.ChargingState.NOT_CHARGING;
case "hue":
return Math.round(val * 3.6);
case "colorTemperature":
return this.colorTempFromK(val);
case "temperature":
return this.tempConversion(val);
case "heatingSetpoint":
case "coolingSetpoint":
case "thermostatSetpoint":
return this.thermostatTempConversion(val);
case "fanSpeed":
return this.fanSpeedIntToLevel(val);
case "level":
case "saturation":
case "volume":
return parseInt(val) || 0;
case "illuminance":
return Math.round(Math.ceil(parseFloat(val)), 0);
case "energy":
case "humidity":
case "power":
return Math.round(val);
case "thermostatOperatingState":
switch (val) {
case "pending cool":
case "cooling":
return Characteristic.CurrentHeatingCoolingState.COOL;
case "pending heat":
case "heating":
return Characteristic.CurrentHeatingCoolingState.HEAT;
default:
// The above list should be inclusive, but we need to return something if they change stuff.
// TODO: Double check if Smartthings can send "auto" as operatingstate. I don't think it can.
return Characteristic.CurrentHeatingCoolingState.OFF;
}
case "thermostatMode":
switch (val) {
case "cool":
return Characteristic.TargetHeatingCoolingState.COOL;
case "emergency heat":
case "heat":
return Characteristic.TargetHeatingCoolingState.HEAT;
case "auto":
return Characteristic.TargetHeatingCoolingState.AUTO;
default:
return Characteristic.TargetHeatingCoolingState.OFF;
}
case "thermostatFanMode":
if (val === Characteristic.TargetFanState.MANUAL) {
return 'on';
} else {
return 'auto';
}
case "windowShade":
if (val === 'opening') {
return Characteristic.PositionState.INCREASING;
} else if (val === 'closing') {
return Characteristic.PositionState.DECREASING;
} else {
return Characteristic.PositionState.STOPPED;
}
case "alarmSystemStatus":
return this.convertAlarmState(val);
default:
return val;
}
}
transformCommandName(attr, val) {
switch (attr) {
case "valve":
return (val === 1 || val === true) ? "open" : "close";
case "switch":
return (val === 1 || val === true) ? "on" : "off";
case "door":
if (val === Characteristic.TargetDoorState.OPEN || val === 0) {
return "open";
} else {
return "close";
}
case "lock":
return (val === 1 || val === true) ? "lock" : "unlock";
case "mute":
return (val === "muted") ? "mute" : "unmute";
case "fanSpeed":
case "level":
case "volume":
case "thermostatMode":
case "saturation":
case "hue":
case "colorTemperature":
return `set${attr.charAt(0).toUpperCase() + attr.slice(1)}`;
case "thermostatFanMode":
switch (val) {
case Characteristic.TargetFanState.MANUAL:
return "fanOn";
default:
return "fanAuto";
}
default:
return val;
}
}
transformCommandValue(attr, val) {
switch (attr) {
case "valve":
return (val === 1 || val === true) ? "open" : "close";
case "switch":
return (val === 1 || val === true) ? "on" : "off";
case "lock":
return (val === 1 || val === true) ? "lock" : "unlock";
case "door":
if (val === Characteristic.TargetDoorState.OPEN || val === 0) {
return "open";
} else if (val === Characteristic.TargetDoorState.CLOSED || val === 1) {
return "close";
}
return 'closing';
case "hue":
return Math.round(val / 3.6);
case "colorTemperature":
return this.colorTempToK(val);
case "mute":
return (val === "muted") ? "mute" : "unmute";
case "alarmSystemStatus":
return this.convertAlarmCmd(val);
case "fanSpeed":
if (val === 0) {
return 0;
} else if (val < 34) {
return 1;
} else if (val < 67) {
return 2;
} else {
return 3;
}
case "thermostatMode":
switch (val) {
case Characteristic.TargetHeatingCoolingState.COOL:
return "cool";
case Characteristic.TargetHeatingCoolingState.HEAT:
return "heat";
case Characteristic.TargetHeatingCoolingState.AUTO:
return "auto";
case Characteristic.TargetHeatingCoolingState.OFF:
return "off";
default:
return undefined;
}
case "fanMode":
if (val >= 0 && val <= CommunityTypes.FanOscilationMode.SLEEP) {
return "sleep";
} else if (val > CommunityTypes.FanOscilationMode.SLEEP && val <= CommunityTypes.FanOscilationMode.LOW) {
return "low";
} else if (val > CommunityTypes.FanOscilationMode.LOW && val <= CommunityTypes.FanOscilationMode.MEDIUM) {
return "medium";
} else if (val > CommunityTypes.FanOscilationMode.MEDIUM && val <= CommunityTypes.FanOscilationMode.HIGH) {
return "high";
} else {
return "sleep";
}
default:
return val;
}
}
colorTempFromK(temp) {
return (1000000 / temp).toFixed();
}
colorTempToK(temp) {
return (1000000 / temp).toFixed();
}
thermostatTempConversion(temp, isSet = false) {
if (isSet) {
return (this.platform.getTempUnit() === 'C') ? Math.round(temp) : Math.round(temp * 1.8 + 32);
} else {
return (this.platform.getTempUnit() === 'C') ? Math.round(temp * 10) / 10 : Math.round((temp - 32) / 1.8 * 10) / 10;
}
}
thermostatTargetTemp(devData) {
// console.log('ThermostatMode:', devData.attributes.thermostatMode, ' | thermostatOperatingState: ', devData.attributes.thermostatOperatingState);
switch (devData.attributes.thermostatMode) {
case 'cool':
case 'cooling':
return devData.attributes.coolingSetpoint;
case 'emergency heat':
case 'heat':
case 'heating':
return devData.attributes.heatingSetpoint;
default:
{
const cool = devData.attributes.coolingSetpoint;
const heat = devData.attributes.heatingSetpoint;
const cur = devData.attributes.temperature;
const cDiff = Math.abs(cool - cur);
const hDiff = Math.abs(heat - cur);
const useCool = cDiff < hDiff;
// console.log('(cool-cur):', cDiff);
// console.log('(heat-cur):', hDiff);
// console.log(`targerTemp(GET) | cool: ${cool} | heat: ${heat} | cur: ${cur} | useCool: ${useCool}`);
return useCool ? cool : heat;
}
}
}
thermostatSupportedModes(devData) {
let hasHeatSetpoint = (devData.attributes.heatingSetpoint !== undefined || devData.attributes.heatingSetpoint !== null);
let hasCoolSetpoint = (devData.attributes.coolingSetpoint !== undefined || devData.attributes.coolingSetpoint !== null);
let sModes = devData.attributes.supportedThermostatModes || [];
let validModes = [Characteristic.TargetHeatingCoolingState.OFF];
if ((sModes.length && sModes.includes("heat")) || sModes.includes("emergency heat") || hasHeatSetpoint)
validModes.push(Characteristic.TargetHeatingCoolingState.HEAT);
if ((sModes.length && sModes.includes("cool")) || hasCoolSetpoint)
validModes.push(Characteristic.TargetHeatingCoolingState.COOL);
if ((sModes.length && sModes.includes("auto")) || (hasCoolSetpoint && hasHeatSetpoint))
validModes.push(Characteristic.TargetHeatingCoolingState.AUTO);
return validModes;
}
thermostatTargetTemp_set(devData) {
let cmdName;
let attrName;
switch (devData.attributes.thermostatMode) {
case "cool":
cmdName = "setCoolingSetpoint";
attrName = "coolingSetpoint";
break;
case "emergency heat":
case "heat":
cmdName = "setHeatingSetpoint";
attrName = "heatingSetpoint";
break;
default:
{
// This should only refer to auto
// Choose closest target as single target
const cool = devData.attributes.coolingSetpoint;
const heat = devData.attributes.heatingSetpoint;
const cur = devData.attributes.temperature;
const cDiff = Math.abs(cool - cur);
const hDiff = Math.abs(heat - cur);
const useCool = cDiff < hDiff;
// console.log('(cool-cur):', cDiff);
// console.log('(heat-cur):', hDiff);
// console.log(`targerTemp(SET) | cool: ${cool} | heat: ${heat} | cur: ${cur} | useCool: ${useCool}`);
cmdName = useCool ? "setCoolingSetpoint" : "setHeatingSetpoint";
attrName = useCool ? "coolingSetpoint" : "heatingSetpoint";
}
}
return {
cmdName: cmdName,
attrName: attrName
};
}
tempConversion(temp, onlyC = false) {
if (this.platform.getTempUnit() === 'C' || onlyC) {
return (parseFloat(temp * 10) / 10);
} else {
return (parseFloat((temp - 32) / 1.8 * 10) / 10).toFixed(2);
}
}
cToF(temp) {
return (parseFloat(temp * 10) / 10);
}
fToC(temp) {
return (parseFloat((temp - 32) / 1.8 * 10) / 10);
}
fanSpeedConversion(speedVal, has4Spd = false) {
if (speedVal <= 0) {
return "off";
}
if (has4Spd) {
if (speedVal > 0 && speedVal <= 25) {
return "low";
} else if (speedVal > 25 && speedVal <= 50) {
return "med";
} else if (speedVal > 50 && speedVal <= 75) {
return "medhigh";
} else if (speedVal > 75 && speedVal <= 100) {
return "high";
}
} else {
if (speedVal > 0 && speedVal <= 33) {
return "low";
} else if (speedVal > 33 && speedVal <= 66) {
return "medium";
} else if (speedVal > 66 && speedVal <= 99) {
return "high";
}
}
}
fanSpeedConversionInt(speedVal) {
if (!speedVal || speedVal <= 0) {
return "off";
} else if (speedVal === 1) {
return "low";
} else if (speedVal === 2) {
return "medium";
} else if (speedVal === 3) {
return "high";
}
}
fanSpeedIntToLevel(speedVal) {
switch (speedVal) {
case 0:
return 0;
case 1:
return 32;
case 2:
return 66;
case 3:
return 100;
default:
return 0;
}
}
fanSpeedLevelToInt(val) {
if (val > 0 && val < 33) {
return 1;
} else if (val >= 33 && val < 66) {
return 2;
} else if (val >= 66 && val <= 100) {
return 3;
} else {
return 0;
}
}
convertAlarmState(value) {
switch (value) {
case "stay":
case "night":
return Characteristic.SecuritySystemCurrentState.STAY_ARM;
case "away":
return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
case "off":
return Characteristic.SecuritySystemCurrentState.DISARMED;
case "alarm_active":
return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
}
}
convertAlarmCmd(value) {
switch (value) {
case 0:
case 2:
return "stay";
case 1:
return "away";
case 3:
return "off";
case 4:
return "alarm_active";
}
}
};