Skip to content

Commit

Permalink
Add Blind Service
Browse files Browse the repository at this point in the history
  • Loading branch information
swissmanu committed Nov 10, 2023
1 parent 015b4a1 commit bbda3ab
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 5 deletions.
42 changes: 42 additions & 0 deletions src/homeAssistant/coverServiceMqttHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { z } from 'zod';
import { putMotorService, type BlindsServiceService, type SlatsServiceService } from '../litecom/restClient/index.js';
import { Logger } from '../util/logger.js';

export interface LitecomBlindsServiceAdapter {
putBlindServiceByZone: (typeof BlindsServiceService)['putBlindServiceByZone'];
putBlindsServiceByZoneAndDevice: (typeof BlindsServiceService)['putBlindServiceByZoneAndDevice'];
}
export interface LitecomSlatsServiceAdapter {
putSlatServiceByZone: (typeof SlatsServiceService)['putSlatServiceByZone'];
putSlatServiceByZoneAndDevice: (typeof SlatsServiceService)['putSlatServiceByZoneAndDevice'];
}

export const CoverCommand = z.discriminatedUnion('command', [
z.object({
command: z.literal('move'),
payload: z.union([z.literal('OPEN'), z.literal('CLOSE'), z.literal('STOP')]),
}),
]);
type CoverCommand = z.infer<typeof CoverCommand>;

export class CoverServiceMQTTHandler {
constructor(
private readonly blindsLitecomAdapter: LitecomBlindsServiceAdapter,
private readonly slatsLitecomAdapter: LitecomSlatsServiceAdapter,
private readonly log: Logger,
) {}

async handleZoneCommand(zoneId: string, { command, payload }: CoverCommand): Promise<void> {
switch (command) {
case 'move': {
await this.blindsLitecomAdapter.putBlindServiceByZone(zoneId, {
command: payload as putMotorService.command, // cast should be fine. HomeAssistantCoverEntity announces this enums' values to Home Assistant as payloads
});
}
}
}

async handleDeviceCommand(zoneId: string, deviceId: string, { command, payload }: CoverCommand): Promise<void> {

Check failure on line 39 in src/homeAssistant/coverServiceMqttHandler.ts

View workflow job for this annotation

GitHub Actions / quality-checks

'zoneId' is defined but never used

Check failure on line 39 in src/homeAssistant/coverServiceMqttHandler.ts

View workflow job for this annotation

GitHub Actions / quality-checks

'deviceId' is defined but never used

Check failure on line 39 in src/homeAssistant/coverServiceMqttHandler.ts

View workflow job for this annotation

GitHub Actions / quality-checks

'command' is defined but never used

Check failure on line 39 in src/homeAssistant/coverServiceMqttHandler.ts

View workflow job for this annotation

GitHub Actions / quality-checks

'payload' is defined but never used
this.log.warning('Not Implemented');
}
}
55 changes: 55 additions & 0 deletions src/homeAssistant/devices/homeAssistantCoverEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as Litecom from '../../litecom/restClient/index.js';
import { Config, config } from '../../util/config.js';
import { DataPointType, HomeAssistantEntity, HomeAssistantEntityType } from './homeAssistantEntity.js';

/**
* @see https://www.home-assistant.io/integrations/cover.mqtt/
*/
export class HomeAssistantCoverEntity extends HomeAssistantEntity {
override readonly homeAssistantEntityType: HomeAssistantEntityType = 'cover';
override readonly dataPointType: DataPointType = 'cover';

constructor(
config: Config,
private readonly hasBlinds: boolean,
private readonly hasSlats: boolean,
zone: Litecom.Zone,
device?: Litecom.Device,
) {
super(config, zone, device);
}

override get homeAssistantCommandTopics(): Record<string, string> {
if (this.device) {
return {
command_topic: `${config.LITECOM2MQTT_MQTT_TOPIC_PREFIX}/${this.zone.id}/evices/${this.objectId}/${this.dataPointType}/move`,
};
}
return {
command_topic: `${config.LITECOM2MQTT_MQTT_TOPIC_PREFIX}/${this.zone.id}/${this.dataPointType}/move`,
};
}

override get homeAssistantEntityConfig(): Record<string, string | number | boolean> {
const shared = {
name: 'Cover',
position_closed: 100.0,
position_open: 0.0,
payload_open: Litecom.putMotorService.command.OPEN,
payload_close: Litecom.putMotorService.command.CLOSE,
payload_stop: Litecom.putMotorService.command.STOP,
};

if (this.device) {
return {
...shared,
position_topic: `${config.LITECOM2MQTT_LITECOM_STATE_MQTT_TOPIC_PREFIX}/zones/${this.zone.id}/devices/${this.device.id}/services/blind/position`,
};
}

return {
...shared,
position_topic: `${config.LITECOM2MQTT_LITECOM_STATE_MQTT_TOPIC_PREFIX}/zones/${this.zone.id}/services/blind/position`,
};
}
}
9 changes: 9 additions & 0 deletions src/homeAssistant/devices/homeAssistantDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Device, Zone, ZoneWithoutAvailableServices } from '../../litecom/interr
import { Config } from '../../util/config.js';
import { getAllParentsForZone } from '../../util/getAllParentNamesForZone.js';
import { Logger } from '../../util/logger.js';
import { HomeAssistantCoverEntity } from './homeAssistantCoverEntity.js';
import { HomeAssistantEntity } from './homeAssistantEntity.js';
import { HomeAssistantLightEntity } from './homeAssistantLightEntity.js';
import { HomeAssistantSceneEntity } from './homeAssistantSceneEntity.js';
Expand Down Expand Up @@ -56,6 +57,9 @@ export class HomeAssistantDevice {
device.addEntity(new HomeAssistantSceneEntity(config, zone.scenes, zone.zone));
device.addEntity(new HomeAssistantSceneOutOfTuneEntity(config, zone.zone));
}
if (zone.blinds || zone.slats) {
device.addEntity(new HomeAssistantCoverEntity(config, zone.blinds, zone.slats, zone.zone));
}

