Skip to content

Commit

Permalink
Fix Fahrenheit mode issue (some temperature degrees can't be set)
Browse files Browse the repository at this point in the history
Enable using half a degree in Celsius mode (e.g. 21.5 °C)
  • Loading branch information
eibenp committed Sep 5, 2023
1 parent 1a7a212 commit db9c511
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 15 deletions.
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It is recommended to add all devices to the Homebridge configuration, so that yo

xFan function is also supported, but it works automatically if enabled in Homebridge configuration. If xFan is enabled for the device, it is automatically turned on when you select a supported operating mode in Home App. If xFan is disabled, the Home App will not modify its actual setting in any case.

Temperature display units in the Home App are in sync with the AC unit's display. (Configuration settings are required to be specified always in Degrees Celsius, independently from the display units.)
Temperature display units of the physical device can be controlled using the Home App. (Configuration settings are required to be specified always in Degrees Celsius, independently from the display units.)

Vertical swing mode can be turned on/off, but special swing settings can't be controlled using the Home App.

Expand All @@ -39,6 +39,9 @@ This plugin was designed to support the Home App's Heater Cooler functionality u
* Lights of the AC unit can't be controlled.
* Additional device functions (e.g. health mode, sleep, SE) are not supported.
* Horizontal swing control is not supported, it remains the same as set directly on the device.
* GREE AC units do not support temperature ranges in auto mode, so temperature ranges have zero length in Home App.
* GREE AC units are not able to display decimals of temperature values (if set to half a degree, e.g. 21.5 °C, then unit display may not be in sync with temperature set in Home App).
* There is no way to get current heating-cooling state from the AC unit in auto mode, so displayed state in the Home App is based on temperature measurement, but internal sensor is not precise enough to always display the correct state.

## Installation instructions

Expand All @@ -50,6 +53,10 @@ npm install @eibenp/homebridge-gree-airconditioner -g
```
If successfully installed and configured, your devices will appear on the Homebridge GUI Accessories page and also in Home App (if Homebridge is already connected to the Home App). (If the additional temperature sensor is enabled, then 2 items will be displayed for supported devices (Heater Cooler and Temperature Sensor).)

## Upgrade

When upgrading from release v1.0.1 or earlier it is neccessary to disable and re-enable the devices to force Home App to reinitialize settings.

## Example configuration
_Only the relevant part of the configuration file is displayed:_
```
Expand Down Expand Up @@ -92,8 +99,8 @@ _Only the relevant part of the configuration file is displayed:_
* speedSteps - fan speed steps of the unit (valid values are: 3 and 5)
* statusUpdateInterval - device status will be refreshed based on this interval (in seconds)
* sensorOffset - device temperature sensor offset value for current temperature calibration (default is 40 °C, must be specified in Degrees Celsius)
* minimumTargetTemperature - minimum target temperature accepted by the device (default is 16 °C, must be specified in Degrees Celsius)
* maximumTargetTemperature - maximum target temperature accepted by the device (default is 30 °C, must be specified in Degrees Celsius)
* minimumTargetTemperature - minimum target temperature accepted by the device (default is 16 °C, must be specified in Degrees Celsius, valid values: 16-30)
* maximumTargetTemperature - maximum target temperature accepted by the device (default is 30 °C, must be specified in Degrees Celsius, valid values: 16-30)
* xFanEnabled - automatically turn on xFan functionality in supported device modes (xFan actual setting is not modified by the Home App if disabled)
* temperatureSensor - control additional temperature sensor accessory in Home App (disabled = do not add to Home App / child = add as a child accessory / separate = add as a separate (independent) accessory)
* disabled - set to true if you do not want to control this device in the Home App (old devices can be removed using this parameter)
Expand Down Expand Up @@ -126,6 +133,25 @@ ipconfig
# Subnet Mask . . . . . . . . . . . : 255.255.255.0
# Default Gateway . . . . . . . . . : 192.168.1.254
```

### Device settings

Some settings are initialized by Home App only once (when enabling the device). They can only be changed by disabling and re-enabling the device. The following settings are affected:

* name
* model
* speedSteps
* minimumTargetTemperature
* maximumTargetTemperature

### Temperature display units

Home App allows to set the device temperature display units but is independent from the temperature units shown in Home App. Home App always displays temperature values as specified by iOS/MacOS (can be changed in Preferences / Regional settings section). Display unit conversion is made by the Home App device.

### Temperature measurement

Temperature measurement is not perfect if using the built-in sensor. It is highly affected by current operation and can differ from actual temperature of other places in the room. It is recommended to use a sperarate temperature sensor and place it not too close to the AC unit.

## Refs & Credits

Special thanks to [tomikaa87](https://github.com/tomikaa87) and [kongkx](https://github.com/kongkx) for GREE network protocol information and code samples.
Expand Down
2 changes: 2 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@
"minimumTargetTemperature": {
"type": "integer",
"required": false,
"minimum": 16,
"default": 16
},
"maximumTargetTemperature": {
"type": "integer",
"required": false,
"maximum": 30,
"default": 30
},
"xFanEnabled": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": false,
"displayName": "GREE Air Conditioner",
"name": "@eibenp/homebridge-gree-airconditioner",
"version": "1.0.1",
"version": "1.0.2",
"description": "Control GREE Air Conditioners from Homebridge",
"license": "Apache-2.0",
"repository": {
Expand Down
6 changes: 6 additions & 0 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export class GreeACPlatform implements DynamicPlatformPlugin {
{speedSteps: 5} : {}),
...((devcfg.temperatureSensor && ['disabled', 'child', 'separate'].includes((devcfg.temperatureSensor as string).toLowerCase())) ?
{temperatureSensor: (devcfg.temperatureSensor as string).toLowerCase()} : {temperatureSensor: 'disabled'}),
...(devcfg.minimumTargetTemperature && (devcfg.minimumTargetTemperature < DEFAULT_DEVICE_CONFIG.minimumTargetTemperature ||
devcfg.minimumTargetTemperature > DEFAULT_DEVICE_CONFIG.maximumTargetTemperature) ?
{ minimumTargetTemperature: DEFAULT_DEVICE_CONFIG.minimumTargetTemperature } : {}),
...(devcfg.maximumTargetTemperature && (devcfg.maximumTargetTemperature < DEFAULT_DEVICE_CONFIG.minimumTargetTemperature ||
devcfg.maximumTargetTemperature > DEFAULT_DEVICE_CONFIG.maximumTargetTemperature) ?
{ maximumTargetTemperature: DEFAULT_DEVICE_CONFIG.maximumTargetTemperature } : {}),
};
Object.entries(DEFAULT_DEVICE_CONFIG).forEach(([key, value]) => {
if (deviceConfig[key] === undefined) {
Expand Down
68 changes: 57 additions & 11 deletions src/platformAccessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import dgram from 'dgram';
import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';

import { GreeACPlatform } from './platform';
import { DeviceConfig } from './settings';
import { DeviceConfig, TEMPERATURE_TABLE } from './settings';
import { GreeAirConditionerTS } from './tsAccessory';
import crypto from './crypto';
import commands from './commands';
Expand Down Expand Up @@ -96,7 +96,7 @@ export class GreeAirConditioner {
.setProps({
minValue: this.deviceConfig.minimumTargetTemperature,
maxValue: this.deviceConfig.maximumTargetTemperature,
minStep: 1.0 })
minStep: 0.5 })
.onGet(this.getTargetTemperature.bind(this, 'CoolingThresholdTemperature'))
.onSet(this.setTargetTemperature.bind(this));

Expand All @@ -105,7 +105,7 @@ export class GreeAirConditioner {
.setProps({
minValue: this.deviceConfig.minimumTargetTemperature,
maxValue: this.deviceConfig.maximumTargetTemperature,
minStep: 1.0 })
minStep: 0.5 })
.onGet(this.getTargetTemperature.bind(this, 'HeatingThresholdTemperature'))
.onSet(this.setTargetTemperature.bind(this));

Expand Down Expand Up @@ -434,6 +434,46 @@ export class GreeAirConditioner {
return name;
}

calcDeviceTargetTemp(temp: number): number {
const baseTemp = Math.round(temp);
const baseFahrenheit = temp * 9 / 5 + 32;
const baseFahrenheitDecimalPart = baseFahrenheit - Math.floor(baseFahrenheit);
const correction = (baseFahrenheitDecimalPart >= 0.05 && baseFahrenheitDecimalPart < 0.15) ||
(baseFahrenheitDecimalPart >= 0.25 && baseFahrenheitDecimalPart < 0.35) ? 1 : 0;
return baseTemp - correction;
}

calcDeviceTargetOffset(temp: number): number {
if (temp === 16) {
return 0;
}
const baseFahrenheit = temp * 9 / 5 + 32;
const baseFahrenheitDecimalPart = baseFahrenheit - Math.floor(baseFahrenheit);
return (((baseFahrenheitDecimalPart >= 0.05 && baseFahrenheitDecimalPart < 0.15) ||
(baseFahrenheitDecimalPart >= 0.25 && baseFahrenheitDecimalPart < 0.35) ||
(baseFahrenheitDecimalPart >= 0.55 && baseFahrenheitDecimalPart < 0.65) ||
(baseFahrenheitDecimalPart >= 0.75 && baseFahrenheitDecimalPart < 0.85)) ? 1 : 0);
}

getTargetTempFromDevice(temp, offset): number {
const key = temp.toString() + ',' + offset.toString();
const value = TEMPERATURE_TABLE[key];
if (value === undefined) {
return 25; // default value if invalid data received from device
}
// some temperature values are the same on the physical AC unit -> fix this issue:
const targetValue = this.HeaterCooler.getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature).value ||
this.HeaterCooler.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).value;
if ((targetValue === 17.5 && value === 18) ||
(targetValue === 22.5 && value === 23) ||
(targetValue === 27.5 && value === 28)) {
this.platform.log.debug(`[${this.getDeviceLabel()}] TargetTemperature FIX: %d -> %d`, value, targetValue);
return targetValue;
}
// no fix needed, return original value
return value;
}

// device functions
get power() {
return (this.status[commands.power.code] === commands.power.value.on);
Expand Down Expand Up @@ -502,19 +542,27 @@ export class GreeAirConditioner {
}

get targetTemperature() {
return Math.max(Math.min((this.status[commands.targetTemperature.code] || 25) + (this.status[commands.temperatureOffset.code] || 1) - 1,
return Math.max(Math.min(
this.getTargetTempFromDevice(this.status[commands.targetTemperature.code] || 25, this.status[commands.temperatureOffset.code] || 0),
(this.deviceConfig.maximumTargetTemperature)), (this.deviceConfig.minimumTargetTemperature));
}

set targetTemperature(value) {
if (value === this.targetTemperature) {
return;
}
const command: Record<string, unknown> = { [commands.targetTemperature.code]: value };
let logValue = 'targetTemperature -> ' + value;
const tempValue = this.calcDeviceTargetTemp(value);
const command: Record<string, unknown> = { [commands.targetTemperature.code]: tempValue };
let logValue = 'targetTemperature -> ' + tempValue.toString();
const tempOffset = this.calcDeviceTargetOffset(value);
command[commands.temperatureOffset.code] = tempOffset;
logValue += ', temperatureOffset -> ' + tempOffset.toString();
const displayUnits = this.HeaterCooler.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits).value;
const deviceDisplayUnits = (displayUnits === this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS) ?
commands.units.value.celsius : commands.units.value.fahrenheit;
if (deviceDisplayUnits === commands.units.value.fahrenheit) {
logValue += ' (-> ' + Math.round(value * 9 / 5 +32) + ' °F)';
}
if (deviceDisplayUnits !== this.units) {
command[commands.units.code] = deviceDisplayUnits;
logValue += ', units -> ' + this.getKeyName(commands.units.value, deviceDisplayUnits);
Expand Down Expand Up @@ -642,17 +690,15 @@ export class GreeAirConditioner {
.updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE);
break;
case commands.mode.value.auto:
if (this.currentTemperature > this.targetTemperature) {
if (this.currentTemperature > this.targetTemperature + 1.5) {
this.platform.log.debug(`[${this.getDeviceLabel()}] updateStatus (Current Heater-Cooler State) -> COOLING`);
this.HeaterCooler.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState)
.updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.COOLING);
}
if (this.currentTemperature < this.targetTemperature) {
} else if (this.currentTemperature < this.targetTemperature - 1.5) {
this.platform.log.debug(`[${this.getDeviceLabel()}] updateStatus (Current Heater-Cooler State) -> HEATING`);
this.HeaterCooler.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState)
.updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.HEATING);
}
if (this.currentTemperature === this.targetTemperature) {
} else {
this.platform.log.debug(`[${this.getDeviceLabel()}] updateStatus (Current Heater-Cooler State) -> IDLE`);
this.HeaterCooler.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState)
.updateValue(this.platform.Characteristic.CurrentHeaterCoolerState.IDLE);
Expand Down
29 changes: 29 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,32 @@ export const DEFAULT_DEVICE_CONFIG: DeviceConfig = {
};

export const UDP_SCAN_PORT = 7000;

export const TEMPERATURE_TABLE = {
'16,0': 16,
'17,0': 16.5,
'17,1': 17,
'18,0': 18,
'18,1': 18.5,
'19,0': 19,
'19,1': 19.5,
'20,0': 20,
'21,0': 20.5,
'21,1': 21,
'22,0': 21.5,
'22,1': 22,
'23,0': 23,
'23,1': 23.5,
'24,0': 24,
'24,1': 24.5,
'25,0': 25,
'26,0': 25.5,
'26,1': 26,
'27,0': 26.5,
'27,1': 27,
'28,0': 28,
'28,1': 28.5,
'29,0': 29,
'29,1': 29.5,
'30,0': 30,
};

0 comments on commit db9c511

Please sign in to comment.