homebridge-tuya icon indicating copy to clipboard operation
homebridge-tuya copied to clipboard

SimpleHeater: the set temperature is doubled

Open frankheit opened this issue 4 years ago • 16 comments

First of all I would like to thank you for your work here - great plugin!

I use a smart thermostat (BHT-002-GBLW) als a simpleHeater. In the config I use "temperatureDivisor": 2 to become the right temperatur from my thermostat This works fine so far. If I now set the temperature manually in the Apple Home app (e.g. to 20 degrees) the set tempetature will be doubled and springs to 40 degrees.

Without use "temperatureDivisor" all ist fine (but I habe the wrong room temperature).

Environment (please complete the following information):*

  • OS: macOS Catalina
  • iOS version: 13.7 & 14 beta
  • Homehubs: Hoods 3.2.6
  • Node Version: 12.13.1
  • Plugin Version: 1.0.5
  • Accessory Type simpleHeater

Additional context

{ "platform": "TuyaLan", "devices": [ { "name": "thermostat", "type": "SimpleHeater", "manufacturer": "Branded", "model": "Wifi Thermostat", "id": "xxxx", "key": "xxxx", "temperatureDivisor": 2 } ] }

frankheit avatar Aug 31 '20 05:08 frankheit

I have the same problems. Presse bug fixing.

thank you verry much

crun11 avatar Sep 08 '20 08:09 crun11

@iRayanKhan du you have any tips?

frankheit avatar Sep 17 '20 13:09 frankheit

Hi there, I'm not sure why your thermostat would do that. I could try to make a custom accessory (no guarantees) to just divide the temperature display in half, and retain the normal temp. I'm backed up so this may take awhile before I can get to it.

iRayanKhan avatar Oct 09 '20 02:10 iRayanKhan

I'm also facing exactly the same issue. Would be great, if you could add the custom accessory! Thank you!

toniweser avatar Oct 10 '20 17:10 toniweser

Having the same issue for quite some time. Everything is great, "temperatureDivisor" is set to 2 and everything shows up correctly except when i change the set temperature, its not divided when homekit updates the accessory status after sending the command. If i set 25 degrees, my thermostat changes to 25, but homekit jumps to 50 immediately, after some time it shows up correctly again. video

fupzlito avatar Oct 19 '20 10:10 fupzlito

I don't have a device to test this, or a test harness, but this is the only reason I could see for incorrect reporting. Would someone care to add some extra logging and test?

This block of code here sets the target temperature, taking the divisor into account before sending the updated value to the device.

    setTargetThresholdTemperature(value, callback) {
        this.setState(this.dpDesiredTemperature, value * this.temperatureDivisor, err => {
            if (err) return callback(err);

            if (this.characteristicHeatingThresholdTemperature) {
                this.characteristicHeatingThresholdTemperature.updateValue(value);
            }

            callback();
        });
    }

So for your target value of 25, setState gets given 50 with your divisor of 2.

Just before the call to this line, can someone log what value actually is? Or even better a log line before setState too

                this.characteristicHeatingThresholdTemperature.updateValue(value);

dhutchison avatar Oct 21 '20 23:10 dhutchison

@dhutchison how can i do that?

fupzlito avatar Oct 28 '20 10:10 fupzlito

Hello, I can't see from the text exactly what you need to be able to help. It may be that my english is so bad. Should the log be sent that is written to the system.log when the thermostat is switched on? I don't see any changes in the code that I could test with customizations. I had already tried to adapt code in certain places. Unfortunately unsuccessful. Good night

crun11 avatar Oct 28 '20 21:10 crun11

My guess is that when accessory changes (either through HomeKit or via just changing the temp on the thermostat), the plugin doesn't use the temperatureDivisor. So its gotta be somewhere here

