Skip to content

Commit

Permalink
feat: add verbose logging option to allow or suppress debug messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jvandenaardweg committed Dec 27, 2022
1 parent eb1f7c8 commit 450573d
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 56 deletions.
10 changes: 10 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
"required": false
}
}
},
"verboseLogging": {
"title": "Verbose Logging",
"type": "boolean",
"required": false,
"default": false,
"description": "If enabled, the plugin will log all debug messages. If everything works as expected, there's no need to enable this."
}
}
},
Expand All @@ -74,6 +81,9 @@
{
"key": "name",
"type": "name"
},
{
"key": "verboseLogging"
}
]
},
Expand Down
23 changes: 13 additions & 10 deletions src/air-quality-sensor-accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class AirQualitySensorAccessory {
username: this.config.api.username,
password: this.config.api.password,
logger: this.platform.log,
verboseLogging: this.config.verboseLogging,
});

this.mqttApiClient.subscribe(MQTT_STATUS_TOPIC);
Expand All @@ -49,6 +50,7 @@ export class AirQualitySensorAccessory {
username: this.config.api.username,
password: this.config.api.password,
logger: this.platform.log,
verboseLogging: this.config.verboseLogging,
});

// Only start polling if we're using the HTTP API
Expand All @@ -57,8 +59,8 @@ export class AirQualitySensorAccessory {

this.log.debug(`Starting polling for status...`);

this.httpApiClient.polling.getStatus.on('response', response => {
this.handleStatusResponse(response as IthoStatusSanitizedPayload); // TODO: fix type
this.httpApiClient.polling.getStatus.on('response.getStatus', response => {
this.handleStatusResponse(response as IthoStatusSanitizedPayload);
});
}

Expand Down Expand Up @@ -124,6 +126,7 @@ export class AirQualitySensorAccessory {
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...parameters: any[]) => {
if (!this.config.verboseLogging) return;
this.platform.log.debug(loggerPrefix, ...parameters);
},
};
Expand All @@ -138,7 +141,7 @@ export class AirQualitySensorAccessory {
const currentAirQualityName = this.getAirQualityName(currentValue as number);

if (currentValue === value) {
// this.log.debug(`AirQuality: Already set to: ${newAirQualityName}. Ignoring.`);
this.log.debug(`AirQuality: Already set to: ${newAirQualityName}. Ignoring.`);
return;
}

Expand All @@ -156,7 +159,7 @@ export class AirQualitySensorAccessory {
const roundedValue = Math.round(value);

if (currentValue === roundedValue) {
// this.log.debug(`CurrentRelativeHumidity: Already set to: ${value}. Ignoring.`);
this.log.debug(`CurrentRelativeHumidity: Already set to: ${value}. Ignoring.`);
return;
}

Expand All @@ -179,7 +182,7 @@ export class AirQualitySensorAccessory {
const parsedCurrentValue = parseFloat((currentValue as number).toFixed(1));

if (parsedCurrentValue === parsedValue) {
// this.log.debug(`CurrentTemperature: Already set to: ${parsedValue}. Ignoring.`);
this.log.debug(`CurrentTemperature: Already set to: ${parsedValue}. Ignoring.`);
return;
}

Expand All @@ -194,7 +197,7 @@ export class AirQualitySensorAccessory {
).value;

if (currentValue === value) {
// this.log.debug(`CarbonDioxideLevel: Already set to: ${value}. Ignoring.`);
this.log.debug(`CarbonDioxideLevel: Already set to: ${value}. Ignoring.`);
return;
}

Expand Down Expand Up @@ -223,7 +226,7 @@ export class AirQualitySensorAccessory {
}

handleMqttMessage(topic: string, message: Buffer): void {
// this.log.debug(`Received new status payload: ${message.toString()}`);
this.log.debug(`Received new status payload: ${message.toString()}`);

if (topic === MQTT_STATUS_TOPIC) {
const messageString = message.toString();
Expand All @@ -241,7 +244,7 @@ export class AirQualitySensorAccessory {
this.lastStatusPayload = data;
this.lastStatusPayloadTimestamp = Date.now();

// this.log.debug(`Parsed new status payload to: ${JSON.stringify(data)}`);
this.log.debug(`Parsed new status payload to: ${JSON.stringify(data)}`);

const airQuality = this.getAirQualityFromStatusPayload(data);
const currentRelativeHumidity = data.hum || 0;
Expand Down Expand Up @@ -274,7 +277,7 @@ export class AirQualitySensorAccessory {

const airQualityName = this.getAirQualityName(currentValue as number);

this.log.debug(`AirQuality is ${airQualityName} (${currentValue})`);
this.log.info(`AirQuality is ${airQualityName} (${currentValue})`);

return Promise.resolve(currentValue);
}
Expand All @@ -284,7 +287,7 @@ export class AirQualitySensorAccessory {
this.platform.Characteristic.StatusActive,
).value;

this.log.debug(`StatusActive is ${currentValue ? 'ACTIVE' : 'INACTIVE'} (${currentValue})`);
this.log.info(`StatusActive is ${currentValue ? 'ACTIVE' : 'INACTIVE'} (${currentValue})`);

return currentValue;
}
Expand Down
52 changes: 35 additions & 17 deletions src/api/http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IthoStatusPayload } from '@/types';
import { IthoGetSpeedResponse, IthoSetSpeedResponse, IthoStatusSanitizedPayload } from '@/types';
import { sanitizeStatusPayload } from '@/utils/api';
import EventEmitter from 'events';
import { Logger } from 'homebridge';
Expand All @@ -10,13 +10,15 @@ interface HttpApiOptions {
ip: string;
username?: string;
password?: string;
verboseLogging?: boolean;
logger: Logger;
}

export class HttpApi {
private readonly url: URL;
private readonly eventEmitter: EventEmitter;
private readonly log: Logger;
private readonly logger: Logger;
private readonly verboseLogging: boolean;
protected isPolling: Record<string, boolean> = {};

constructor(options: HttpApiOptions) {
Expand All @@ -32,22 +34,38 @@ export class HttpApi {

this.eventEmitter = new EventEmitter();

this.log = options.logger;
this.logger = options.logger;

this.verboseLogging = options.verboseLogging || false;
}

protected log(...args: unknown[]): void {
if (!this.logger) return;
if (!this.verboseLogging) return;

return this.logger.debug('[HTTP API] ->', ...args);
}

on<T>(event: 'response', listener: (response: T) => void): void;
on<T extends IthoStatusSanitizedPayload>(
event: 'response.getStatus',
listener: (response: T) => void,
): void;
on<T extends IthoGetSpeedResponse>(
event: 'response.getSpeed',
listener: (response: T) => void,
): void;
on(event: 'error', listener: (error: Error) => void): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event: string, listener: (...args: any[]) => void) {
return this.eventEmitter.on(event, listener);
}

async setSpeed<T extends number>(speed: number): Promise<T> {
async setSpeed<T extends IthoSetSpeedResponse>(speed: number): Promise<T> {
// Make a copy of the URL so we don't modify the original
const requestUrl = new URL(this.url.toString());
requestUrl.searchParams.set('speed', speed.toString());

this.log.debug(`[API] -> Setting speed to ${speed} at ${requestUrl}`);
this.log(`Setting speed to ${speed} at ${requestUrl}`);

const response = await request(requestUrl, {
method: 'GET',
Expand All @@ -64,12 +82,12 @@ export class HttpApi {
return speed as T;
}

async getSpeed<T extends number>(): Promise<T> {
async getSpeed<T extends IthoGetSpeedResponse>(): Promise<T> {
// Make a copy of the URL so we don't modify the original
const requestUrl = new URL(this.url.toString());
requestUrl.searchParams.set('get', 'currentspeed');

this.log.debug(`[API] -> Getting speed at ${requestUrl}`);
this.log(`Getting speed at ${requestUrl}`);

const response = await request(requestUrl, {
method: 'GET',
Expand All @@ -90,12 +108,12 @@ export class HttpApi {
return currentSpeed as T;
}

async getStatus<T extends IthoStatusPayload>(): Promise<T> {
async getStatus<T extends IthoStatusSanitizedPayload>(): Promise<T> {
// Make a copy of the URL so we don't modify the original
const requestUrl = new URL(this.url.toString());
requestUrl.searchParams.set('get', 'ithostatus');

this.log.debug(`[API] -> Getting status at ${requestUrl}`);
this.log(`Getting status at ${requestUrl}`);

const response = await request(requestUrl, {
method: 'GET',
Expand Down Expand Up @@ -140,13 +158,13 @@ export class HttpApi {
*/
protected stopPolling(method: string): void {
if (!this.isPolling[method]) {
this.log.debug(`Polling for "${method}" is not started or already stopped.`);
this.log(`Polling for "${method}" is not started or already stopped.`);
return;
}

this.isPolling[method] = false;

this.log.debug(`Stopping polling for "${method}".`);
this.log(`Stopping polling for "${method}".`);
}

protected async startPolling(method: string, apiMethod: () => Promise<unknown>): Promise<void> {
Expand All @@ -156,19 +174,19 @@ export class HttpApi {
try {
const response = await apiMethod();

this.log.debug(
this.log(
`Received response while polling "${method}". Emitting "response": ${JSON.stringify(
response,
)}`,
);

this.eventEmitter.emit('response', response);
this.eventEmitter.emit(`response.${method}`, response);
} catch (error) {
this.log.debug(`Received error while polling "${method}": ${JSON.stringify(error)}`);
this.log(`Received error while polling "${method}": ${JSON.stringify(error)}`);

this.eventEmitter.emit('error', error);
this.eventEmitter.emit(`error.${method}`, error);
} finally {
this.log.debug(`Waiting for next polling interval for "${method}"...`);
this.log(`Waiting for next polling interval for "${method}"...`);
await new Promise(resolve => setTimeout(resolve, DEFAULT_POLLING_INTERVAL));
}
}
Expand Down
19 changes: 15 additions & 4 deletions src/api/mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ interface MqttApiOptions {
port: number;
username?: string;
password?: string;
verboseLogging?: boolean;
logger: Logger;
}

export class MqttApi {
private readonly mqttApiClient: mqtt.Client;
private readonly log: Logger;
private readonly logger: Logger;
private readonly verboseLogging: boolean;

constructor(options: MqttApiOptions) {
this.mqttApiClient = mqtt.connect({
Expand All @@ -25,7 +27,16 @@ export class MqttApi {
this.mqttApiClient.on('connect', this.handleMqttConnect.bind(this));
this.mqttApiClient.on('error', this.handleMqttError.bind(this));

this.log = options.logger;
this.logger = options.logger;

this.verboseLogging = options.verboseLogging || false;
}

protected log(...args: unknown[]): void {
if (!this.logger) return;
if (!this.verboseLogging) return;

return this.logger.debug('[MQTT API] ->', ...args);
}

subscribe(topic: string | string[]): mqtt.Client {
Expand All @@ -41,10 +52,10 @@ export class MqttApi {
}

handleMqttConnect(packet: mqtt.IConnackPacket) {
this.log.debug(`MQTT connect: ${JSON.stringify(packet)}`);
this.log(`MQTT connect: ${JSON.stringify(packet)}`);
}

handleMqttError(error: Error) {
this.log.debug(`MQTT error: ${JSON.stringify(error)}`);
this.log(`MQTT error: ${JSON.stringify(error)}`);
}
}
9 changes: 9 additions & 0 deletions src/config.schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { DEFAULT_BRIDGE_NAME, PLATFORM_NAME } from './settings';
describe('config.schema.json', () => {
it('should have the correct name property', async () => {
const nameProperty: keyof ConfigSchema = 'name';
const apiProperty: keyof ConfigSchema = 'api';
const verboseLoggingProperty: keyof ConfigSchema = 'verboseLogging';

const properties = configSchemaJson.schema.properties;

Expand All @@ -17,8 +19,15 @@ describe('config.schema.json', () => {
expect(properties.name.type).toBe('string');
expect(properties.name.default).toBe(DEFAULT_BRIDGE_NAME);

expect(properties).toHaveProperty(verboseLoggingProperty);
expect(properties.verboseLogging).toHaveProperty('required');
expect(properties.verboseLogging.required).toBe(false);
expect(properties.verboseLogging.type).toBe('boolean');
expect(properties.verboseLogging.default).toBe(false);

const apiProperties = properties.api.properties;

expect(properties).toHaveProperty(apiProperty);
expect(apiProperties.protocol).toHaveProperty('required');
expect(apiProperties.protocol.required).toBe(true);
expect(apiProperties.protocol.type).toBe('string');
Expand Down
19 changes: 5 additions & 14 deletions src/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@ import { z } from 'zod';
import isIP from 'validator/lib/isIP';
import { PlatformConfig } from 'homebridge';

// const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
// if (issue.code === z.ZodIssueCode.invalid_type) {
// if (issue.expected === 'string') {
// return { message: 'bad type!' };
// }
// }
// if (issue.code === z.ZodIssueCode.custom) {
// return { message: `less-than-${(issue.params || {}).minimum}` };
// }
// return { message: ctx.defaultError };
// };

// z.setErrorMap(customErrorMap);

// this schema should match the config.schema.json
// using zod to validate the config gives us type-safety over the config in our code
export const configSchema = z.object({
Expand Down Expand Up @@ -50,6 +36,11 @@ export const configSchema = z.object({
})
.optional(),
}),
verboseLogging: z
.boolean({
invalid_type_error: "'verboseLogging' must be a boolean",
})
.optional(),
});

export type ConfigSchema = z.infer<typeof configSchema> & PlatformConfig;
Loading

0 comments on commit 450573d

Please sign in to comment.