homebridge-mitsubishi-vac-ir
Version:
Mitsubihishi VAC IR Remote plugin for homebridge: https://github.com/nfarina/homebridge
697 lines (643 loc) • 23 kB
JavaScript
/*
homebridge-mitsubishi-vac-ir
Version 1.4.4
Mitsubihishi VAC IR Remote plugin for homebridge: https://github.com/nfarina/homebridge
Copyright (c) 2017-2023 @Kounch
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED “AS IS” AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
THE USE OR PERFORMANCE OF THIS SOFTWARE.
Sample configuration file
{
"bridge": {
...
},
"description": "...",
"accessories": [
{
"accessory": "MitsubishiVACIR",
"name": "Mitsubishi VAC"
}
],
"platforms":[
...
]
}
*/
var Service, Characteristic;
var SerialPort = require('serialport');
var Net = require('net');
var serialPort = null;
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory("homebridge-mitsubishi-vac-ir", "MitsubishiVACIR", MitsubishiVACIRAccessory);
};
function MitsubishiVACIRAccessory(log, config) {
this.log = log;
this.lastCommandDate = new Date();
this.mode = config.mode || "serial";
if (this.mode == "serial") {
this.portName = config.portname || "/dev/ttyACM0";
this.portSpeed = config.portspeed || 19200;
//this.log(this.portName + "," + this.portSpeed);
serialPort = new SerialPort(this.portName, {
baudRate: this.portSpeed
});
} else if (this.mode == "network") {
this.hostname = config.hostname || "arduino_mitsubishi.local";
} else {
this.log("Unknown mode");
}
var default_references = [{ "sensor_value": 1.0, "real_value": 1.0 }];
var references = config.references || default_references;
if (references.length == 1) {
var x1 = references[0].sensor_value;
var y1 = references[0].real_value;
if (x1 != 0) {
this.slope = y1 / x1;
} else {
this.slope = 1.0;
}
this.intercept = 0.0;
} else {
var x1 = references[0].sensor_value;
var y1 = references[0].real_value;
var x2 = references[1].sensor_value;
var y2 = references[1].real_value;
if (x2 != x1) {
this.slope = (y2 - y1) / (x2 - x1);
} else {
this.slope = 1.0;
}
this.intercept = y1 - (this.slope * x1);
}
this.name = config.name || "Mitsubishi VAC IR Accessory";
this.manufacturer = config.manufacturer || "Mitsubishi";
this.model = config.model || "Infrared Remote";
this.serialnumber = config.serialnumber || "KM05";
this.humidity = config.humidity || false;
// Required Characteristics
//Characteristic.Active.INACTIVE = 0;
//Characteristic.Active.ACTIVE = 1;
this.Active = Characteristic.Active.INACTIVE;
//Read-only
//Characteristic.CurrentHeaterCoolerState.INACTIVE;
//Characteristic.CurrentHeaterCoolerState.IDLE;
//Characteristic.CurrentHeaterCoolerState.HEATING;
//Characteristic.CurrentHeaterCoolerState.COOLING;
this.CurrentHeaterCoolerState = Characteristic.CurrentHeaterCoolerState.INACTIVE;
//Characteristic.TargetHeaterCoolerState.AUTO;
//Characteristic.TargetHeaterCoolerState.HEAT;
//Characteristic.TargetHeaterCoolerState.COOL;
this.TargetHeaterCoolerState = Characteristic.TargetHeaterCoolerState.AUTO;
//Read-only
this.CurrentTemperature = 23;
// Optional Characteristics
//Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
//Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED;
//this.LockPhysicalControls = Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
//Characteristic.SwingMode.SWING_DISABLED;
//Characteristic.SwingMode.SWING_ENABLED;
this.SwingMode = Characteristic.SwingMode.SWING_DISABLED;
this.TargetHorizontalTiltAngle = 0;
this.CurrentHorizontalTiltAngle = 0;
this.CoolingThresholdTemperature = 22.0;
this.HeatingThresholdTemperature = 24.0;
//Characteristic.TemperatureDisplayUnits.CELSIUS;
//Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
this.temperatureDisplayUnits = Characteristic.TemperatureDisplayUnits.CELSIUS;
//Characteristic.TargetFanState.MANUAL = 0;
//Characteristic.TargetFanState.AUTO = 1;
this.TargetFanState = Characteristic.TargetFanState.AUTO;
this.RotationSpeed = 0;
if (this.humidity) {
this.CurrentRelativeHumidity = 50;
}
this.service = new Service.HeaterCooler(this.name);
}
MitsubishiVACIRAccessory.prototype = {
serialSendCmd: function (message, retries, timeout, callback) {
var receivedData = "";
var mssg = message;
//Serial Events
serialPort.on('data', function (data) {
receivedData += data;
}.bind(this));
serialPort.on('close', function () {
var result = "KO,Bad Serial Data,";
var arrData = receivedData.split('\n');
if (arrData[0].trim() == 'BOOT') {
if (arrData.length > 1) {
result = arrData.slice(1).join(",");
}
}
arrData = result.split(",");
if (arrData[0] == "OK") {
callback(null, arrData.slice(1));
} else {
callback(new Error(arrData.slice(1)));
}
}.bind(this));
serialPort.on('error', function (err) {
callback(new Error("Serial error: " + err.message));
}.bind(this));
//Data Send
//Wait 1700 milliseconds before sending and reading
setTimeout(function () {
for (var i = 0; i < mssg.length; i++) {
serialPort.write(Buffer.from(mssg[i], 'ascii'));
}
// Send terminate character
serialPort.write(Buffer.from('\n', 'ascii'));
//Read after 400 milliseconds
setTimeout(function () {
var arrData = receivedData.split('\n');
if (arrData[0].trim() == 'BOOT') {
if (arrData.length > 1) {
result = arrData.slice(1).join(",");
}
}
arrData = result.split(",");
if (arrData[0] == "OK") {
callback(null, arrData.slice(1));
} else {
callback(new Error(arrData.slice(1)));
}
receivedData = '';
}.bind(this), 400);
}.bind(this), 1700);
},
netSendCmd: function (message, callback) {
var timedOut = false;
var client = new Net.Socket();
client.setTimeout(3000);
//Socket events
client.on("data", function (data) {
var answer = data.toString();
var arrData = answer.split(",");
if (arrData[0] == "OK") {
callback(null, arrData.slice(1));
} else {
callback(new Error(arrData.slice(1)));
}
}.bind(this));
client.on("error", function (network_error) {
if (timedOut) {
timedOut = false;
} else {
callback(network_error);
}
}.bind(this));
client.on("close", function (had_error) {
//this.log("Network connection closed");
}.bind(this));
client.on("timeout", function () {
client.end();
if (timedOut) {
timedOut = false;
} else {
timedOut = true;
callback(new Error("Network Timeout error"));
}
}.bind(this));
//Connection
client.connect(80, this.hostname, function () {
client.write(message);
}.bind(this));
},
delayedSendCmd: function (message, callback) {
var currentDate = new Date();
if ((currentDate - this.lastCommandDate) > 3000) {
this.lastCommandDate = currentDate;
if (this.mode == "serial") {
//Send serial data
this.serialSendCmd(message, 3, 1000, function (serial_error, thData) {
if (serial_error) {
this.log("Serial command function failed:", serial_error.message);
callback(serial_error);
} else {
callback(null, thData);
}
}.bind(this));
} else if (this.mode == "network") {
//Send data through network
this.netSendCmd(message, function (network_error, thData) {
if (network_error) {
this.log("Network command function failed:", network_error.message);
callback(network_error);
} else {
callback(null, thData);
}
}.bind(this));
} else {
callback(new Error("Unknown mode on SendCmd"));
}
} else {
// Try again after 3000 milliseconds
setTimeout(function () {
this.delayedSendCmd(message, function (error, thData) {
if (error) {
callback(error);
} else {
callback(null, thData);
}
}.bind(this));
}.bind(this), 3000);
}
},
sendCmd: function (that, callback) {
var params = ["0", "22", "0", "0", "1"];
//Default temperature and States
var temperature = 22.0;
var futureState = Characteristic.CurrentHeaterCoolerState.IDLE;
var horizontalTiltAngle = 0;
if (that.heatingThresholdTemperature) {
temperature = that.heatingThresholdTemperature;
futureState = Characteristic.CurrentHeaterCoolerState.HEATING;
}
if (that.CoolingThresholdTemperature) {
temperature = that.CoolingThresholdTemperature;
futureState = Characteristic.CurrentHeaterCoolerState.COOLING;
}
//Mode: 0-auto, 1-hot, 2-cold, 3-dry, 4-fan
switch (that.TargetHeaterCoolerState) {
case Characteristic.TargetHeaterCoolerState.AUTO:
params[0] = "0";
break;
case Characteristic.TargetHeaterCoolerState.HEAT:
params[0] = "1";
temperature = that.HeatingThresholdTemperature;
futureState = Characteristic.CurrentHeaterCoolerState.HEATING;
break;
case Characteristic.TargetHeaterCoolerState.COOL:
params[0] = "2";
temperature = that.CoolingThresholdTemperature;
futureState = Characteristic.CurrentHeaterCoolerState.COOLING;
break;
default:
params[0] = "0";
break;
}
//ThresholdTemperature (celsius, int)
params[1] = Math.round(temperature).toString();
//Fan: 0-auto, 1,2,3,4,5-speed, 6-Silent)
//RotationSpeed: 0-Silent, 20,40,60,80,100
params[2] = "6";
if (that.RotationSpeed) {
params[2] = (parseInt(that.RotationSpeed) / 20).toString();
}
if (that.TargetFanState == Characteristic.TargetFanState.AUTO) {
params[2] = 0;
}
//Vane: 0-auto, 1,2,3,4,5-angle, 6-moving)
if (that.TargetHorizontalTiltAngle) {
horizontalTiltAngle = parseInt(that.TargetHorizontalTiltAngle);
}
if (that.SwingMode == Characteristic.SwingMode.SWING_ENABLED) {
horizontalTiltAngle = -54;
}
params[3] = (-horizontalTiltAngle / 9).toString();
//Status: 0-off, 1-on
params[4] = "1";
if (that.Active == Characteristic.Active.INACTIVE) {
params[4] = "0";
futureState = Characteristic.CurrentHeaterCoolerState.INACTIVE;
}
var msg = "S," + params.join(",");
this.delayedSendCmd(msg, function (error, thData) {
if (error) {
callback(error);
} else {
this.CurrentHeaterCoolerState = futureState;
callback(null);
}
}.bind(this));
},
//Start
identify: function (callback) {
this.log("Identify requested!");
callback(null);
},
// Required
getActive: function (callback) {
this.log("getActive :", this.Active);
callback(null, this.Active);
},
setActive: function (value, callback) {
this.log("setActive from/to:", this.Active, value);
var that = this;
that.Active = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.Active = value;
callback(null);
}
}.bind(this));
},
getCurrentHeaterCoolerState: function (callback) {
this.log("getCurrentHeaterCoolerState: ", this.CurrentHeaterCoolerState);
callback(null, this.CurrentHeaterCoolerState);
},
getTargetHeaterCoolerState: function (callback) {
this.log("getTargetHeaterCoolerState", this.TargetHeaterCoolerState);
callback(null, this.TargetHeaterCoolerState);
},
setTargetHeaterCoolerState: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setTargetHeaterCoolerState from/to:", this.TargetHeaterCoolerState, value);
var that = this;
that.TargetHeaterCoolerState = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.TargetHeaterCoolerState = value;
callback(null);
}
}.bind(this));
}
},
getCurrentTemperature: function (callback) {
this.log("getCurrentTemperature");
msg = "G";
this.delayedSendCmd(msg, function (error, thData) {
if (error) {
callback(error);
} else {
this.log("Sensor:", Math.round(thData[0].trim() * 100) / 100);
temperature = (thData[0] * this.slope) + this.intercept;
this.CurrentTemperature = Math.round(temperature * 100) / 100;
this.log("Temperature:", this.CurrentTemperature)
callback(null, this.CurrentTemperature);
}
}.bind(this));
},
// Optional
getSwingMode: function (callback) {
this.log("getSwingMode:", this.SwingMode);
callback(null, this.SwingMode);
},
setSwingMode: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setSwingMode from/to:", this.SwingMode, value);
var that = this;
that.SwingMode = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.SwingMode = value;
callback(null);
}
}.bind(this));
}
},
getCoolingThresholdTemperature: function (callback) {
this.log("getCoolingThresholdTemperature:", this.CoolingThresholdTemperature);
callback(null, this.CoolingThresholdTemperature);
},
setCoolingThresholdTemperature: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setCoolingThresholdTemperature from/to:", this.CoolingThresholdTemperature, value);
var that = this;
that.CoolingThresholdTemperature = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.CoolingThresholdTemperature = value;
callback(null);
}
}.bind(this));
}
},
getHeatingThresholdTemperature: function (callback) {
this.log("getHeatingThresholdTemperature:", this.HeatingThresholdTemperature);
callback(null, this.HeatingThresholdTemperature);
},
setHeatingThresholdTemperature: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setHeatingThresholdTemperature from/to:", this.HeatingThresholdTemperature, value);
var that = this;
that.HeatingThresholdTemperature = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.HeatingThresholdTemperature = value;
callback(null);
}
}.bind(this));
}
},
getTemperatureDisplayUnits: function (callback) {
this.log("getTemperatureDisplayUnits:", this.temperatureDisplayUnits);
callback(null, this.temperatureDisplayUnits);
},
setTemperatureDisplayUnits: function (value, callback) {
this.log("setTemperatureDisplayUnits from %s to %s", this.temperatureDisplayUnits, value);
this.temperatureDisplayUnits = value;
callback(null);
},
getRotationSpeed: function (callback) {
this.log("getRotationSpeed:", this.RotationSpeed);
callback(null, this.RotationSpeed);
},
setRotationSpeed: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setRotationSpeed from/to:", this.RotationSpeed, value);
var that = this;
that.RotationSpeed = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.RotationSpeed = value;
callback(null);
}
}.bind(this));
}
},
getTargetFanState: function (callback) {
this.log("getTargetFanState:", this.TargetFanState);
callback(null, this.TargetFanState);
},
setTargetFanState: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setTargetFanState from/to:", this.TargetFanState, value);
var that = this;
that.TargetFanState = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.TargetFanState = value;
callback(null);
}
}.bind(this));
}
},
getTargetHorizontalTiltAngle: function (callback) {
this.log("getTargetHorizontalTiltAngle:", this.TargetHorizontalTiltAngle);
callback(null, this.TargetHorizontalTiltAngle);
},
setTargetHorizontalTiltAngle: function (value, callback) {
if (value === undefined) {
callback(null);
} else {
this.log("setTargetHorizontalTiltAngle from/to:", this.TargetHorizontalTiltAngle, value);
var that = this;
that.TargetHorizontalTiltAngle = value;
this.sendCmd(that, function (error) {
if (error) {
callback(error);
} else {
this.TargetHorizontalTiltAngle = value;
this.CurrentHorizontalTiltAngle = value;
callback(null);
}
}.bind(this));
}
},
getCurrentHorizontalTiltAngle: function (callback) {
this.log("getTargetHorizontalTiltAngle:", this.CurrentHorizontalTiltAngle);
callback(null, this.CurrentHorizontalTiltAngle);
},
getCurrentRelativeHumidity: function (callback) {
this.log("getCurrentRelativeHumidity");
msg = "G";
this.delayedSendCmd(msg, function (error, thData) {
if (error) {
callback(error);
} else {
this.CurrentRelativeHumidity = Math.round(thData[1].trim() * 100) / 100;
this.log("Humidity:", this.CurrentRelativeHumidity)
callback(null, this.CurrentRelativeHumidity);
}
}.bind(this));
},
getName: function (callback) {
this.log("getName :", this.name);
callback(null, this.name);
},
getServices: function () {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.serialnumber);
// Required Characteristics
this.service
.getCharacteristic(Characteristic.Active)
.on('get', this.getActive.bind(this))
.on('set', this.setActive.bind(this));
this.service
.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
.on('get', this.getCurrentHeaterCoolerState.bind(this));
this.service
.getCharacteristic(Characteristic.TargetHeaterCoolerState)
.on('get', this.getTargetHeaterCoolerState.bind(this))
.on('set', this.setTargetHeaterCoolerState.bind(this));
this.service
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getCurrentTemperature.bind(this));
// Optional Characteristics
this.service
.getCharacteristic(Characteristic.SwingMode)
.on('get', this.getSwingMode.bind(this))
.on('set', this.setSwingMode.bind(this));
this.service
.getCharacteristic(Characteristic.CoolingThresholdTemperature)
.on('get', this.getCoolingThresholdTemperature.bind(this))
.on('set', this.setCoolingThresholdTemperature.bind(this));
this.service
.getCharacteristic(Characteristic.HeatingThresholdTemperature)
.on('get', this.getHeatingThresholdTemperature.bind(this))
.on('set', this.setHeatingThresholdTemperature.bind(this));
this.service
.getCharacteristic(Characteristic.TemperatureDisplayUnits)
.on('get', this.getTemperatureDisplayUnits.bind(this))
.on('set', this.setTemperatureDisplayUnits.bind(this));
this.service
.getCharacteristic(Characteristic.RotationSpeed)
.on('get', this.getRotationSpeed.bind(this))
.on('set', this.setRotationSpeed.bind(this));
this.service
.getCharacteristic(Characteristic.Name)
.on('get', this.getName.bind(this));
//this.service.getCharacteristic(Characteristic.CurrentTemperature)
// .setProps({
// minValue: 0,
// maxValue: 45,
// minStep: 1
// });
this.service.getCharacteristic(Characteristic.CoolingThresholdTemperature)
.setProps({
minValue: 10,
maxValue: 35,
minStep: 1
});
this.service.getCharacteristic(Characteristic.HeatingThresholdTemperature)
.setProps({
minValue: 0,
maxValue: 25,
minStep: 1
});
this.service.getCharacteristic(Characteristic.RotationSpeed)
.setProps({
minValue: 0,
maxValue: 100,
minStep: 20
});
//Extra Characteristics
this.service.addOptionalCharacteristic(Characteristic.TargetFanState);
this.service
.getCharacteristic(Characteristic.TargetFanState)
.on('get', this.getTargetFanState.bind(this))
.on('set', this.setTargetFanState.bind(this));
this.service.addOptionalCharacteristic(Characteristic.SlatType);
this.service.setCharacteristic(Characteristic.SlatType, Characteristic.SlatType.HORIZONTAL);
this.service.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle);
this.service
.getCharacteristic(Characteristic.TargetHorizontalTiltAngle)
.on('get', this.getTargetHorizontalTiltAngle.bind(this))
.on('set', this.setTargetHorizontalTiltAngle.bind(this));
this.service.getCharacteristic(Characteristic.TargetHorizontalTiltAngle)
.setProps({
maxValue: 0,
minValue: -45,
minStep: 9
});
this.service.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle);
this.service
.getCharacteristic(Characteristic.CurrentHorizontalTiltAngle)
.on('get', this.getCurrentHorizontalTiltAngle.bind(this));
if (this.humidity) {
this.service.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity);
this.service
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getCurrentRelativeHumidity.bind(this));
}
return [informationService, this.service];
}
};