Skip to content

Commit

Permalink
feat: implement heater service and add accessoryType property to config
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-seongwoo-jun committed Feb 20, 2024
1 parent 75b9773 commit f84c1c8
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 25 deletions.
27 changes: 20 additions & 7 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
"oneOf": [
{
"title": "Navien Smart Account",
"enum": [
"account"
]
"enum": [ "account" ]
},
{
"title": "Token",
"enum": [
"token"
]
"enum": [ "token" ]
}
]
],
"required": true
},
"username": {
"title": "Username",
Expand Down Expand Up @@ -54,6 +51,22 @@
"type": "boolean",
"default": true,
"description": "When set to `true`, displays the accessory's target temperature on the Apple Home screen, but may inaccurately reflect the home's climate as the target temperature. When `false`, shows \"--°\" with a \"No response\" warning on the Apple Home screen, but does not set the home's climate to the target temperature."
},
"accessoryType": {
"title": "Accessory Type",
"type": "string",
"default": "HeaterCooler",
"oneOf": [
{
"title": "Heater",
"enum": [ "HeaterCooler" ]
},
{
"title": "Thermostat",
"enum": [ "Thermostat" ]
}
],
"required": true
}
}
}
Expand Down
151 changes: 133 additions & 18 deletions src/homebridge/electric-mat.device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { NavienService } from '../navien/navien.service';
import { NavienHomebridgePlatform, NavienPlatformAccessory } from '../platform';

