Skip to content
This repository has been archived by the owner on Nov 7, 2024. It is now read-only.

Commit

Permalink
feat(sensor): add humidity & temperature sensor
Browse files Browse the repository at this point in the history
  • Loading branch information
pvanbuijtene authored Aug 13, 2024
1 parent e93fc81 commit f730905
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ClusterServer, RelativeHumidityMeasurementCluster } from '@project-chip/matter.js/cluster';
import { Device } from '@project-chip/matter.js/device';

import { HomeAssistantMatterEntity } from '@/models/index.js';

import { AspectBase } from './aspect-base.js';

export class HumidityMeasurementAspect extends AspectBase {
constructor(
private readonly device: Device,
entity: HomeAssistantMatterEntity,
) {
super('HumidityMeasurementAspect', entity);

device.addClusterServer(
ClusterServer(
RelativeHumidityMeasurementCluster,
{
minMeasuredValue: null,
maxMeasuredValue: null,
measuredValue: this.getMeasuredValue(entity.state),
},
{},
),
);
}

async update(entity: HomeAssistantMatterEntity): Promise<void> {
const cluster = this.device.getClusterServer(RelativeHumidityMeasurementCluster)!;
const value = this.getMeasuredValue(entity.state);
if (value != null && cluster.getMeasuredValueAttribute() !== value) {
this.log.debug('FROM HA: %s changed measured value to %s', this.entityId, value);
cluster.setMeasuredValueAttribute(value);
}
}

getMeasuredValue(state: string) {
if (state != null) {
return +state * 100;
}
return null;
}
}
2 changes: 2 additions & 0 deletions packages/home-assistant-matter-hub/core/src/aspects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export * from './level-control-aspect.js';
export * from './occupancy-sensing-aspect.js';
export * from './on-off-aspect.js';
export * from './window-covering-aspect.js';
export * from './humidity-measurement-aspect.js';
export * from './temperature-measurement-aspect.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ClusterServer, TemperatureMeasurementCluster } from '@project-chip/matter.js/cluster';
import { Device } from '@project-chip/matter.js/device';

import { HomeAssistantMatterEntity } from '@/models/index.js';

import { AspectBase } from './aspect-base.js';

export class TemperatureMeasurementAspect extends AspectBase {
private unitOfMeasurement: string | undefined;

constructor(
private readonly device: Device,
entity: HomeAssistantMatterEntity,
) {
super('TemperatureMeasurementAspect', entity);
this.unitOfMeasurement = entity.attributes.unit_of_measurement;
device.addClusterServer(
ClusterServer(
TemperatureMeasurementCluster,
{
minMeasuredValue: null,
maxMeasuredValue: null,
measuredValue: this.getTemperatureInCelsius(entity),
},
{},
),
);
}

async update(entity: HomeAssistantMatterEntity): Promise<void> {
const cluster = this.device.getClusterServer(TemperatureMeasurementCluster)!;
const value = this.getTemperatureInCelsius(entity);
if (value != null && cluster.getMeasuredValueAttribute() !== value) {
this.log.debug('FROM HA: %s changed measured value to %s', this.entityId, value);
cluster.setMeasuredValueAttribute(value);
}
}

getTemperatureInCelsius(entity: HomeAssistantMatterEntity): number | null {
if (entity.state == null) {
return null;
}

const temperature = +entity.state * 100;
switch (this.unitOfMeasurement) {
case '°C':
return temperature;
case '°F':
return ((temperature - 32) * 5) / 9;
case 'K':
return temperature - 273.15;
default:
this.log.warn('Unsupported unit of measurement for temperature: %s', this.unitOfMeasurement);
return null;
}
}
}
3 changes: 3 additions & 0 deletions packages/home-assistant-matter-hub/core/src/devices/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './lock-device.js';
export * from './switch-device.js';
export * from './cover-device.js';
export * from './fan-device.js';
export * from './sensor-device.js';
export * from './utils/unsupported-device-class-error.js';

