Improve consumers implementation
This commit is contained in:
30
src/consumers/abstract.ts
Normal file
30
src/consumers/abstract.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Dayjs } from "dayjs";
|
||||||
|
import { Config } from "../config";
|
||||||
|
import { Measurement } from "../fetcher/types";
|
||||||
|
import { ConsumerConfig } from "./types";
|
||||||
|
|
||||||
|
export abstract class Consumer<C extends ConsumerConfig> {
|
||||||
|
public abstract readonly name: string;
|
||||||
|
protected abstract requiredFields: readonly (keyof C)[];
|
||||||
|
protected abstract publish(config: C, date: Dayjs, measurement: Measurement): void;
|
||||||
|
|
||||||
|
#validate(config: Partial<C>): C {
|
||||||
|
for (const field of this.requiredFields) {
|
||||||
|
if (config[field] === undefined) {
|
||||||
|
throw new Error(`The '${String(field)}' configuration field of'${this.name}' consumer is required`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config as C;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(config: Config, date: Dayjs, measurement: Measurement): void {
|
||||||
|
const cfg = config.consumers[this.name] as Partial<C>;
|
||||||
|
|
||||||
|
if (cfg.enable !== true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.publish(this.#validate(cfg), date, measurement);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,9 +3,18 @@ import { Config } from "../config";
|
|||||||
import { MQTTConsumer } from "./mqtt";
|
import { MQTTConsumer } from "./mqtt";
|
||||||
import { Measurement } from "../fetcher/types";
|
import { Measurement } from "../fetcher/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered consumers
|
||||||
|
*/
|
||||||
const consumers = [
|
const consumers = [
|
||||||
new MQTTConsumer()
|
new MQTTConsumer()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs all registered consumers for the measurement.
|
||||||
|
* @param config global configuration
|
||||||
|
* @param date date of measurement
|
||||||
|
* @param measurement measurement data
|
||||||
|
*/
|
||||||
export const consume = (config: Config, date: Dayjs, measurement: Measurement) =>
|
export const consume = (config: Config, date: Dayjs, measurement: Measurement) =>
|
||||||
consumers.forEach(consumer => consumer.consume(config, date, measurement));
|
consumers.forEach(consumer => consumer.consume(config, date, measurement));
|
||||||
31
src/consumers/mqtt/index.ts
Normal file
31
src/consumers/mqtt/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import mqtt from "mqtt";
|
||||||
|
import { Measurement } from "../../fetcher/types";
|
||||||
|
import { Dayjs } from 'dayjs';
|
||||||
|
import { MQTTConfig } from './types';
|
||||||
|
import { Consumer } from '../abstract';
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
export class MQTTConsumer extends Consumer<MQTTConfig> {
|
||||||
|
public name = "mqtt";
|
||||||
|
protected requiredFields = ['brokerURL', 'usernamePath', 'passwordPath'] as const;
|
||||||
|
|
||||||
|
protected publish({ brokerURL, usernamePath, passwordPath, clientId, prefix, publishOptions }: MQTTConfig, date: Dayjs, measurement: Measurement) {
|
||||||
|
const client = mqtt.connect(brokerURL, {
|
||||||
|
username: fs.readFileSync(usernamePath, 'utf8'),
|
||||||
|
password: fs.readFileSync(passwordPath, 'utf8'),
|
||||||
|
clientId: clientId || "tauron-scrapper"
|
||||||
|
});
|
||||||
|
|
||||||
|
const topicPrefix = prefix || 'tauron';
|
||||||
|
|
||||||
|
client.on('connect', () => {
|
||||||
|
client.publish(`${topicPrefix}/energy/daily`, measurement.energyForDay.sum.toString(), publishOptions);
|
||||||
|
client.publish(`${topicPrefix}/energy/monthly`, measurement.energyForMonth.sum.toString(), publishOptions);
|
||||||
|
client.publish(`${topicPrefix}/energy/yearly`, measurement.energyForYear.sum.toString(), publishOptions);
|
||||||
|
client.publish(`${topicPrefix}/reading`, measurement.reading.C.toString(), publishOptions);
|
||||||
|
client.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/consumers/mqtt/types.ts
Normal file
38
src/consumers/mqtt/types.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import mqtt from "mqtt";
|
||||||
|
import { ConsumerConfig } from "../types";
|
||||||
|
|
||||||
|
export type MQTTConfig = ConsumerConfig & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL to broker.
|
||||||
|
* Example: mqtt://localhost:1882
|
||||||
|
*/
|
||||||
|
brokerURL: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to file containing a username of MQTT user.
|
||||||
|
*/
|
||||||
|
usernamePath: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to file containing a password of MQTT user.
|
||||||
|
*/
|
||||||
|
passwordPath: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional client ID used to connect to MQTT (visible in MQTT broker logs).
|
||||||
|
* Default: tauron-scrapper
|
||||||
|
*/
|
||||||
|
clientId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional prefix under which the messages will be published.
|
||||||
|
* Default: tauron
|
||||||
|
*/
|
||||||
|
prefix?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional configuration of published messages (i.e. QoS, whether the message should be retained etc.).
|
||||||
|
*/
|
||||||
|
publishOptions?: mqtt.IClientPublishOptions;
|
||||||
|
}
|
||||||
@@ -1,33 +1,7 @@
|
|||||||
import { Dayjs } from "dayjs";
|
|
||||||
import { Measurement } from "../fetcher/types";
|
|
||||||
import { Config } from "../config";
|
|
||||||
|
|
||||||
export type ConsumerConfig = {
|
export type ConsumerConfig = {
|
||||||
|
/**
|
||||||
|
* Whether given consumer should be invoked.
|
||||||
|
* Default: false
|
||||||
|
*/
|
||||||
enable?: boolean;
|
enable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Consumer<C extends ConsumerConfig> {
|
|
||||||
public abstract readonly name: string;
|
|
||||||
protected abstract requiredFields: readonly (keyof C)[];
|
|
||||||
protected abstract publish(config: C, date: Dayjs, measurement: Measurement): void;
|
|
||||||
|
|
||||||
#validate(config: Partial<C>): C {
|
|
||||||
for (const field of this.requiredFields) {
|
|
||||||
if (config[field] === undefined) {
|
|
||||||
throw new Error(`The '${String(field)}' configuration field of'${this.name}' consumer is required`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config as C;
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(config: Config, date: Dayjs, measurement: Measurement): void {
|
|
||||||
const cfg = config.consumers[this.name] as Partial<C>;
|
|
||||||
|
|
||||||
if (cfg.enable !== true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.publish(this.#validate(cfg), date, measurement);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user