return device;
}
Expand Down Expand Up @@ -83,6 +87,11 @@ export class HomeAssistantDevice {
);
homeAssistantDevice.addEntity(new HomeAssistantSceneOutOfTuneEntity(config, inZone.zone, device.device));
}
if (device.blinds || device.slats) {
homeAssistantDevice.addEntity(
new HomeAssistantCoverEntity(config, device.blinds, device.slats, inZone.zone, device.device),
);
}

return homeAssistantDevice;
}
Expand Down
6 changes: 2 additions & 4 deletions src/homeAssistant/devices/homeAssistantEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Config } from '../../util/config.js';
import { HomeAssistantAnnouncement, HomeAssistantDevice } from './homeAssistantDevice.js';

export type HomeAssistantEntityType = 'light' | 'button' | 'cover' | 'select' | 'sensor';
export type DataPointType = 'lighting' | 'activeScene' | 'sceneOutOfTune';
export type DataPointType = 'lighting' | 'activeScene' | 'sceneOutOfTune' | 'cover';

export abstract class HomeAssistantEntity {
abstract readonly homeAssistantEntityType: HomeAssistantEntityType;
Expand All @@ -20,9 +20,7 @@ export abstract class HomeAssistantEntity {
protected readonly config: Config,
protected readonly zone: Zone,
protected readonly device?: Device,
) {
// this.subscribe();
}
) {}

get name(): string {
return this.device?.name ?? this.zone.name;
Expand Down
12 changes: 12 additions & 0 deletions src/homeAssistant/mqttClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MqttClient as Client, connectAsync } from 'mqtt';
import { Config } from '../util/config.js';
import { createAsyncDisposable } from '../util/createDisposable.js';
import { Logger, log } from '../util/logger.js';
import { CoverCommand, CoverServiceMQTTHandler } from './coverServiceMqttHandler.js';
import {
HomeAssistantAnnouncement,
HomeAssistantDevice,
Expand All @@ -16,6 +17,7 @@ export class MqttClient implements HomeAssistantDeviceAnnouncer {
constructor(
private readonly lightingHandler: LightingServiceMQTTHandler,
private readonly sceneHandler: SceneServiceMQTTHandler,
private readonly coverHandler: CoverServiceMQTTHandler,
private readonly log: Logger,
) {}

Expand Down Expand Up @@ -58,6 +60,13 @@ export class MqttClient implements HomeAssistantDeviceAnnouncer {
SceneCommand.parse({ command, payload }),
);
break;
case 'cover':
this.coverHandler.handleDeviceCommand(
zoneId,
deviceId,
CoverCommand.parse({ command, payload }),
);
break;
default:
this.log.warning(`Cannot handle unknown datapoint type "${dataPointType}"`, {
zoneId,
Expand Down Expand Up @@ -92,6 +101,9 @@ export class MqttClient implements HomeAssistantDeviceAnnouncer {
}),
);
break;
case 'cover':
this.coverHandler.handleZoneCommand(zoneId, CoverCommand.parse({ command, payload }));
break;
default:
this.log.warning(`Cannot handle unknown datapoint type "${dataPointType}"`, {
zoneId,
Expand Down
12 changes: 12 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CoverServiceMQTTHandler } from './homeAssistant/coverServiceMqttHandler.js';
import { HomeAssistantDevice } from './homeAssistant/devices/homeAssistantDevice.js';
import { LightingServiceMQTTHandler } from './homeAssistant/lightingServiceMqttHandler.js';
import { MqttClient } from './homeAssistant/mqttClient.js';
Expand Down Expand Up @@ -32,6 +33,17 @@ const mqttClient = new MqttClient(
scenesByZoneOrDeviceId,
log,
),
new CoverServiceMQTTHandler(
{
putBlindServiceByZone: Litecom.BlindsServiceService.putBlindServiceByZone,
putBlindsServiceByZoneAndDevice: Litecom.BlindsServiceService.putBlindServiceByZoneAndDevice,
},
{
putSlatServiceByZone: Litecom.SlatsServiceService.putSlatServiceByZone,
putSlatServiceByZoneAndDevice: Litecom.SlatsServiceService.putSlatServiceByZoneAndDevice,
},
log,
),
log,
);
await mqttClient.init(config);
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
Expand Down

0 comments on commit bbda3ab

Please sign in to comment.