Skip to content

Commit

Permalink
Add serial number + fix on/off + fix polling temperature
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwych committed Dec 23, 2023
1 parent 61a75cb commit 20243c0
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 366 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"displayName": "Vastra Radiator Valve",
"name": "homebridge-vastra-radiator-valve",
"version": "1.0.0",
"version": "1.0.1",
"description": "Homebridge integration for Vastra's radiator valves",
"license": "MIT",
"author": {
Expand Down Expand Up @@ -40,6 +40,6 @@
"rimraf": "^3.0.2",
"ts-node": "^10.9.1",
"typescript": "^4.9.5",
"vastra-radiator-valve": "^1.0.6"
"vastra-radiator-valve": "^1.1.1"
}
}
30 changes: 15 additions & 15 deletions src/platform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
API,
Categories,
Characteristic,
DynamicPlatformPlugin,
Logger,
Expand All @@ -24,7 +25,7 @@ export class VastraRadiatorValveHomebridgePlugin

private radiatorValves?: RadiatorValves;

public readonly accessories: PlatformAccessory[] = [];
public readonly accessories: VastraRadiatorValvePlatformAccessory[] = [];

constructor(
public readonly log: Logger,
Expand All @@ -42,19 +43,17 @@ export class VastraRadiatorValveHomebridgePlugin

configureAccessory(accessory: PlatformAccessory) {
this.log.info("Loading accessory from cache:", accessory.displayName);
this.accessories.push(accessory);
this.accessories.push(
new VastraRadiatorValvePlatformAccessory(this, accessory)
);
}

startDiscovering() {
const bluetooth = new NobleBluetoothCentral();

this.radiatorValves = new RadiatorValves(bluetooth, {
this.radiatorValves = new RadiatorValves(new NobleBluetoothCentral(), {
logger: new VastraLogger(false),
raspberryFix: true,
});
this.radiatorValves.startScanning(async (valve) => {
const uuid = this.api.hap.uuid.generate(valve.peripheral.address);

try {
await valve.connect();
} catch (error) {
Expand All @@ -65,25 +64,26 @@ export class VastraRadiatorValveHomebridgePlugin
return;
}

const uuid = this.api.hap.uuid.generate(valve.peripheral.address);
const existingAccessory = this.accessories.find(
(accessory) => accessory.UUID === uuid
(accessory) => accessory.accessory.UUID === uuid
);
if (existingAccessory) {
this.log.info("Restoring accessory:", valve.peripheral.address);
new VastraRadiatorValvePlatformAccessory(
this,
existingAccessory,
valve
);
existingAccessory.setValve(valve);
} else {
this.log.info("Adding new accessory:", valve.peripheral.address);
const accessory = new this.api.platformAccessory(
valve.peripheral.address,
uuid
uuid,
Categories.THERMOSTAT
);
accessory.context.serialNumber = valve.getSerialNumber();
accessory.context.address = valve.peripheral.address;

new VastraRadiatorValvePlatformAccessory(this, accessory, valve);
this.accessories.push(
new VastraRadiatorValvePlatformAccessory(this, accessory, valve)
);

this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
accessory,
Expand Down
130 changes: 90 additions & 40 deletions src/radiatorValveAccessory.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { Service, PlatformAccessory, CharacteristicValue } from "homebridge";
import { VastraRadiatorValveHomebridgePlugin } from "./platform";
import { CharacteristicValue, PlatformAccessory, Service } from "homebridge";
import { RadiatorValve } from "vastra-radiator-valve";
import PrefixLogger from "./logger";
import { VastraRadiatorValveHomebridgePlugin } from "./platform";

export class VastraRadiatorValvePlatformAccessory {
private readonly log = new PrefixLogger(
this.platform.log,
this.getMacAddress()
);

private valve?: RadiatorValve;
private thermostatService!: Service;
private currentTemperature = 0;
private currentTemperatureUpdateIntervalId?: NodeJS.Timeout;
private targetTemperature = 0;
private temperatureUpdateInterval?: NodeJS.Timeout;
private isUpdatingTargetTemperature = false;

constructor(
private readonly platform: VastraRadiatorValveHomebridgePlugin,
private readonly accessory: PlatformAccessory,
private readonly valve: RadiatorValve
public readonly platform: VastraRadiatorValveHomebridgePlugin,
public readonly accessory: PlatformAccessory,
valve?: RadiatorValve
) {
this.configureInformationService();
this.configureThermostatService();
this.startPollingTask();

if (valve) {
this.setValve(valve);
}
}

private configureInformationService() {
Expand All @@ -34,7 +39,7 @@ export class VastraRadiatorValvePlatformAccessory {
)
.setCharacteristic(
this.platform.Characteristic.SerialNumber,
"Default-Serial"
this.getSerialNumber()
);
}

Expand All @@ -50,84 +55,129 @@ export class VastraRadiatorValvePlatformAccessory {
)
.setCharacteristic(
this.platform.Characteristic.CurrentHeatingCoolingState,
this.platform.Characteristic.CurrentHeatingCoolingState.HEAT
)
.setCharacteristic(
this.platform.Characteristic.TemperatureDisplayUnits,
this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS
this.platform.Characteristic.CurrentHeatingCoolingState.OFF
);

this.thermostatService
.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState)
.onSet(this.handleTargetHeatingCoolingStateSet)
.onGet(this.handleTargetHeatingCoolingStateGet);
.onSet(this.setTargetHeatingCoolingState)
.onGet(this.getTargetHeatingCoolingState);

this.thermostatService
.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.onGet(this.handleCurrentTemperatureGet);
.onGet(this.getCurrentTemperature);

this.thermostatService
.getCharacteristic(this.platform.Characteristic.TargetTemperature)
.onSet(this.handleTargetTemperatureSet)
.onGet(this.handleTargetTemperatureGet);
.onSet(this.setTargetTemperature)
.onGet(this.getTargetTemperature);

this.valve.getTargetTemperature().then((targetTemperature) => {
this.targetTemperature = targetTemperature;
});
this.thermostatService
.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits)
.onSet(this.setTemperatureDisplayUnits)
.onGet(this.getTemperatureDisplayUnits);
}

private startPollingTask() {
this.currentTemperatureUpdateIntervalId = setInterval(async () => {
this.log.info(`Polling temperature`);

this.currentTemperature = await this.valve.getCurrentTemperature();
private async pollTemperatures() {
try {
this.currentTemperature = await this.valve!.getCurrentTemperature();
this.thermostatService.updateCharacteristic(
this.platform.Characteristic.CurrentTemperature,
this.currentTemperature
);

this.targetTemperature = await this.valve.getTargetTemperature();
this.thermostatService.updateCharacteristic(
this.platform.Characteristic.TargetTemperature,
this.targetTemperature
);
if (!this.isUpdatingTargetTemperature) {
this.targetTemperature = await this.valve!.getTargetTemperature();
this.thermostatService.updateCharacteristic(
this.platform.Characteristic.TargetTemperature,
this.targetTemperature
);
}
} catch (error) {
this.log.error("Failed to poll temperatures");
this.log.error(String(error));
}
}

private async startPollingTask() {
if (!this.valve) {
return;
}

await this.pollTemperatures();

this.temperatureUpdateInterval = setInterval(() => {
this.pollTemperatures();
}, 10000);

this.platform.api.on("shutdown", () => {
clearInterval(this.currentTemperatureUpdateIntervalId);
clearInterval(this.temperatureUpdateInterval);
});
}

private handleCurrentTemperatureGet = () => {
private getCurrentTemperature = () => {
return Math.max(this.currentTemperature, 10);
};

private handleTargetTemperatureSet = async (value: CharacteristicValue) => {
private setTargetTemperature = async (value: CharacteristicValue) => {
const { HapStatusError, HAPStatus } = this.platform.api.hap;
if (!this.valve) {
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
}
if (this.isUpdatingTargetTemperature) {
throw new HapStatusError(HAPStatus.RESOURCE_BUSY);
}

this.isUpdatingTargetTemperature = true;
try {
await this.valve.setTargetTemperature(value as number);
this.targetTemperature = value as number;
this.log.debug(`Target temperature set to ` + value);
} catch (error) {
this.log.error(`Failed to set target temperature: ` + String(error));

const { HapStatusError, HAPStatus } = this.platform.api.hap;
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
} finally {
this.isUpdatingTargetTemperature = false;
}
};

private handleTargetTemperatureGet = () => {
private getTargetTemperature = () => {
return Math.max(this.targetTemperature, 10);
};

private handleTargetHeatingCoolingStateSet = () => {
private setTargetHeatingCoolingState = () => {
const { HapStatusError, HAPStatus } = this.platform.api.hap;
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
};

private getTargetHeatingCoolingState = () => {
return this.valve
? this.platform.Characteristic.TargetHeatingCoolingState.AUTO
: this.platform.Characteristic.TargetHeatingCoolingState.OFF;
};

private setTemperatureDisplayUnits = () => {
const { HapStatusError, HAPStatus } = this.platform.api.hap;
throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
};

private handleTargetHeatingCoolingStateGet = () => {
return this.platform.Characteristic.TargetHeatingCoolingState.AUTO;
private getTemperatureDisplayUnits = () => {
return this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS;
};

public setValve(valve: RadiatorValve) {
this.valve = valve;
this.thermostatService.updateCharacteristic(
this.platform.Characteristic.CurrentHeatingCoolingState,
this.platform.Characteristic.CurrentHeatingCoolingState.HEAT
);
this.startPollingTask();
}

private getSerialNumber() {
return this.accessory.context.serialNumber ?? "Unknown";
}

private getMacAddress() {
return this.accessory.context.address;
}
Expand Down
Loading

0 comments on commit 20243c0

Please sign in to comment.