Skip to content

Commit

Permalink
feat: Reworked the integration loading mechanism
Browse files Browse the repository at this point in the history
Integration files are now only imported if they are configured.
Publishers are now also integrations. Communication for entity updates
is now done loosely coupled via the NodeJS event bus.
  • Loading branch information
mKeRix committed Jan 18, 2020
1 parent 6387baf commit 05c0fa3
Show file tree
Hide file tree
Showing 46 changed files with 190 additions and 335 deletions.
6 changes: 3 additions & 3 deletions config/default.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import AppConfig from '../src/config/app.config';
import {BluetoothLowEnergyConfig} from '../src/bluetooth-low-energy/bluetooth-low-energy.config';
import {BluetoothLowEnergyConfig} from '../src/integrations/bluetooth-low-energy/bluetooth-low-energy.config';
import {ClusterConfig} from '../src/cluster/cluster.config';
import {GlobalConfig} from '../src/config/global.config';
import {HomeAssistantConfig} from '../src/home-assistant/home-assistant.config';
import {OmronD6tConfig} from '../src/omron-d6t/omron-d6t.config';
import {HomeAssistantConfig} from '../src/integrations/home-assistant/home-assistant.config';
import {OmronD6tConfig} from '../src/integrations/omron-d6t/omron-d6t.config';

const config: AppConfig = {
global: new GlobalConfig(),
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"bin": "./bin/room-assistant.js",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"build": "nest build && npm run copy:assets",
"format": "prettier --single-quote --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
Expand All @@ -19,7 +19,8 @@
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"copy:assets": "cp package-lock.json dist/ && cp -r bin/ dist/bin/"
},
"dependencies": {
"@nestjs/common": "^6.7.2",
Expand All @@ -34,6 +35,7 @@
"lodash": "^4.17.15",
"mathjs": "^6.5.0",
"mdns": "^2.5.1",
"nest-emitter": "^1.1.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0",
"rxjs": "^6.5.3",
Expand Down
29 changes: 18 additions & 11 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { Module, Type } from '@nestjs/common';
import { BluetoothLowEnergyModule } from './bluetooth-low-energy/bluetooth-low-energy.module';
import { Module } from '@nestjs/common';
import c from 'config';
import { resolveClasses } from './util/resolver';
import { OmronD6tModule } from './omron-d6t/omron-d6t.module';
import { IntegrationsModule } from './integrations/integrations.module';
import { EntitiesModule } from './entities/entities.module';
import { ConfigModule } from './config/config.module';
import { ClusterModule } from './cluster/cluster.module';
import { ScheduleModule } from '@nestjs/schedule';
import _ from 'lodash';
import { NestEmitterModule } from 'nest-emitter';
import { EventEmitter } from 'events';

export const CONFIGURED_INTEGRATIONS = c.get<string[]>('global.integrations');
export const INTEGRATION_MAPPING: { [key: string]: Type<any> } = {
bluetoothLowEnergy: BluetoothLowEnergyModule,
omronD6t: OmronD6tModule
};
export const CONFIGURED_INTEGRATIONS = c
.get<string[]>('global.integrations')
.map(id => _.kebabCase(id));

@Module({
imports: [
...resolveClasses(CONFIGURED_INTEGRATIONS, INTEGRATION_MAPPING),
OmronD6tModule
EntitiesModule,
ConfigModule,
ClusterModule,
ScheduleModule.forRoot(),
NestEmitterModule.forRoot(new EventEmitter()),
IntegrationsModule.register(CONFIGURED_INTEGRATIONS)
]
})
export class AppModule {}
18 changes: 0 additions & 18 deletions src/bluetooth-low-energy/bluetooth-low-energy.module.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/config/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GlobalConfig } from './global.config';
import { ClusterConfig } from '../cluster/cluster.config';
import { BluetoothLowEnergyConfig } from '../bluetooth-low-energy/bluetooth-low-energy.config';
import { HomeAssistantConfig } from '../home-assistant/home-assistant.config';
import { OmronD6tConfig } from '../omron-d6t/omron-d6t.config';
import { BluetoothLowEnergyConfig } from '../integrations/bluetooth-low-energy/bluetooth-low-energy.config';
import { HomeAssistantConfig } from '../integrations/home-assistant/home-assistant.config';
import { OmronD6tConfig } from '../integrations/omron-d6t/omron-d6t.config';