export enum EntityDomain {
automation = 'automation',
Expand All @@ -19,4 +21,5 @@ export enum EntityDomain {
script = 'script',
switch = 'switch',
fan = 'fan',
sensor = 'sensor',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Device, DeviceTypeDefinition, DeviceTypes } from '@project-chip/matter.js/device';

import { IdentifyAspect, HumidityMeasurementAspect, TemperatureMeasurementAspect } from '@/aspects/index.js';
import { AspectBase } from '@/aspects/index.js';
import { DeviceBase, DeviceBaseConfig } from '@/devices/index.js';
import { HomeAssistantMatterEntity } from '@/models/index.js';

import { EntityDomain } from './index.js';
import { UnsupportedDeviceClassError } from './utils/unsupported-device-class-error.js';

// https://www.home-assistant.io/integrations/sensor/
enum SensorDeviceClass {
Humidity = 'humidity',
Temperature = 'temperature',
}

interface InternalSensorDeviceConfig {
deviceType: DeviceTypeDefinition;
createAspects: (device: Device, entity: HomeAssistantMatterEntity) => AspectBase[];
}

const humiditySensor: InternalSensorDeviceConfig = {
deviceType: DeviceTypes.HUMIDITY_SENSOR,
createAspects: (device, entity) => [new HumidityMeasurementAspect(device, entity)],
};

const temperatureSensor: InternalSensorDeviceConfig = {
deviceType: DeviceTypes.TEMPERATURE_SENSOR,
createAspects: (device, entity) => [new TemperatureMeasurementAspect(device, entity)],
};

const deviceClassConfigs: Record<SensorDeviceClass, InternalSensorDeviceConfig> = {
[SensorDeviceClass.Humidity]: humiditySensor,
[SensorDeviceClass.Temperature]: temperatureSensor,
};

export class SensorDevice extends DeviceBase {
private static getConfig(entity: HomeAssistantMatterEntity): InternalSensorDeviceConfig {
if (entity.attributes.device_class) {
const config = deviceClassConfigs[entity.attributes.device_class as SensorDeviceClass];
if (config) {
return config;
}
}
throw new UnsupportedDeviceClassError(EntityDomain.sensor, entity.attributes.device_class ?? '<unknown>');
}

constructor(entity: HomeAssistantMatterEntity, config: DeviceBaseConfig) {
const deviceTypeConfig = SensorDevice.getConfig(entity);
super(entity, deviceTypeConfig.deviceType, config);
this.addAspect(new IdentifyAspect(this.matter, entity));
deviceTypeConfig.createAspects(this.matter, entity).forEach((v) => this.addAspect(v));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class UnsupportedDeviceClassError extends Error {
public domain: string;
public deviceClass: string;
constructor(domain: string, deviceClass: string) {
super(`Device class "${deviceClass}" for domain "${domain}" is not supported!`);
this.domain = domain;
this.deviceClass = deviceClass;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class MatterConnector {
[EntityDomain.binary_sensor]: (entity, config) => new devices.BinarySensorDevice(entity, config),
[EntityDomain.cover]: (entity, config) => new devices.CoverDevice(this.client, entity, config),
[EntityDomain.fan]: (entity) => new devices.FanDevice(this.client, entity, this.defaultDeviceConfig),
[EntityDomain.sensor]: (entity, config) => new devices.SensorDevice(entity, config),
// climate: (entity) => new ClimateDevice(this.client, entity),
};

Expand Down Expand Up @@ -118,18 +119,18 @@ export class MatterConnector {
this.deviceOverrides.domains?.[domain] ?? {},
this.deviceOverrides.entities?.[entity.entity_id] ?? {},
);
const device = this.deviceFactories[domain](entity, deviceConfig);

try {
const device = this.deviceFactories[domain](entity, deviceConfig);
await this.registry.register(device);
this.devices.set(entity.entity_id, device);
await device.updateState(entity);
} catch (e: unknown) {
this.log.warn('Failed to register device for %s', entity.entity_id);
this.log.error((e as object).toString());
this.ignoreEntities.add(entity.entity_id);
this.devices.delete(entity.entity_id);
}
await device.updateState(entity);
}

private async update(entity: HomeAssistantMatterEntity) {
Expand Down

0 comments on commit f730905

Please sign in to comment.