if (changes.hasOwnProperty(this.dpDesiredTemperature)) {
                if (characteristicHeatingThresholdTemperature.value !== changes[this.dpDesiredTemperature])
                    characteristicHeatingThresholdTemperature.updateValue(changes[this.dpDesiredTemperature]);
            }

            if (changes.hasOwnProperty(this.dpCurrentTemperature) && characteristicCurrentTemperature.value !== changes[this.dpCurrentTemperature]) characteristicCurrentTemperature.updateValue(changes[this.dpCurrentTemperature]);

            console.log('[TuyaAccessory] SimpleHeater changed: ' + JSON.stringify(state));
        });
    }

I know nothing about coding, so its up to you to fix it.

fupzlito avatar Oct 29 '20 08:10 fupzlito

Okay, I somehow fixed it, I just put the temperatureDivisor everywhere I could, but it works just fine now

            if (changes.hasOwnProperty(this.dpDesiredTemperature)) {
                if (characteristicHeatingThresholdTemperature.value !== changes[this.dpDesiredTemperature * this.temperatureDivisor])
                    characteristicHeatingThresholdTemperature.updateValue(changes[this.dpDesiredTemperature * this.temperatureDivisor]);
            }

            if (changes.hasOwnProperty(this.dpCurrentTemperature) && characteristicCurrentTemperature.value !== changes[this.dpCurrentTemperature]) characteristicCurrentTemperature.updateValue(changes[this.dpCurrentTemperature * this.temperatureDivisor]);

            console.log('[TuyaAccessory] SimpleHeater changed: ' + JSON.stringify(state));
        });
    }

fupzlito avatar Oct 29 '20 08:10 fupzlito

it works perfect. great

crun11 avatar Nov 01 '20 10:11 crun11

Sorry for the stupid question, but how can I implement the changes of fupzlito? I have a Rasperry Pi 4 running HOOBS. When I log into HOOBS via hoobs.local, I can only change the config file.

Thank you very much!

marvelousmo avatar Nov 07 '20 13:11 marvelousmo

@marvelousmo the fix should be in the next release of the plugin. If you dont want to wait, you could replace the file homebridge-tuya/lib/SimpleHeaterAccessory.js with the fixed one from github You’ll have to look up where HOOBS keeps its plugins to do that

fupzlito avatar Nov 07 '20 16:11 fupzlito

@fupzlito Thank you very much! So I'll wait for the next update :-)

marvelousmo avatar Nov 07 '20 20:11 marvelousmo

So I made it to change the code in SimpleHeaterAccessory.js as you proposed in

if (changes.hasOwnProperty(this.dpDesiredTemperature)) { if (characteristicHeatingThresholdTemperature.value !== changes[this.dpDesiredTemperature]) characteristicHeatingThresholdTemperature.updateValue(changes[this.dpDesiredTemperature]); }

        if (changes.hasOwnProperty(this.dpCurrentTemperature) && characteristicCurrentTemperature.value !== changes[this.dpCurrentTemperature]) characteristicCurrentTemperature.updateValue(changes[this.dpCurrentTemperature]);

        console.log('[TuyaAccessory] SimpleHeater changed: ' + JSON.stringify(state));
    });
}

... but unfortunately it didn't work for me (restartet homebridge server and even the whole rasperry pi). have to wait for an updated plugin version I guess... Thanks anyway!

marvelousmo avatar Nov 07 '20 22:11 marvelousmo

Hey, i got the same Problem at "Heat Convector" Can you add the "temperatureDivisor" also for that device type?

best, chris

edit: i done it by myself.

ConvectorAccessory.js

const BaseAccessory = require('./BaseAccessory');