export default interface AppConfig {
global: GlobalConfig;
Expand Down
1 change: 0 additions & 1 deletion src/config/global.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ import * as os from 'os';

export class GlobalConfig {
instanceName: string = os.hostname();
publishers: string[] = [];
integrations: string[] = [];
}
14 changes: 8 additions & 6 deletions src/entities/attributes.proxy.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import _ from 'lodash';
import { EntitiesEventEmitter } from './entities.events';

export class AttributesProxyHandler
implements ProxyHandler<{ [key: string]: string | number | boolean }> {
constructor(
private readonly entityId: string,
private readonly distributed: boolean,
private readonly attributesCallback: (
entityId: string,
attributes: { [key: string]: any },
distributed?: boolean
) => void
private readonly emitter: EntitiesEventEmitter
) {}

set(
Expand All @@ -22,7 +19,12 @@ export class AttributesProxyHandler
target[p as string] = value;

if (!_.isEqual(value, oldValue)) {
this.attributesCallback(this.entityId, target, this.distributed);
this.emitter.emit(
'attributesUpdate',
this.entityId,
target,
this.distributed
);
}

return true;
Expand Down
25 changes: 25 additions & 0 deletions src/entities/entities.events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Entity } from './entity.entity';
import { EntityOptions } from './entity-options.entity';
import { StrictEventEmitter } from 'nest-emitter';
import EventEmitter = NodeJS.EventEmitter;

interface EntitiesEvents {
newEntity: (entity: Entity, publisherOptions?: EntityOptions) => void;

stateUpdate: (
id: string,
state: boolean | string | number,
distributed?: boolean
) => void;

attributesUpdate: (
entityId: string,
attributes: { [key: string]: any },
distributed?: boolean
) => void;
}

export type EntitiesEventEmitter = StrictEventEmitter<
EventEmitter,
EntitiesEvents
>;
2 changes: 0 additions & 2 deletions src/entities/entities.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Module } from '@nestjs/common';
import { EntitiesService } from './entities.service';
import { PublishersModule } from '../publishers/publishers.module';

@Module({
imports: [PublishersModule],
providers: [EntitiesService],
exports: [EntitiesService]
})
Expand Down
16 changes: 8 additions & 8 deletions src/entities/entities.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Injectable } from '@nestjs/common';
import { Entity } from './entity.entity';
import { PublishersService } from '../publishers/publishers.service';
import { EntityProxyHandler } from './entity.proxy';
import { EntityOptions } from '../publishers/entity-options.entity';
import { EntityOptions } from './entity-options.entity';
import { InjectEventEmitter } from 'nest-emitter';
import { EntitiesEventEmitter } from './entities.events';

