homebridge-tasmota-sonoff-thermostat
Version:
Homebridge plugin to make a tasmota sonoff work as a termostat
279 lines (246 loc) • 8.32 kB
JavaScript
var mqtt = require('mqtt');
let Service, Characteristic;
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory(
"homebridge-tasmota-thermostat",
"tasmota-thermostat",
TasmotaSonoffThermostat
);
};
class TasmotaSonoffThermostat {
constructor(log, config) {
this.log = log;
this.name = config.name;
this.target_heating_state = Characteristic.TargetHeatingCoolingState.OFF;
this.current_heating_state = Characteristic.CurrentHeatingCoolingState.OFF;
this.target_temp = config.start_temperature || 10;
this.current_temp = this.target_temp;
if (config.temperature_unit == "F") {
this.temperature_unit = Characteristic.TemperatureDisplayUnits.FAHRENHEIT;
} else {
this.temperature_unit = Characteristic.TemperatureDisplayUnits.CELSIUS;
}
this.mqtt_url = config.mqtt.url || '';
this.mqtt_clientid = config.mqtt.clientid || this.createClientId();
this.mqtt_username = config.mqtt.username || 'tasmota_sonoff_termostat';
this.mqtt_password = config.mqtt.password || 'password';
this.mqtt_sensor_name = config.mqtt.sensor_name || 'AM2301';
this.mqtt_sensor_topic = config.mqtt.sensor_topic || 'tasmota/sensor';
this.mqtt_output_topic = config.mqtt.output_topic || 'tasmota/output';
this.mqtt_status_topic = config.mqtt.status_topic || 'tasmota/status';
this.mqtt_will_topic = config.mqtt.will_topic || 'tasmota/will';
this.mqtt_options = {
keepalive: 10,
clientId: this.mqtt_clientId,
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30000,
will: {
topic: this.mqtt_will_topic,
payload: this.mqtt_clientid + " - Disconnected",
qos: 0,
retain: false
},
username: this.mqtt_username,
password: this.mqtt_password,
rejectUnauthorized: false
};
this.setupMQTT();
this.setupHapServices();
}
setupHapServices() {
this.informationService = new Service.AccessoryInformation();
this.thermostatService = new Service.Thermostat(this.name);
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Tom")
.setCharacteristic(Characteristic.Model, "Tasmota-Sonoff-Thermostat")
.setCharacteristic(Characteristic.SerialNumber, "TST-" + this.name)
.setCharacteristic(
Characteristic.FirmwareRevision,
require('./package.json').version
);
this.thermostatService
.getCharacteristic(Characteristic.Name)
.on('get', this.getName.bind(this));
this.thermostatService
.getCharacteristic(Characteristic.TemperatureDisplayUnits)
.on('get', this.getTemperatureDisplayUnits.bind(this))
.on('set', this.setTemperatureDisplayUnits.bind(this));
this.thermostatService
.getCharacteristic(Characteristic.TargetTemperature)
.on('get', this.getTargetTemperature.bind(this))
.on('set', this.setTargetTemperature.bind(this));
this.thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getCurrentTemperature.bind(this));
this.thermostatService
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
.on('get', this.getTargetHeatingCoolingState.bind(this))
.on('set', this.setTargetHeatingCoolingState.bind(this))
.setProps({
validValues: [0,1,3]
});
this.thermostatService
.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
.on('get', this.getCurrentHeatingCoolingState.bind(this))
.setProps({
validValues: [0,1]
});
this.thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.setProps({
minValue: 0,
maxValue: 40,
minStep: 0.1
});
this.thermostatService
.getCharacteristic(Characteristic.TargetTemperature)
.setProps({
minValue: 0,
maxValue: 40,
minStep: 0.1
});
}
createClientId() {
return 'tasmota_sonoff_termostat_' +
this.name.replace(/[^\x20-\x7F]/g, "") + '_' +
Math.random().toString(16).substr(2, 8)
}
setupMQTT() {
this.mqtt_client = mqtt.connect(this.mqtt_url, this.mqtt_options);
this.mqtt_client.on('error', function (err) {
this.log('MQTT Error: ' + err);
}.bind(this));
this.mqtt_client.on('message', this.handleMqttMessage.bind(this));
this.mqtt_client.on('connect', this.handleMqttConnected.bind(this));
}
handleMqttConnected() {
this.log('MQTT Connected');
this.mqtt_client.subscribe(this.mqtt_sensor_topic, function (err) {
if (err) {
this.log('MQTT Sensor Subscription error:' + err);
} else {
this.log('MQTT Sensor Subscribed');
}
}.bind(this));
this.mqtt_client.subscribe(this.mqtt_status_topic, function (err) {
if (err) {
this.log('MQTT Status Subscription error:' + err);
} else {
this.log('MQTT Status Subscribed');
}
}.bind(this));
}
handleMqttMessage(topic, message) {
this.log("MQTT receieved, Topic: " + topic + " message: " + message);
if (topic == this.mqtt_sensor_topic) {
var payload = JSON.parse(message);
if ((this.mqtt_sensor_name in payload) && ("Temperature" in payload[this.mqtt_sensor_name])) {
if (!isNaN(payload[this.mqtt_sensor_name]["Temperature"])) {
this.current_temp = parseFloat(payload[this.mqtt_sensor_name]["Temperature"]);
this.thermostatService
.setCharacteristic(
Characteristic.CurrentTemperature,
this.current_temp
);
this.updateStatuses();
} else {
this.log('MQTT Recieved NaN message: ' + message);
this.setHeating(false);
}
} else {
this.log('MQTT no thermostat pesent in message: ' + message);
this.setHeating(false);
}
} else if (topic == this.mqtt_status_topic) {
this.heaterStatusUpdate(message);
} else {
this.log('MQTT Message error topic: ' + topic + ' message: ' + message);
}
}
getName(callback) {
this.log("HAP get name");
return callback(null, this.name);
}
getTemperatureDisplayUnits(callback) {
this.log("HAP get Display Unit");
callback(null, this.temperatureDisplayUnits);
}
setTemperatureDisplayUnits(value, callback) {
this.log("HAP set Display Unit");
this.temperatureDisplayUnits = value;
callback(null);
}
getTargetTemperature(callback) {
this.log("HAP get Target Temperature");
return callback(null, this.target_temp);
}
setTargetTemperature(value, callback) {
this.log("HAP set Target Temperature:" + value);
this.target_temp = parseFloat(value);
this.updateStatuses();
return callback(null);
}
getCurrentTemperature(callback) {
this.log("HAP get Current Temperature:");
return callback(null, this.current_temp);
}
getTargetHeatingCoolingState(callback) {
this.log("HAP get Target Heater State");
return callback(null, this.target_heating_state);
}
setTargetHeatingCoolingState(value, callback) {
this.log("HAP set Current Target State");
this.target_heating_state = value;
this.updateStatuses()
return callback(null);
}
getCurrentHeatingCoolingState(callback) {
this.log("HAP get Current Heater state");
return callback(null, this.current_heating_state);
}
updateStatuses() {
if (this.target_heating_state == Characteristic.TargetHeatingCoolingState.OFF) {
this.setHeating(false);
} else if (this.target_heating_state == Characteristic.TargetHeatingCoolingState.AUTO) {
if (this.current_temp < this.target_temp) {
this.setHeating(true);
} else {
this.setHeating(false);
}
} else if (this.target_heating_state == Characteristic.TargetHeatingCoolingState.HEAT) {
this.setHeating(true);
} else {
this.target_heating_state = Characteristic.TargetHeatingCoolingState.OFF;
this.setHeating(false);
}
}
setHeating(isOn) {
if (isOn) {
this.log("Turing On Heater");
this.mqtt_client.publish(this.mqtt_output_topic, "ON");
} else {
this.log("Turing Off Heater");
this.mqtt_client.publish(this.mqtt_output_topic, "OFF");
}
}
heaterStatusUpdate(isOn) {
if (isOn == "ON") {
this.current_heating_state = Characteristic.CurrentHeatingCoolingState.HEAT;
} else {
this.current_heating_state = Characteristic.CurrentHeatingCoolingState.OFF;
}
this.thermostatService
.setCharacteristic(
Characteristic.CurrentHeatingCoolingState,
this.current_heating_state
);
}
getServices() {
return [this.informationService, this.thermostatService];
}
}