v2c-any 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/factory/mqtt-service-factory.js +7 -5
- package/dist/index.js +5 -7
- package/dist/service/abstract-executable-service.js +48 -0
- package/dist/service/mqtt-bridge-service.js +7 -7
- package/dist/service/mqtt-service.js +6 -13
- package/dist/service/no-op-executable-service.js +4 -3
- package/dist/service/pull-push-service.js +5 -3
- package/dist/service/rest-service.js +9 -5
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AbstractExecutableService } from '../service/abstract-executable-service.js';
|
|
1
2
|
import { MqttService } from '../service/mqtt-service.js';
|
|
2
3
|
/**
|
|
3
4
|
* Factory for creating an MQTT service that manages energy publishing.
|
|
@@ -45,17 +46,18 @@ export class MqttServiceFactory {
|
|
|
45
46
|
energyType: 'solar',
|
|
46
47
|
callbacks,
|
|
47
48
|
});
|
|
48
|
-
|
|
49
|
-
async
|
|
49
|
+
const mqttExecutableService = class extends AbstractExecutableService {
|
|
50
|
+
async doStart() {
|
|
50
51
|
await mqttService.start();
|
|
51
52
|
await gridEnergyPublisher.start();
|
|
52
53
|
await sunEnergyPublisher.start();
|
|
53
|
-
}
|
|
54
|
-
async
|
|
54
|
+
}
|
|
55
|
+
async doStop() {
|
|
55
56
|
await sunEnergyPublisher.stop();
|
|
56
57
|
await gridEnergyPublisher.stop();
|
|
57
58
|
await mqttService.stop();
|
|
58
|
-
}
|
|
59
|
+
}
|
|
59
60
|
};
|
|
61
|
+
return new mqttExecutableService();
|
|
60
62
|
}
|
|
61
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,29 +10,27 @@ import { MqttPushExecutableServiceFactory } from './factory/mqtt-push-executable
|
|
|
10
10
|
import { RestServiceFactory } from './factory/rest-service-factory.js';
|
|
11
11
|
import { MqttServiceFactory } from './factory/mqtt-service-factory.js';
|
|
12
12
|
async function main() {
|
|
13
|
-
let service = null;
|
|
14
13
|
await loadDeviceModules();
|
|
15
14
|
const configurationValidator = new ConfigurationValidator();
|
|
16
15
|
const configurationLoader = new ConfigurationLoader(configurationValidator);
|
|
17
16
|
const configuration = await configurationLoader.load();
|
|
17
|
+
let executableServiceFactory;
|
|
18
18
|
switch (configuration.provider) {
|
|
19
19
|
case 'rest': {
|
|
20
|
-
|
|
21
|
-
service = emulatorFactory.create(configuration);
|
|
20
|
+
executableServiceFactory = new RestServiceFactory(new EM1StatusProviderFactory(devicesProviderRegistry));
|
|
22
21
|
break;
|
|
23
22
|
}
|
|
24
23
|
case 'mqtt': {
|
|
25
24
|
const mqttFeedExecutableServiceFactory = new MqttFeedExecutableServiceFactory(new MqttPullExecutableServiceFactory(devicesProviderRegistry, devicesAdapterRegistry), new MqttPushExecutableServiceFactory(devicesAdapterRegistry));
|
|
26
|
-
|
|
27
|
-
service = mqttFactory.create(configuration);
|
|
25
|
+
executableServiceFactory = new MqttServiceFactory(mqttFeedExecutableServiceFactory);
|
|
28
26
|
break;
|
|
29
27
|
}
|
|
30
28
|
}
|
|
29
|
+
const service = executableServiceFactory.create(configuration);
|
|
31
30
|
const shutdown = async () => {
|
|
32
31
|
try {
|
|
33
32
|
logger.info('Shutting down...');
|
|
34
|
-
|
|
35
|
-
await service.stop();
|
|
33
|
+
await service?.stop();
|
|
36
34
|
logger.info('Shutdown complete');
|
|
37
35
|
}
|
|
38
36
|
catch (err) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { logger } from '../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for executable services.
|
|
4
|
+
* Provides idempotent start/stop lifecycle management with state tracking.
|
|
5
|
+
* Subclasses implement the actual service logic via doStart() and doStop() hooks.
|
|
6
|
+
*/
|
|
7
|
+
export class AbstractExecutableService {
|
|
8
|
+
constructor() {
|
|
9
|
+
/**
|
|
10
|
+
* Internal state flag tracking whether the service is currently started.
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
this._started = false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns whether the service is currently started.
|
|
17
|
+
* @returns true if the service has been started and not yet stopped
|
|
18
|
+
*/
|
|
19
|
+
get started() {
|
|
20
|
+
return this._started;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Starts the service.
|
|
24
|
+
* Idempotent: calling start() on an already-started service is a no-op with a warning.
|
|
25
|
+
* @returns A promise that resolves when the service has successfully started
|
|
26
|
+
*/
|
|
27
|
+
async start() {
|
|
28
|
+
if (this._started) {
|
|
29
|
+
logger.warn(`Service is already started`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await this.doStart();
|
|
33
|
+
this._started = true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stops the service.
|
|
37
|
+
* Idempotent: calling stop() on an already-stopped service is a no-op with a warning.
|
|
38
|
+
* @returns A promise that resolves when the service has successfully stopped
|
|
39
|
+
*/
|
|
40
|
+
async stop() {
|
|
41
|
+
if (!this._started) {
|
|
42
|
+
logger.warn(`Service is not started`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await this.doStop();
|
|
46
|
+
this._started = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
2
|
import { createMqttClient } from '../utils/mqtt.js';
|
|
3
|
+
import { AbstractExecutableService } from './abstract-executable-service.js';
|
|
3
4
|
/**
|
|
4
5
|
* Service that bridges MQTT messages to a typed callback via an adapter.
|
|
5
6
|
* Subscribes to a topic, parses incoming messages, adapts them, and forwards
|
|
@@ -8,7 +9,7 @@ import { createMqttClient } from '../utils/mqtt.js';
|
|
|
8
9
|
* @template InputMessage - The raw message type received from MQTT
|
|
9
10
|
* @template Payload - The adapted payload type passed to the callback
|
|
10
11
|
*/
|
|
11
|
-
export class MqttBridgeService {
|
|
12
|
+
export class MqttBridgeService extends AbstractExecutableService {
|
|
12
13
|
/**
|
|
13
14
|
* Creates a new MQTT bridge service.
|
|
14
15
|
* @param properties - MQTT connection and subscription details
|
|
@@ -16,6 +17,7 @@ export class MqttBridgeService {
|
|
|
16
17
|
* @param adapter - Adapter that transforms raw MQTT messages to the payload type
|
|
17
18
|
*/
|
|
18
19
|
constructor(properties, callbackProperties, adapter) {
|
|
20
|
+
super();
|
|
19
21
|
this.properties = properties;
|
|
20
22
|
this.callbackProperties = callbackProperties;
|
|
21
23
|
this.adapter = adapter;
|
|
@@ -25,7 +27,7 @@ export class MqttBridgeService {
|
|
|
25
27
|
* Starts the bridge: connects to the broker, subscribes, and wires message handling.
|
|
26
28
|
* @returns A promise that resolves when the subscription is active
|
|
27
29
|
*/
|
|
28
|
-
async
|
|
30
|
+
async doStart() {
|
|
29
31
|
logger.info('Starting MQTT bridge service');
|
|
30
32
|
this.client = await createMqttClient(this.properties.url);
|
|
31
33
|
this.client.on('message', (topic, message) => {
|
|
@@ -49,12 +51,10 @@ export class MqttBridgeService {
|
|
|
49
51
|
* Stops the bridge: disconnects the MQTT client and clears resources.
|
|
50
52
|
* @returns A promise that resolves when the client has disconnected
|
|
51
53
|
*/
|
|
52
|
-
async
|
|
54
|
+
async doStop() {
|
|
53
55
|
logger.info('Stopping MQTT bridge service');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.client = null;
|
|
57
|
-
}
|
|
56
|
+
await this.client?.endAsync();
|
|
57
|
+
this.client = null;
|
|
58
58
|
logger.info('MQTT bridge service stopped');
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
2
|
import { createMqttClient } from '../utils/mqtt.js';
|
|
3
|
+
import { AbstractExecutableService } from './abstract-executable-service.js';
|
|
3
4
|
/**
|
|
4
5
|
* MQTT topic for publishing solar power readings.
|
|
5
6
|
*/
|
|
@@ -12,12 +13,13 @@ const GRID_POWER_TOPIC = 'trydan_v2c_grid_power';
|
|
|
12
13
|
* Service that publishes energy readings to an MQTT broker.
|
|
13
14
|
* Manages MQTT client lifecycle and exposes methods to push grid and solar power values.
|
|
14
15
|
*/
|
|
15
|
-
export class MqttService {
|
|
16
|
+
export class MqttService extends AbstractExecutableService {
|
|
16
17
|
/**
|
|
17
18
|
* Creates a new MQTT service.
|
|
18
19
|
* @param properties - MQTT connection properties including broker URL
|
|
19
20
|
*/
|
|
20
21
|
constructor(properties) {
|
|
22
|
+
super();
|
|
21
23
|
this.properties = properties;
|
|
22
24
|
this.client = null;
|
|
23
25
|
}
|
|
@@ -53,12 +55,8 @@ export class MqttService {
|
|
|
53
55
|
* @returns A promise that resolves when the client is connected
|
|
54
56
|
* @throws {Error} If the client is already started
|
|
55
57
|
*/
|
|
56
|
-
async
|
|
58
|
+
async doStart() {
|
|
57
59
|
logger.info('Starting MQTT mode');
|
|
58
|
-
if (this.client) {
|
|
59
|
-
logger.error('MQTT client already started');
|
|
60
|
-
throw new Error('MQTT client already started');
|
|
61
|
-
}
|
|
62
60
|
this.client = await createMqttClient(this.properties.url);
|
|
63
61
|
logger.info('MQTT client started');
|
|
64
62
|
}
|
|
@@ -67,15 +65,10 @@ export class MqttService {
|
|
|
67
65
|
* @returns A promise that resolves when the client is disconnected
|
|
68
66
|
* @throws {Error} If the client is not started
|
|
69
67
|
*/
|
|
70
|
-
async
|
|
68
|
+
async doStop() {
|
|
71
69
|
logger.info('Stopping MQTT mode');
|
|
72
|
-
|
|
70
|
+
await this.client?.endAsync();
|
|
73
71
|
this.client = null;
|
|
74
|
-
if (!client) {
|
|
75
|
-
logger.error('MQTT client not started');
|
|
76
|
-
throw new Error('MQTT client not started');
|
|
77
|
-
}
|
|
78
|
-
await client.endAsync();
|
|
79
72
|
logger.info('MQTT client disconnected');
|
|
80
73
|
}
|
|
81
74
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import { AbstractExecutableService } from './abstract-executable-service.js';
|
|
1
2
|
/**
|
|
2
3
|
* A no-operation implementation of ExecutableService.
|
|
3
4
|
* Used as a null-object pattern implementation when no actual service execution is needed.
|
|
4
5
|
*/
|
|
5
|
-
class NoOpExecutableService {
|
|
6
|
+
class NoOpExecutableService extends AbstractExecutableService {
|
|
6
7
|
/**
|
|
7
8
|
* Starts the service (no-op implementation).
|
|
8
9
|
* @returns An immediately resolved promise
|
|
9
10
|
*/
|
|
10
|
-
async
|
|
11
|
+
async doStart() {
|
|
11
12
|
// no-op
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Stops the service (no-op implementation).
|
|
15
16
|
* @returns An immediately resolved promise
|
|
16
17
|
*/
|
|
17
|
-
async
|
|
18
|
+
async doStop() {
|
|
18
19
|
// no-op
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
|
+
import { AbstractExecutableService } from './abstract-executable-service.js';
|
|
2
3
|
/**
|
|
3
4
|
* Periodic pull-then-push service.
|
|
4
5
|
* Fetches data from a `Provider` at a fixed interval and forwards it via a callback.
|
|
@@ -6,7 +7,7 @@ import { logger } from '../utils/logger.js';
|
|
|
6
7
|
*
|
|
7
8
|
* @template Payload - The type of data provided and pushed
|
|
8
9
|
*/
|
|
9
|
-
export class PullPushService {
|
|
10
|
+
export class PullPushService extends AbstractExecutableService {
|
|
10
11
|
/**
|
|
11
12
|
* Creates a new pull/push service.
|
|
12
13
|
* @param provider - Source `Provider` that supplies data
|
|
@@ -14,6 +15,7 @@ export class PullPushService {
|
|
|
14
15
|
* @param callbackProperties - Callback container invoked with fetched data
|
|
15
16
|
*/
|
|
16
17
|
constructor(provider, intervalMs, callbackProperties) {
|
|
18
|
+
super();
|
|
17
19
|
this.provider = provider;
|
|
18
20
|
this.intervalMs = intervalMs;
|
|
19
21
|
this.callbackProperties = callbackProperties;
|
|
@@ -24,7 +26,7 @@ export class PullPushService {
|
|
|
24
26
|
* @returns A promise that resolves once the service starts
|
|
25
27
|
* @throws {Error} If the service is already started
|
|
26
28
|
*/
|
|
27
|
-
|
|
29
|
+
doStart() {
|
|
28
30
|
if (this.abortController) {
|
|
29
31
|
throw new Error('Adapter already started');
|
|
30
32
|
}
|
|
@@ -37,7 +39,7 @@ export class PullPushService {
|
|
|
37
39
|
* Stops periodic polling if running.
|
|
38
40
|
* @returns A promise that resolves once the service stops
|
|
39
41
|
*/
|
|
40
|
-
|
|
42
|
+
doStop() {
|
|
41
43
|
this.abortController?.abort();
|
|
42
44
|
this.abortController = null;
|
|
43
45
|
return Promise.resolve();
|
|
@@ -4,13 +4,15 @@ import { expectationBodySchema } from '../schema/expectation-body.js';
|
|
|
4
4
|
import { statusQuerySchema } from '../schema/status-query.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
6
|
import { FixedValueProvider } from '../provider/fixed-value-provider.js';
|
|
7
|
+
import { AbstractExecutableService } from './abstract-executable-service.js';
|
|
7
8
|
/**
|
|
8
9
|
* REST service that exposes Shelly EM1-like endpoints for energy status.
|
|
9
10
|
* Provides health checks, mock expectation updates, and status queries for grid and solar.
|
|
10
11
|
* Implements the executable service lifecycle to start and stop the HTTP server.
|
|
11
12
|
*/
|
|
12
|
-
export class RestService {
|
|
13
|
+
export class RestService extends AbstractExecutableService {
|
|
13
14
|
constructor(gridEnergyProvider, solarEnergyProvider, properties) {
|
|
15
|
+
super();
|
|
14
16
|
this.gridEnergyProvider = gridEnergyProvider;
|
|
15
17
|
this.solarEnergyProvider = solarEnergyProvider;
|
|
16
18
|
this.properties = properties;
|
|
@@ -41,7 +43,8 @@ export class RestService {
|
|
|
41
43
|
* - `GET /rpc/EM1.GetStatus` fetch status for a given id
|
|
42
44
|
* @returns A promise that resolves when the server is listening
|
|
43
45
|
*/
|
|
44
|
-
async
|
|
46
|
+
async doStart() {
|
|
47
|
+
logger.info('Starting REST service...');
|
|
45
48
|
const app = Fastify({
|
|
46
49
|
loggerInstance: logger,
|
|
47
50
|
disableRequestLogging: true,
|
|
@@ -92,16 +95,17 @@ export class RestService {
|
|
|
92
95
|
//app.post('/rpc/EM1.GetStatus', async () => app.inject({ method: 'GET', url: '/rpc/EM1.GetStatus' }).then(r => r.json()));
|
|
93
96
|
await app.listen({ port: this.properties.port, host: '0.0.0.0' });
|
|
94
97
|
logger.info({ port: this.properties.port }, 'Listening');
|
|
98
|
+
logger.info('REST service started');
|
|
95
99
|
}
|
|
96
100
|
/**
|
|
97
101
|
* Stops the REST server if running.
|
|
98
102
|
* @returns A promise that resolves when the server has closed
|
|
99
103
|
*/
|
|
100
|
-
async
|
|
101
|
-
logger.info('Stopping
|
|
104
|
+
async doStop() {
|
|
105
|
+
logger.info('Stopping REST service...');
|
|
102
106
|
if (this.app) {
|
|
103
107
|
await this.app.close();
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
logger.info('REST service stopped');
|
|
106
110
|
}
|
|
107
111
|
}
|