Skip to content

Commit

Permalink
fix(home-assistant): connect to broker correctly on init
Browse files Browse the repository at this point in the history
This allows the other integrations to register sensors on bootstrap already. Additionally it fixes some logging and code logic issues.
  • Loading branch information
mKeRix committed Feb 1, 2020
1 parent 9757052 commit 4ad5872
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 40 deletions.
42 changes: 22 additions & 20 deletions src/integrations/home-assistant/home-assistant.service.spec.ts
Expand Up @@ -31,7 +31,7 @@ jest.mock('systeminformation', () => {

describe('HomeAssistantService', () => {
let service: HomeAssistantService;
const emitter: EventEmitter = new EventEmitter();
let emitter: EventEmitter;
const mockMqtt = mocked(mqtt, true);
const mockSystem = mocked(system);
const loggerService = {
Expand All @@ -41,6 +41,7 @@ describe('HomeAssistantService', () => {
};

beforeEach(async () => {
emitter = new EventEmitter();
const module: TestingModule = await Test.createTestingModule({
imports: [NestEmitterModule.forRoot(emitter), ConfigModule],
providers: [HomeAssistantService]
Expand All @@ -52,10 +53,10 @@ describe('HomeAssistantService', () => {
service = module.get<HomeAssistantService>(HomeAssistantService);
});

it('should subscribe to entity events on init', () => {
it('should subscribe to entity events on init', async () => {
const emitterOnSpy = jest.spyOn(emitter, 'on');

service.onModuleInit();
await service.onModuleInit();
expect(emitterOnSpy).toHaveBeenCalledWith(
'newEntity',
expect.any(Function)
Expand All @@ -70,21 +71,22 @@ describe('HomeAssistantService', () => {
);
});

it('should connect to MQTT on bootstrap', async () => {
await service.onApplicationBootstrap();
it('should connect to MQTT on init', async () => {
await service.onModuleInit();
expect(mockMqtt.connectAsync).toHaveBeenCalledWith(
expect.any(String),
expect.any(Object)
expect.any(Object),
false
);
});

it('should get the device info on bootstrap', async () => {
await service.onApplicationBootstrap();
it('should get the device info on init', async () => {
await service.onModuleInit();
expect(mockSystem).toHaveBeenCalled();
});

it('should send offline messages for local entities on shutdown', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test', 'Test'));
service.handleNewEntity(new Sensor('test2', 'Test 2'));
mockMqttClient.publish.mockClear();
Expand All @@ -103,24 +105,24 @@ describe('HomeAssistantService', () => {
});

it('should not send offline messages for distributed entities', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('distributed', 'Dist', true));
mockMqttClient.publish.mockClear();

await service.onApplicationBootstrap();
await service.onModuleInit();

expect(mockMqttClient.publish).not.toHaveBeenCalled();
});

it('should terminate the MQTT connection gracefully on shutdown', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
await service.onApplicationShutdown();

expect(mockMqttClient.end).toHaveBeenCalled();
});

it('should publish discovery configuration for a new sensor', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test-sensor', 'Test Sensor'));

expect(mockMqttClient.publish).toHaveBeenCalledWith(
Expand Down Expand Up @@ -151,7 +153,7 @@ describe('HomeAssistantService', () => {
});

it('should publish discovery configuration for a new distributed sensor', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('dist-sensor', 'Dist Sensor', true));

expect(JSON.parse(mockMqttClient.publish.mock.calls[0][1])).toMatchObject({
Expand All @@ -174,7 +176,7 @@ describe('HomeAssistantService', () => {
manufacturer: 'Foundation'
} as SystemData);

await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('d6t-sensor', 'D6T Sensor'));

expect(JSON.parse(mockMqttClient.publish.mock.calls[0][1])).toMatchObject({
Expand All @@ -188,7 +190,7 @@ describe('HomeAssistantService', () => {
});

it('should apply sensor customizations to the discovery message', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('custom-sensor', 'Custom'), [
{
for: SensorConfig,
Expand All @@ -206,7 +208,7 @@ describe('HomeAssistantService', () => {
});

it('should exclude internal values from the discovery message', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test-sensor', 'Test Sensor'));

const configMsg = JSON.parse(mockMqttClient.publish.mock.calls[0][1]);
Expand Down Expand Up @@ -234,7 +236,7 @@ describe('HomeAssistantService', () => {
});

it('should publish state updates for entities', async () => {
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test', 'Test'));
service.handleNewState('test', 2);

Expand All @@ -255,7 +257,7 @@ describe('HomeAssistantService', () => {

it('should publish attribute updates for entities', async () => {
jest.useFakeTimers();
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test', 'Test'));
service.handleNewAttributes('test', { stateUpdated: true });
jest.runAllTimers();
Expand All @@ -268,7 +270,7 @@ describe('HomeAssistantService', () => {

it('should debounce the attribute updates', async () => {
jest.useFakeTimers();
await service.onApplicationBootstrap();
await service.onModuleInit();
service.handleNewEntity(new Sensor('test', 'Test'));
mockMqttClient.publish.mockClear();

Expand Down
39 changes: 19 additions & 20 deletions src/integrations/home-assistant/home-assistant.service.ts
@@ -1,7 +1,6 @@
import {
Injectable,
Logger,
OnApplicationBootstrap,
OnApplicationShutdown,
OnModuleInit
} from '@nestjs/common';
Expand All @@ -25,7 +24,7 @@ const PROPERTY_BLACKLIST = ['component', 'configTopic'];

@Injectable()
export class HomeAssistantService
implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown {
implements OnModuleInit, OnApplicationShutdown {
private config: HomeAssistantConfig;
private device: Device;
private entityConfigs: Map<string, EntityConfig> = new Map<
Expand All @@ -49,33 +48,33 @@ export class HomeAssistantService
/**
* Lifecycle hook, called once the host module has been initialized.
*/
onModuleInit(): void {
this.emitter.on('newEntity', this.handleNewEntity.bind(this));
this.emitter.on('stateUpdate', this.handleNewState.bind(this));
this.emitter.on('attributesUpdate', this.handleNewAttributes.bind(this));
}
async onModuleInit(): Promise<void> {
this.device = await this.getDeviceInfo();

/**
* Lifecycle hook, called once the application has started.
*/
async onApplicationBootstrap(): Promise<void> {
this.mqttClient = await mqtt.connectAsync(
this.config.mqttUrl,
this.config.mqttOptions
);
this.mqttClient.on('connect', () =>
this.logger.log(`Connected to ${this.config.mqttUrl}`)
);
try {
this.mqttClient = await mqtt.connectAsync(
this.config.mqttUrl,
this.config.mqttOptions,
false
);
this.logger.log(
`Successfully connected to MQTT broker at ${this.config.mqttUrl}`
);

this.device = await this.getDeviceInfo();
this.emitter.on('newEntity', this.handleNewEntity.bind(this));
this.emitter.on('stateUpdate', this.handleNewState.bind(this));
this.emitter.on('attributesUpdate', this.handleNewAttributes.bind(this));
} catch (e) {
this.logger.error(e);
}
}

/**
* Lifecycle hook, called once the application is shutting down.
*/
async onApplicationShutdown(): Promise<void> {
this.entityConfigs.forEach(config => {
if (config.device.identifiers !== DISTRIBUTED_DEVICE_ID) {
if (config.device?.identifiers !== DISTRIBUTED_DEVICE_ID) {
this.mqttClient.publish(
config.availabilityTopic,
config.payloadNotAvailable,
Expand Down

0 comments on commit 4ad5872

Please sign in to comment.