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
@@ -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
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
@@ -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
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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;
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
]
};
}
}
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.
@@ -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
@@ -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]
};
}
}

0 comments on commit 05c0fa3

Please sign in to comment.