class ConvectorAccessory extends BaseAccessory { static getCategory(Categories) { return Categories.AIR_HEATER; }

constructor(...props) {
    super(...props);
}

_registerPlatformAccessory() {
    const {Service} = this.hap;

    this.accessory.addService(Service.HeaterCooler, this.device.context.name);

    super._registerPlatformAccessory();
}

_registerCharacteristics(dps) {
    const {Service, Characteristic} = this.hap;
    const service = this.accessory.getService(Service.HeaterCooler);
    this._checkServiceName(service, this.device.context.name);

    this.dpActive = this._getCustomDP(this.device.context.dpActive) || '7';
    this.dpDesiredTemperature = this._getCustomDP(this.device.context.dpDesiredTemperature) || '2';
    this.dpCurrentTemperature = this._getCustomDP(this.device.context.dpCurrentTemperature) || '3';
    this.dpRotationSpeed = this._getCustomDP(this.device.context.dpRotationSpeed) || '4';
    this.dpChildLock = this._getCustomDP(this.device.context.dpChildLock) || '6';
    this.dpTemperatureDisplayUnits = this._getCustomDP(this.device.context.dpTemperatureDisplayUnits) || '19';
this.temperatureDivisor = parseInt(this.device.context.temperatureDivisor) || 1;


    this.cmdLow = 'LOW';
    if (this.device.context.cmdLow) {
        if (/^l[a-z]+$/i.test(this.device.context.cmdLow)) this.cmdLow = ('' + this.device.context.cmdLow).trim();
        else throw new Error('The cmdLow doesn\'t appear to be valid: ' + this.device.context.cmdLow);
    }

    this.cmdHigh = 'HIGH';
    if (this.device.context.cmdHigh) {
        if (/^h[a-z]+$/i.test(this.device.context.cmdHigh)) this.cmdHigh = ('' + this.device.context.cmdHigh).trim();
        else throw new Error('The cmdHigh doesn\'t appear to be valid: ' + this.device.context.cmdHigh);
    }

    this.enableFlipSpeedSlider = !!this.device.context.enableFlipSpeedSlider;

    const characteristicActive = service.getCharacteristic(Characteristic.Active)
        .updateValue(this._getActive(dps[this.dpActive]))
        .on('get', this.getActive.bind(this))
        .on('set', this.setActive.bind(this));

    service.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
        .updateValue(this._getCurrentHeaterCoolerState(dps))
        .on('get', this.getCurrentHeaterCoolerState.bind(this));

    service.getCharacteristic(Characteristic.TargetHeaterCoolerState)
        .setProps({
            minValue: 1,
            maxValue: 1,
            validValues: [Characteristic.TargetHeaterCoolerState.HEAT]
        })
        .updateValue(this._getTargetHeaterCoolerState())
        .on('get', this.getTargetHeaterCoolerState.bind(this))
        .on('set', this.setTargetHeaterCoolerState.bind(this));

    const characteristicCurrentTemperature = service.getCharacteristic(Characteristic.CurrentTemperature)
        .updateValue(this._getDividedState(dps[this.dpCurrentTemperature], this.temperatureDivisor))
        .on('get', this.getDividedState.bind(this, this.dpCurrentTemperature, this.temperatureDivisor));


    const characteristicHeatingThresholdTemperature = service.getCharacteristic(Characteristic.HeatingThresholdTemperature)
        .setProps({
            minValue: this.device.context.minTemperature || 15,
            maxValue: this.device.context.maxTemperature || 35,
            minStep: this.device.context.minTemperatureSteps || 1
        })
         .updateValue(this._getDividedState(dps[this.dpDesiredTemperature], this.temperatureDivisor))
        .on('get', this.getDividedState.bind(this, this.dpDesiredTemperature, this.temperatureDivisor))
        .on('set', this.setTargetThresholdTemperature.bind(this));


    let characteristicTemperatureDisplayUnits;
    if (!this.device.context.noTemperatureUnit) {
        characteristicTemperatureDisplayUnits = service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .updateValue(this._getTemperatureDisplayUnits(dps[this.dpTemperatureDisplayUnits * this.temperatureDivisor]))
            .on('get', this.getTemperatureDisplayUnits.bind(this))
            .on('set', this.setTemperatureDisplayUnits.bind(this));
    } else this._removeCharacteristic(service, Characteristic.TemperatureDisplayUnits);

    let characteristicLockPhysicalControls;
    if (!this.device.context.noChildLock) {
        characteristicLockPhysicalControls = service.getCharacteristic(Characteristic.LockPhysicalControls)
            .updateValue(this._getLockPhysicalControls(dps[this.dpChildLock]))
            .on('get', this.getLockPhysicalControls.bind(this))
            .on('set', this.setLockPhysicalControls.bind(this));
    } else this._removeCharacteristic(service, Characteristic.LockPhysicalControls);

    const characteristicRotationSpeed = service.getCharacteristic(Characteristic.RotationSpeed)
        .updateValue(this._getRotationSpeed(dps))
        .on('get', this.getRotationSpeed.bind(this))
        .on('set', this.setRotationSpeed.bind(this));

    this.characteristicActive = characteristicActive;
    this.characteristicHeatingThresholdTemperature = characteristicHeatingThresholdTemperature;
    this.characteristicRotationSpeed = characteristicRotationSpeed;

    this.device.on('change', (changes, state) => {
        if (changes.hasOwnProperty(this.dpActive)) {
            const newActive = this._getActive(changes[this.dpActive]);
            if (characteristicActive.value !== newActive) {
                characteristicActive.updateValue(newActive);

                if (!changes.hasOwnProperty(this.dpRotationSpeed)) {
                    characteristicRotationSpeed.updateValue(this._getRotationSpeed(state));
                }
            }
        }

        if (characteristicLockPhysicalControls && changes.hasOwnProperty(this.dpChildLock)) {
            const newLockPhysicalControls = this._getLockPhysicalControls(changes[this.dpChildLock]);
            if (characteristicLockPhysicalControls.value !== newLockPhysicalControls) {
                characteristicLockPhysicalControls.updateValue(newLockPhysicalControls);
            }
        }

        if (changes.hasOwnProperty(this.dpDesiredTemperature)) {
            if (characteristicHeatingThresholdTemperature.value !== changes[this.dpDesiredTemperature])
                characteristicHeatingThresholdTemperature.updateValue(changes[this.dpDesiredTemperature * this.temperatureDivisor]);
        }

        if (changes.hasOwnProperty(this.dpCurrentTemperature) && characteristicCurrentTemperature.value !== changes[this.dpCurrentTemperature]) 
	characteristicCurrentTemperature.updateValue(changes[this.dpCurrentTemperature * this.temperatureDivisor]);

        if (characteristicTemperatureDisplayUnits && changes.hasOwnProperty(this.dpTemperatureDisplayUnits)) {
            const newTemperatureDisplayUnits = this._getTemperatureDisplayUnits(changes[this.dpTemperatureDisplayUnits * this.temperatureDivisor]);
            if (characteristicTemperatureDisplayUnits.value !== newTemperatureDisplayUnits) characteristicTemperatureDisplayUnits.updateValue(newTemperatureDisplayUnits);
        }

        if (changes.hasOwnProperty(this.dpRotationSpeed)) {
            const newRotationSpeed = this._getRotationSpeed(state);
            if (characteristicRotationSpeed.value !== newRotationSpeed) characteristicRotationSpeed.updateValue(newRotationSpeed);
        }
    });
}

getActive(callback) {
    this.getState(this.dpActive, (err, dp) => {
        if (err) return callback(err);

        callback(null, this._getActive(dp));
    });
}

_getActive(dp) {
    const {Characteristic} = this.hap;

    return dp ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
}

setActive(value, callback) {
    const {Characteristic} = this.hap;

    switch (value) {
        case Characteristic.Active.ACTIVE:
            return this.setState(this.dpActive, true, callback);

        case Characteristic.Active.INACTIVE:
            return this.setState(this.dpActive, false, callback);
    }

    callback();
}

getLockPhysicalControls(callback) {
    this.getState(this.dpChildLock, (err, dp) => {
        if (err) return callback(err);

        callback(null, this._getLockPhysicalControls(dp));
    });
}

_getLockPhysicalControls(dp) {
    const {Characteristic} = this.hap;

    return dp ? Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED : Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
}

setLockPhysicalControls(value, callback) {
    const {Characteristic} = this.hap;

    switch (value) {
        case Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED:
            return this.setState(this.dpChildLock, true, callback);

        case Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED:
            return this.setState(this.dpChildLock, false, callback);
    }

    callback();
}

getCurrentHeaterCoolerState(callback) {
    this.getState([this.dpActive], (err, dps) => {
        if (err) return callback(err);

        callback(null, this._getCurrentHeaterCoolerState(dps));
    });
}

_getCurrentHeaterCoolerState(dps) {
    const {Characteristic} = this.hap;
    return dps[this.dpActive] ? Characteristic.CurrentHeaterCoolerState.HEATING : Characteristic.CurrentHeaterCoolerState.INACTIVE;
}

getTargetHeaterCoolerState(callback) {
    callback(null, this._getTargetHeaterCoolerState());
}

_getTargetHeaterCoolerState() {
    const {Characteristic} = this.hap;
    return Characteristic.TargetHeaterCoolerState.HEAT;
}

setTargetHeaterCoolerState(value, callback) {
    this.setState(this.dpActive, true, callback);
}

setTargetThresholdTemperature(value, callback) {
    this.setState(this.dpDesiredTemperature, value * this.temperatureDivisor, err => {
        if (err) return callback(err);

        if (this.characteristicHeatingThresholdTemperature) {
            this.characteristicHeatingThresholdTemperature.updateValue(value);
        }

        callback();
    });
}

getTemperatureDisplayUnits(callback) {
    this.getState(this.dpTemperatureDisplayUnits, (err, dp) => {
        if (err) return callback(err);

        callback(null, this._getTemperatureDisplayUnits(dp));
    });
}

_getTemperatureDisplayUnits(dp) {
    const {Characteristic} = this.hap;

    return dp === 'F' ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;
}

setTemperatureDisplayUnits(value, callback) {
    const {Characteristic} = this.hap;

    this.setState(this.dpTemperatureDisplayUnits, value === Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? 'F' : 'C', callback);
}

getRotationSpeed(callback) {
    this.getState([this.dpActive, this.dpRotationSpeed], (err, dps) => {
        if (err) return callback(err);

        callback(null, this._getRotationSpeed(dps));
    });
}

_getRotationSpeed(dps) {
    if (!dps[this.dpActive]) return 0;

    if (this._hkRotationSpeed) {
        const currntRotationSpeed = this.convertRotationSpeedFromHomeKitToTuya(this._hkRotationSpeed);

        return currntRotationSpeed === dps[this.dpRotationSpeed] ? this._hkRotationSpeed : this.convertRotationSpeedFromTuyaToHomeKit(dps[this.dpRotationSpeed]);
    }

    return this._hkRotationSpeed = this.convertRotationSpeedFromTuyaToHomeKit(dps[this.dpRotationSpeed]);
}

setRotationSpeed(value, callback) {
    const {Characteristic} = this.hap;

    if (value === 0) {
        this.setActive(Characteristic.Active.INACTIVE, callback);
    } else {
        this._hkRotationSpeed = value;

        const newSpeed = this.convertRotationSpeedFromHomeKitToTuya(value);
        const currentSpeed = this.convertRotationSpeedFromHomeKitToTuya(this.characteristicRotationSpeed.value);

        if (this.enableFlipSpeedSlider) this._hkRotationSpeed = this.convertRotationSpeedFromTuyaToHomeKit(newSpeed);

        if (newSpeed !== currentSpeed) {
            this.characteristicRotationSpeed.updateValue(this._hkRotationSpeed);
            this.setMultiState({[this.dpActive]: true, [this.dpRotationSpeed]: newSpeed}, callback);
        } else {
            callback();
            if (this.enableFlipSpeedSlider)
                process.nextTick(() => {
                    this.characteristicRotationSpeed.updateValue(this._hkRotationSpeed);
                });
        }
    }
}

convertRotationSpeedFromTuyaToHomeKit(value) {
    return {[this.cmdLow]: 1, [this.cmdHigh]: 100}[value];
}

convertRotationSpeedFromHomeKitToTuya(value) {
    return value < 50 ? this.cmdLow : this.cmdHigh;
}

}

module.exports = ConvectorAccessory;

thechris1992 avatar Feb 06 '21 22:02 thechris1992