@Injectable()
export class EntitiesService {
private readonly entities = new Map<string, Entity>();

constructor(private readonly publishersService: PublishersService) {}
constructor(
@InjectEventEmitter() private readonly emitter: EntitiesEventEmitter
) {}

has(id: string): boolean {
return this.entities.has(id);
Expand All @@ -25,13 +28,10 @@ export class EntitiesService {

const proxy = new Proxy<Entity>(
entity,
new EntityProxyHandler(
this.publishersService.publishNewState.bind(this.publishersService),
this.publishersService.publishNewAttributes.bind(this.publishersService)
)
new EntityProxyHandler(this.emitter)
);
this.entities.set(entity.id, proxy);
this.publishersService.publishNewEntity(proxy, entityOptions);
this.emitter.emit('newEntity', proxy, entityOptions);
return proxy;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntityConfig } from '../home-assistant/entity-config';
import { EntityConfig } from '../integrations/home-assistant/entity-config';

export class EntityOptions {
homeAssistant?: Partial<EntityConfig> = {};
Expand Down
22 changes: 4 additions & 18 deletions src/entities/entity.proxy.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import { Entity } from './entity.entity';
import { AttributesProxyHandler } from './attributes.proxy';
import { EntitiesEventEmitter } from './entities.events';

export class EntityProxyHandler implements ProxyHandler<Entity> {
constructor(
private readonly stateCallback: (
id: string,
state: string | number | boolean,
distributed?: boolean
) => void,
private readonly attributesCallback: (
entityId: string,
attributes: { [key: string]: any },
distributed?: boolean
) => void
) {}
constructor(private readonly emitter: EntitiesEventEmitter) {}

get(target: Entity, p: string | number | symbol, receiver: any): any {
if (p === 'attributes') {
return new Proxy(
target[p],
new AttributesProxyHandler(
target.id,
target.distributed,
this.attributesCallback
)
new AttributesProxyHandler(target.id, target.distributed, this.emitter)
);
} else {
return target[p];
Expand All @@ -40,7 +26,7 @@ export class EntityProxyHandler implements ProxyHandler<Entity> {
target[p] = value;

if (p === 'state' && oldValue !== value) {
this.stateCallback(target.id, value, target.distributed);
this.emitter.emit('stateUpdate', target.id, value, target.distributed);
}

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Sensor } from '../entities/sensor.entity';
import { Sensor } from '../../entities/sensor.entity';

export class BluetoothLowEnergyDistributedSensor extends Sensor {
timeout: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common';
import { NewDistanceEvent } from './new-distance.event';
import { ConfigService } from '../config/config.service';
import { EntitiesService } from '../entities/entities.service';
import { ConfigService } from '../../config/config.service';
import { EntitiesService } from '../../entities/entities.service';
import { BluetoothLowEnergyConfig } from './bluetooth-low-energy.config';
import slugify from 'slugify';
import _ from 'lodash';
import { BluetoothLowEnergyDistributedSensor } from './bluetooth-low-energy-distributed.sensor';
import { SchedulerRegistry } from '@nestjs/schedule';
import { Entity } from '../entities/entity.entity';
import { Entity } from '../../entities/entity.entity';

@Injectable()
export class BluetoothLowEnergyDistributedService {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { DynamicModule, Module } from '@nestjs/common';
import { BluetoothLowEnergyService } from './bluetooth-low-energy.service';
import { EntitiesModule } from '../../entities/entities.module';
import { ConfigModule } from '../../config/config.module';
import { ClusterModule } from '../../cluster/cluster.module';
import { BluetoothLowEnergyDistributedService } from './bluetooth-low-energy-distributed.service';
import { ScheduleModule } from '@nestjs/schedule';

@Module({})
export default class BluetoothLowEnergyModule {
static forRoot(): DynamicModule {
return {
module: BluetoothLowEnergyModule,
imports: [
EntitiesModule,
ConfigModule,
ClusterModule,
ScheduleModule.forRoot()
],
providers: [
BluetoothLowEnergyService,
BluetoothLowEnergyDistributedService
]
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
import noble, { Peripheral } from '@abandonware/noble';
import * as _ from 'lodash';
import slugify from 'slugify';
import { EntitiesService } from '../entities/entities.service';
import { Sensor } from '../entities/sensor.entity';
import { EntitiesService } from '../../entities/entities.service';
import { Sensor } from '../../entities/sensor.entity';
import { IBeacon } from './i-beacon.entity';
import { Tag } from './tag.entity';
import { ConfigService } from '../config/config.service';
import { Entity } from '../entities/entity.entity';
import { ConfigService } from '../../config/config.service';
import { Entity } from '../../entities/entity.entity';
import { BluetoothLowEnergyConfig } from './bluetooth-low-energy.config';
import { ClusterService } from '../cluster/cluster.service';
import { ClusterService } from '../../cluster/cluster.service';
import { NewDistanceEvent } from './new-distance.event';
import { BluetoothLowEnergyDistributedService } from './bluetooth-low-energy-distributed.service';

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { version } from '../../package.json';
import { version } from '../../../package.json';

export class Device {
constructor(identifiers: string | string[]) {
Expand Down
File renamed without changes.
File renamed without changes.
14 changes: 14 additions & 0 deletions src/integrations/home-assistant/home-assistant.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DynamicModule, Module } from '@nestjs/common';
import { HomeAssistantService } from './home-assistant.service';
import { ConfigModule } from '../../config/config.module';

@Module({})
export default class HomeAssistantModule {
static forRoot(): DynamicModule {
return {
module: HomeAssistantModule,
imports: [ConfigModule],
providers: [HomeAssistantService]
};
}
}
Loading

0 comments on commit 05c0fa3

Please sign in to comment.