export default class ElectricMat {
private readonly VALID_HEATING_STATES: number[];

private readonly thermostat: Service;
private readonly heater?: Service;
private readonly thermostat?: Service;
private readonly service: NavienService;
private readonly device: NavienDevice;

Expand All @@ -18,9 +17,12 @@ export default class ElectricMat {
const {
Characteristic,
Service,
navienService,
config: {
accessoryType,
},
} = this.platform;

const { navienService } = this.platform;
this.service = navienService;

const { device } = accessory.context;
Expand All @@ -32,19 +34,88 @@ export default class ElectricMat {
.setCharacteristic(Characteristic.Model, device.modelName)
.setCharacteristic(Characteristic.SerialNumber, device.id);

// set thermostat information
this.VALID_HEATING_STATES = [
Characteristic.CurrentHeatingCoolingState.OFF,
Characteristic.CurrentHeatingCoolingState.HEAT,
];
this.thermostat = this.initializeThermostat(device);
// set heater or thermostat service
if (accessoryType === 'HeaterCooler') {
this.heater = this.initializeHeater(device);
} else if (accessoryType === 'Thermostat') {
this.thermostat = this.initializeThermostat(device);
}
}

private get log() {
return this.platform.log;
}

initializeThermostat(device: NavienDevice): Service {
private initializeHeater(device: NavienDevice): Service {
const {
Characteristic: {
Name,
Active,
CurrentHeaterCoolerState,
TargetHeaterCoolerState,
CurrentTemperature,
HeatingThresholdTemperature,
TemperatureDisplayUnits,
},
Service: {
HeaterCooler,
},
} = this.platform;

const heater = this.accessory.getService(HeaterCooler)
|| this.accessory.addService(HeaterCooler);

// name, temp unit
heater.setCharacteristic(Name, device.name);
heater.getCharacteristic(TemperatureDisplayUnits)
.setProps({
validValues: [TemperatureDisplayUnits.CELSIUS],
})
.setValue(TemperatureDisplayUnits.CELSIUS);

// active state
heater.getCharacteristic(Active)
.onGet(this.getActive.bind(this))
.onSet(this.setActive.bind(this));

// current state
heater.getCharacteristic(CurrentHeaterCoolerState)
.onGet(this.getHeaterState.bind(this));

// target state
heater.getCharacteristic(TargetHeaterCoolerState)
.setProps({
validValues: [TargetHeaterCoolerState.HEAT],
})
.setValue(TargetHeaterCoolerState.HEAT);

// current temperature
heater.getCharacteristic(CurrentTemperature)
.onGet(this.getCurrentTemperature.bind(this));

// target temperature
const { heatControl } = device.functions;
heater.getCharacteristic(HeatingThresholdTemperature)
.setProps({
minValue: heatControl.rangeMin,
maxValue: heatControl.rangeMax,
minStep: parseFloat(heatControl.unit),
})
.onGet(this.getTemperature.bind(this))
.onSet(this.setTemperature.bind(this));

// subscribe to device events
device.powerChanges.subscribe((power) => {
heater.updateCharacteristic(Active, power ? Active.ACTIVE : Active.INACTIVE);
});
device.temperatureChanges.subscribe((temperature) => {
heater.updateCharacteristic(HeatingThresholdTemperature, temperature);
});

return heater;
}

private initializeThermostat(device: NavienDevice): Service {
const {
Characteristic: {
Name,
Expand Down Expand Up @@ -73,14 +144,20 @@ export default class ElectricMat {
// current state
thermostat.getCharacteristic(CurrentHeatingCoolingState)
.setProps({
validValues: this.VALID_HEATING_STATES,
validValues: [
CurrentHeatingCoolingState.OFF,
CurrentHeatingCoolingState.HEAT,
],
})
.onGet(this.getHeatingState.bind(this));

// target state
thermostat.getCharacteristic(TargetHeatingCoolingState)
.setProps({
validValues: this.VALID_HEATING_STATES,
validValues: [
TargetHeatingCoolingState.OFF,
TargetHeatingCoolingState.HEAT,
],
})
.onGet(this.getHeatingState.bind(this))
.onSet(this.setHeatingState.bind(this));
Expand Down Expand Up @@ -112,7 +189,45 @@ export default class ElectricMat {
return thermostat;
}

async getHeatingState(): Promise<CharacteristicValue> {

private async getActive(): Promise<CharacteristicValue> {
const { Characteristic } = this.platform;
const { power } = this.device;

this.log.debug('Get Active:', power ? 'ON' : 'OFF');

const state = power ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
return state;
}

private async setActive(value: CharacteristicValue) {
const state = value as number;
const power = !!state;

this.log.debug('Set Active:', power ? 'ON' : 'OFF');

await this.service.setPower(this.device, power);
}

private async getHeaterState(): Promise<CharacteristicValue> {
const { Characteristic } = this.platform;
const { power, isIdle } = this.device;

const state = (() => {
if (!power) {
return [Characteristic.CurrentHeaterCoolerState.INACTIVE, 'INACTIVE'];
}
return isIdle ?
[Characteristic.CurrentHeaterCoolerState.IDLE, 'IDLE'] :
[Characteristic.CurrentHeaterCoolerState.HEATING, 'HEATING'];
})();

this.log.debug('Get Heater State:', state[1]);

return state[0];
}

private async getHeatingState(): Promise<CharacteristicValue> {
const { Characteristic } = this.platform;
const { power } = this.device;

Expand All @@ -122,7 +237,7 @@ export default class ElectricMat {
return state;
}

async setHeatingState(value: CharacteristicValue) {
private async setHeatingState(value: CharacteristicValue) {
const state = value as number;
const power = !!state;

Expand All @@ -131,23 +246,23 @@ export default class ElectricMat {
await this.service.setPower(this.device, power);
}

async getTemperature(): Promise<CharacteristicValue> {
private async getTemperature(): Promise<CharacteristicValue> {
const { temperature } = this.device;

this.log.debug('Get Temperature:', temperature);

return temperature;
}

async setTemperature(value: CharacteristicValue) {
private async setTemperature(value: CharacteristicValue) {
const temperature = value as number;

this.log.debug('Set Temperature:', temperature);

await this.service.setTemperature(this.device, temperature);
}

async getCurrentTemperature(): Promise<CharacteristicValue> {
private async getCurrentTemperature(): Promise<CharacteristicValue> {
// this device does not support to get current temperature

// if the user wants to show the current temperature as the target temperature,
Expand Down
6 changes: 6 additions & 0 deletions src/navien/navien.device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export class NavienDevice {
this.powerSubject.next(value);
}

get isIdle() {
const { heatControl } = this.json.Properties.registry.attributes.functions;
const idleTemperature = heatControl.rangeMin - parseFloat(heatControl.unit);
return this._power && this._temperature === idleTemperature;
}

get temperature() {
return this._temperature;
}
Expand Down
1 change: 1 addition & 0 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type NavienPlatformConfig = PlatformConfig & {
refreshToken?: string;
accountSeq?: number;
showCurrentTemperatureAsTarget: boolean;
accessoryType: 'HeaterCooler' | 'Thermostat';
};

/**
Expand Down

0 comments on commit f84c1c8

Please sign in to comment.