UNPKG

homebridge-tasmota-sonoff-thermostat

Version:
279 lines (246 loc) 8.32 kB
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]; } }