homebridge-evn-smartmeter 1.0.0
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/README.md +222 -0
- package/config.schema.json +118 -0
- package/dist/accessories/currentMonitorAccessory.d.ts +29 -0
- package/dist/accessories/currentMonitorAccessory.js +86 -0
- package/dist/accessories/energyMeterAccessory.d.ts +27 -0
- package/dist/accessories/energyMeterAccessory.js +75 -0
- package/dist/accessories/powerMonitorAccessory.d.ts +27 -0
- package/dist/accessories/powerMonitorAccessory.js +75 -0
- package/dist/accessories/voltageMonitorAccessory.d.ts +27 -0
- package/dist/accessories/voltageMonitorAccessory.js +75 -0
- package/dist/config/defaults.d.ts +4 -0
- package/dist/config/defaults.js +11 -0
- package/dist/config/types.d.ts +33 -0
- package/dist/config/types.js +18 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mqtt/mqttClient.d.ts +56 -0
- package/dist/mqtt/mqttClient.js +126 -0
- package/dist/mqtt/topicParser.d.ts +8 -0
- package/dist/mqtt/topicParser.js +25 -0
- package/dist/platform.d.ts +42 -0
- package/dist/platform.js +146 -0
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +11 -0
- package/homebridge-evn-smartmeter/README.md +222 -0
- package/homebridge-evn-smartmeter/config.schema.json +118 -0
- package/homebridge-evn-smartmeter/package-lock.json +4090 -0
- package/homebridge-evn-smartmeter/package.json +51 -0
- package/package.json +51 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VoltageMonitorAccessory = void 0;
|
|
4
|
+
const topicParser_1 = require("../mqtt/topicParser");
|
|
5
|
+
const types_1 = require("../config/types");
|
|
6
|
+
/**
|
|
7
|
+
* Voltage Monitor Accessory
|
|
8
|
+
* Displays voltage measurements as temperature sensors (°C = Volt)
|
|
9
|
+
*/
|
|
10
|
+
class VoltageMonitorAccessory {
|
|
11
|
+
constructor(platform, accessory, mqttClient) {
|
|
12
|
+
this.platform = platform;
|
|
13
|
+
this.accessory = accessory;
|
|
14
|
+
this.mqttClient = mqttClient;
|
|
15
|
+
this.services = [];
|
|
16
|
+
this.cachedValues = new Map();
|
|
17
|
+
// Set accessory information
|
|
18
|
+
this.accessory
|
|
19
|
+
.getService(this.platform.Service.AccessoryInformation)
|
|
20
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'EVN')
|
|
21
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Kaifa Smartmeter - Voltage Monitor')
|
|
22
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'VOL-001');
|
|
23
|
+
// Create temperature sensors for voltage measurements
|
|
24
|
+
this.createSensor('Voltage L1 (V)', types_1.MeasurementType.SpannungL1);
|
|
25
|
+
this.createSensor('Voltage L2 (V)', types_1.MeasurementType.SpannungL2);
|
|
26
|
+
this.createSensor('Voltage L3 (V)', types_1.MeasurementType.SpannungL3);
|
|
27
|
+
// Subscribe to MQTT measurements
|
|
28
|
+
this.mqttClient.on('measurement', this.handleMeasurement.bind(this));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a temperature sensor for a voltage measurement
|
|
32
|
+
*/
|
|
33
|
+
createSensor(name, type) {
|
|
34
|
+
const service = this.accessory.getService(name) ||
|
|
35
|
+
this.accessory.addService(this.platform.Service.TemperatureSensor, name, type);
|
|
36
|
+
service
|
|
37
|
+
.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
|
|
38
|
+
.setProps({
|
|
39
|
+
minValue: 0,
|
|
40
|
+
maxValue: 300,
|
|
41
|
+
minStep: 0.1,
|
|
42
|
+
})
|
|
43
|
+
.onGet(() => this.getValue(type));
|
|
44
|
+
this.services.push(service);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the current value for a measurement type
|
|
48
|
+
*/
|
|
49
|
+
getValue(type) {
|
|
50
|
+
return this.cachedValues.get(type) ?? 0;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Handle incoming MQTT measurement
|
|
54
|
+
*/
|
|
55
|
+
handleMeasurement(topic, value) {
|
|
56
|
+
const measurementType = (0, topicParser_1.parseTopic)(topic, this.platform.config.mqtt.topic_prefix || 'Smartmeter');
|
|
57
|
+
if (!measurementType) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Check if this measurement belongs to this accessory
|
|
61
|
+
if (measurementType !== types_1.MeasurementType.SpannungL1 &&
|
|
62
|
+
measurementType !== types_1.MeasurementType.SpannungL2 &&
|
|
63
|
+
measurementType !== types_1.MeasurementType.SpannungL3) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Update cache
|
|
67
|
+
this.cachedValues.set(measurementType, value);
|
|
68
|
+
// Update HomeKit characteristic
|
|
69
|
+
const service = this.services.find((s) => s.subtype === measurementType);
|
|
70
|
+
if (service) {
|
|
71
|
+
service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).updateValue(value);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.VoltageMonitorAccessory = VoltageMonitorAccessory;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_DISPLAY_CONFIG = exports.DEFAULT_TOPIC_PREFIX = exports.DEFAULT_MQTT_PORT = void 0;
|
|
4
|
+
exports.DEFAULT_MQTT_PORT = 1883;
|
|
5
|
+
exports.DEFAULT_TOPIC_PREFIX = 'Smartmeter';
|
|
6
|
+
exports.DEFAULT_DISPLAY_CONFIG = {
|
|
7
|
+
showPowerMonitor: true,
|
|
8
|
+
showEnergyMeter: true,
|
|
9
|
+
showVoltageMonitor: true,
|
|
10
|
+
showCurrentMonitor: true,
|
|
11
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface EVNSmartmeterConfig {
|
|
2
|
+
platform: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
mqtt: MQTTConfig;
|
|
5
|
+
display?: DisplayConfig;
|
|
6
|
+
}
|
|
7
|
+
export interface MQTTConfig {
|
|
8
|
+
broker: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
username?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
topic_prefix?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DisplayConfig {
|
|
15
|
+
showPowerMonitor?: boolean;
|
|
16
|
+
showEnergyMeter?: boolean;
|
|
17
|
+
showVoltageMonitor?: boolean;
|
|
18
|
+
showCurrentMonitor?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare enum MeasurementType {
|
|
21
|
+
WirkenergieP = "WirkenergieP",
|
|
22
|
+
WirkenergieN = "WirkenergieN",
|
|
23
|
+
MomentanleistungP = "MomentanleistungP",
|
|
24
|
+
MomentanleistungN = "MomentanleistungN",
|
|
25
|
+
Momentanleistung = "Momentanleistung",
|
|
26
|
+
SpannungL1 = "SpannungL1",
|
|
27
|
+
SpannungL2 = "SpannungL2",
|
|
28
|
+
SpannungL3 = "SpannungL3",
|
|
29
|
+
StromL1 = "StromL1",
|
|
30
|
+
StromL2 = "StromL2",
|
|
31
|
+
StromL3 = "StromL3",
|
|
32
|
+
Leistungsfaktor = "Leistungsfaktor"
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MeasurementType = void 0;
|
|
4
|
+
var MeasurementType;
|
|
5
|
+
(function (MeasurementType) {
|
|
6
|
+
MeasurementType["WirkenergieP"] = "WirkenergieP";
|
|
7
|
+
MeasurementType["WirkenergieN"] = "WirkenergieN";
|
|
8
|
+
MeasurementType["MomentanleistungP"] = "MomentanleistungP";
|
|
9
|
+
MeasurementType["MomentanleistungN"] = "MomentanleistungN";
|
|
10
|
+
MeasurementType["Momentanleistung"] = "Momentanleistung";
|
|
11
|
+
MeasurementType["SpannungL1"] = "SpannungL1";
|
|
12
|
+
MeasurementType["SpannungL2"] = "SpannungL2";
|
|
13
|
+
MeasurementType["SpannungL3"] = "SpannungL3";
|
|
14
|
+
MeasurementType["StromL1"] = "StromL1";
|
|
15
|
+
MeasurementType["StromL2"] = "StromL2";
|
|
16
|
+
MeasurementType["StromL3"] = "StromL3";
|
|
17
|
+
MeasurementType["Leistungsfaktor"] = "Leistungsfaktor";
|
|
18
|
+
})(MeasurementType || (exports.MeasurementType = MeasurementType = {}));
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
/**
|
|
3
|
+
* MQTT client manager with auto-reconnect
|
|
4
|
+
* Emits events:
|
|
5
|
+
* - 'connected': When successfully connected to broker
|
|
6
|
+
* - 'measurement': (topic: string, value: number) - When a valid measurement is received
|
|
7
|
+
* - 'error': (error: Error) - On MQTT errors
|
|
8
|
+
* - 'offline': When connection is lost
|
|
9
|
+
*/
|
|
10
|
+
export declare class EVNMQTTClient extends EventEmitter {
|
|
11
|
+
private broker;
|
|
12
|
+
private port;
|
|
13
|
+
private topicPrefix;
|
|
14
|
+
private username?;
|
|
15
|
+
private password?;
|
|
16
|
+
private client;
|
|
17
|
+
private reconnectAttempts;
|
|
18
|
+
private isConnected;
|
|
19
|
+
constructor(broker: string, port: number, topicPrefix: string, username?: string | undefined, password?: string | undefined);
|
|
20
|
+
/**
|
|
21
|
+
* Connect to MQTT broker
|
|
22
|
+
*/
|
|
23
|
+
connect(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Disconnect from MQTT broker
|
|
26
|
+
*/
|
|
27
|
+
disconnect(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Handle successful connection
|
|
30
|
+
*/
|
|
31
|
+
private handleConnect;
|
|
32
|
+
/**
|
|
33
|
+
* Handle incoming MQTT message
|
|
34
|
+
*/
|
|
35
|
+
private handleMessage;
|
|
36
|
+
/**
|
|
37
|
+
* Handle MQTT errors
|
|
38
|
+
*/
|
|
39
|
+
private handleError;
|
|
40
|
+
/**
|
|
41
|
+
* Handle offline event
|
|
42
|
+
*/
|
|
43
|
+
private handleOffline;
|
|
44
|
+
/**
|
|
45
|
+
* Handle reconnection attempt
|
|
46
|
+
*/
|
|
47
|
+
private handleReconnect;
|
|
48
|
+
/**
|
|
49
|
+
* Get connection status
|
|
50
|
+
*/
|
|
51
|
+
getConnectionStatus(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Get reconnect attempts count
|
|
54
|
+
*/
|
|
55
|
+
getReconnectAttempts(): number;
|
|
56
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EVNMQTTClient = void 0;
|
|
7
|
+
const mqtt_1 = __importDefault(require("mqtt"));
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
/**
|
|
10
|
+
* MQTT client manager with auto-reconnect
|
|
11
|
+
* Emits events:
|
|
12
|
+
* - 'connected': When successfully connected to broker
|
|
13
|
+
* - 'measurement': (topic: string, value: number) - When a valid measurement is received
|
|
14
|
+
* - 'error': (error: Error) - On MQTT errors
|
|
15
|
+
* - 'offline': When connection is lost
|
|
16
|
+
*/
|
|
17
|
+
class EVNMQTTClient extends events_1.EventEmitter {
|
|
18
|
+
constructor(broker, port, topicPrefix, username, password) {
|
|
19
|
+
super();
|
|
20
|
+
this.broker = broker;
|
|
21
|
+
this.port = port;
|
|
22
|
+
this.topicPrefix = topicPrefix;
|
|
23
|
+
this.username = username;
|
|
24
|
+
this.password = password;
|
|
25
|
+
this.client = null;
|
|
26
|
+
this.reconnectAttempts = 0;
|
|
27
|
+
this.isConnected = false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Connect to MQTT broker
|
|
31
|
+
*/
|
|
32
|
+
connect() {
|
|
33
|
+
const options = {
|
|
34
|
+
host: this.broker,
|
|
35
|
+
port: this.port,
|
|
36
|
+
username: this.username,
|
|
37
|
+
password: this.password,
|
|
38
|
+
reconnectPeriod: 1000, // Start reconnect attempts after 1 second
|
|
39
|
+
connectTimeout: 10000, // 10 second connection timeout
|
|
40
|
+
};
|
|
41
|
+
this.client = mqtt_1.default.connect(options);
|
|
42
|
+
this.client.on('connect', () => this.handleConnect());
|
|
43
|
+
this.client.on('message', (topic, message) => this.handleMessage(topic, message));
|
|
44
|
+
this.client.on('error', (error) => this.handleError(error));
|
|
45
|
+
this.client.on('offline', () => this.handleOffline());
|
|
46
|
+
this.client.on('reconnect', () => this.handleReconnect());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Disconnect from MQTT broker
|
|
50
|
+
*/
|
|
51
|
+
disconnect() {
|
|
52
|
+
if (this.client) {
|
|
53
|
+
this.client.end();
|
|
54
|
+
this.client = null;
|
|
55
|
+
this.isConnected = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Handle successful connection
|
|
60
|
+
*/
|
|
61
|
+
handleConnect() {
|
|
62
|
+
this.reconnectAttempts = 0;
|
|
63
|
+
this.isConnected = true;
|
|
64
|
+
// Subscribe to all topics under the prefix
|
|
65
|
+
const subscriptionTopic = `${this.topicPrefix}/#`;
|
|
66
|
+
this.client?.subscribe(subscriptionTopic, { qos: 0 }, (err) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
this.emit('error', new Error(`Failed to subscribe to ${subscriptionTopic}: ${err.message}`));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.emit('connected');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Handle incoming MQTT message
|
|
77
|
+
*/
|
|
78
|
+
handleMessage(topic, message) {
|
|
79
|
+
try {
|
|
80
|
+
const valueStr = message.toString().trim();
|
|
81
|
+
const value = parseFloat(valueStr);
|
|
82
|
+
if (isNaN(value)) {
|
|
83
|
+
// Invalid value - log but don't crash
|
|
84
|
+
this.emit('error', new Error(`Invalid MQTT value for ${topic}: "${valueStr}"`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Emit measurement event
|
|
88
|
+
this.emit('measurement', topic, value);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
this.emit('error', new Error(`Error processing MQTT message for ${topic}: ${error}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Handle MQTT errors
|
|
96
|
+
*/
|
|
97
|
+
handleError(error) {
|
|
98
|
+
this.emit('error', error);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Handle offline event
|
|
102
|
+
*/
|
|
103
|
+
handleOffline() {
|
|
104
|
+
this.isConnected = false;
|
|
105
|
+
this.emit('offline');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Handle reconnection attempt
|
|
109
|
+
*/
|
|
110
|
+
handleReconnect() {
|
|
111
|
+
this.reconnectAttempts++;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get connection status
|
|
115
|
+
*/
|
|
116
|
+
getConnectionStatus() {
|
|
117
|
+
return this.isConnected;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get reconnect attempts count
|
|
121
|
+
*/
|
|
122
|
+
getReconnectAttempts() {
|
|
123
|
+
return this.reconnectAttempts;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.EVNMQTTClient = EVNMQTTClient;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MeasurementType } from '../config/types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse an MQTT topic and extract the measurement type
|
|
4
|
+
* @param fullTopic - The full MQTT topic (e.g., "Smartmeter/Momentanleistung")
|
|
5
|
+
* @param topicPrefix - The configured topic prefix (e.g., "Smartmeter")
|
|
6
|
+
* @returns The measurement type or null if the topic doesn't match
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseTopic(fullTopic: string, topicPrefix: string): MeasurementType | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTopic = parseTopic;
|
|
4
|
+
const types_1 = require("../config/types");
|
|
5
|
+
/**
|
|
6
|
+
* Parse an MQTT topic and extract the measurement type
|
|
7
|
+
* @param fullTopic - The full MQTT topic (e.g., "Smartmeter/Momentanleistung")
|
|
8
|
+
* @param topicPrefix - The configured topic prefix (e.g., "Smartmeter")
|
|
9
|
+
* @returns The measurement type or null if the topic doesn't match
|
|
10
|
+
*/
|
|
11
|
+
function parseTopic(fullTopic, topicPrefix) {
|
|
12
|
+
// Normalize prefix - ensure it ends with /
|
|
13
|
+
const prefix = topicPrefix.endsWith('/') ? topicPrefix : `${topicPrefix}/`;
|
|
14
|
+
// Check if topic starts with the prefix
|
|
15
|
+
if (!fullTopic.startsWith(prefix)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
// Extract measurement name
|
|
19
|
+
const measurementName = fullTopic.substring(prefix.length);
|
|
20
|
+
// Check if it's a valid MeasurementType
|
|
21
|
+
if (Object.values(types_1.MeasurementType).includes(measurementName)) {
|
|
22
|
+
return measurementName;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, Service, Characteristic } from 'homebridge';
|
|
2
|
+
import { EVNSmartmeterConfig } from './config/types';
|
|
3
|
+
/**
|
|
4
|
+
* HomebridgePlatform
|
|
5
|
+
* This class is the main constructor for your plugin, this is where you should
|
|
6
|
+
* parse the user config and discover/register accessories with Homebridge.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EVNSmartmeterPlatform implements DynamicPlatformPlugin {
|
|
9
|
+
readonly log: Logger;
|
|
10
|
+
readonly config: EVNSmartmeterConfig;
|
|
11
|
+
readonly api: API;
|
|
12
|
+
readonly Service: typeof Service;
|
|
13
|
+
readonly Characteristic: typeof Characteristic;
|
|
14
|
+
readonly accessories: PlatformAccessory[];
|
|
15
|
+
private mqttClient?;
|
|
16
|
+
constructor(log: Logger, config: EVNSmartmeterConfig, api: API);
|
|
17
|
+
/**
|
|
18
|
+
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
|
19
|
+
* It should be used to setup event handlers for characteristics and update respective values.
|
|
20
|
+
*/
|
|
21
|
+
configureAccessory(accessory: PlatformAccessory): void;
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the platform and register accessories
|
|
24
|
+
*/
|
|
25
|
+
private initializePlatform;
|
|
26
|
+
/**
|
|
27
|
+
* Discover and register accessories based on configuration
|
|
28
|
+
*/
|
|
29
|
+
private discoverAccessories;
|
|
30
|
+
/**
|
|
31
|
+
* Register an accessory with Homebridge
|
|
32
|
+
*/
|
|
33
|
+
private registerAccessory;
|
|
34
|
+
/**
|
|
35
|
+
* Remove accessories that are disabled in the configuration
|
|
36
|
+
*/
|
|
37
|
+
private removeDisabledAccessories;
|
|
38
|
+
/**
|
|
39
|
+
* Cleanup resources on shutdown
|
|
40
|
+
*/
|
|
41
|
+
private cleanup;
|
|
42
|
+
}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVNSmartmeterPlatform = void 0;
|
|
4
|
+
const defaults_1 = require("./config/defaults");
|
|
5
|
+
const settings_1 = require("./settings");
|
|
6
|
+
const mqttClient_1 = require("./mqtt/mqttClient");
|
|
7
|
+
const powerMonitorAccessory_1 = require("./accessories/powerMonitorAccessory");
|
|
8
|
+
const energyMeterAccessory_1 = require("./accessories/energyMeterAccessory");
|
|
9
|
+
const voltageMonitorAccessory_1 = require("./accessories/voltageMonitorAccessory");
|
|
10
|
+
const currentMonitorAccessory_1 = require("./accessories/currentMonitorAccessory");
|
|
11
|
+
/**
|
|
12
|
+
* HomebridgePlatform
|
|
13
|
+
* This class is the main constructor for your plugin, this is where you should
|
|
14
|
+
* parse the user config and discover/register accessories with Homebridge.
|
|
15
|
+
*/
|
|
16
|
+
class EVNSmartmeterPlatform {
|
|
17
|
+
constructor(log, config, api) {
|
|
18
|
+
this.log = log;
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.api = api;
|
|
21
|
+
this.Service = this.api.hap.Service;
|
|
22
|
+
this.Characteristic = this.api.hap.Characteristic;
|
|
23
|
+
// Cached accessories
|
|
24
|
+
this.accessories = [];
|
|
25
|
+
// Validate configuration
|
|
26
|
+
if (!this.config.mqtt?.broker) {
|
|
27
|
+
this.log.error('MQTT broker not configured! Please configure mqtt.broker in config.json');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.log.info('EVN Smartmeter platform loaded');
|
|
31
|
+
// When this event is fired it means Homebridge has restored all cached accessories from disk.
|
|
32
|
+
// Dynamic Platform plugins should only register new accessories after this event was fired,
|
|
33
|
+
// in order to ensure they weren't added to homebridge already. This event can also be used
|
|
34
|
+
// to start discovery of new accessories.
|
|
35
|
+
this.api.on('didFinishLaunching', () => {
|
|
36
|
+
this.log.info('Finished launching, initializing platform');
|
|
37
|
+
this.initializePlatform();
|
|
38
|
+
});
|
|
39
|
+
// Clean up on shutdown
|
|
40
|
+
this.api.on('shutdown', () => {
|
|
41
|
+
this.cleanup();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
|
46
|
+
* It should be used to setup event handlers for characteristics and update respective values.
|
|
47
|
+
*/
|
|
48
|
+
configureAccessory(accessory) {
|
|
49
|
+
this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
50
|
+
this.accessories.push(accessory);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Initialize the platform and register accessories
|
|
54
|
+
*/
|
|
55
|
+
initializePlatform() {
|
|
56
|
+
// Initialize MQTT client
|
|
57
|
+
const mqttConfig = this.config.mqtt;
|
|
58
|
+
this.mqttClient = new mqttClient_1.EVNMQTTClient(mqttConfig.broker, mqttConfig.port || defaults_1.DEFAULT_MQTT_PORT, mqttConfig.topic_prefix || defaults_1.DEFAULT_TOPIC_PREFIX, mqttConfig.username, mqttConfig.password);
|
|
59
|
+
// Set up MQTT event handlers
|
|
60
|
+
this.mqttClient.on('connected', () => {
|
|
61
|
+
this.log.info('MQTT connected successfully');
|
|
62
|
+
});
|
|
63
|
+
this.mqttClient.on('error', (error) => {
|
|
64
|
+
this.log.error('MQTT Error:', error.message);
|
|
65
|
+
});
|
|
66
|
+
this.mqttClient.on('offline', () => {
|
|
67
|
+
this.log.warn('MQTT offline, will attempt to reconnect...');
|
|
68
|
+
});
|
|
69
|
+
// Register accessories
|
|
70
|
+
this.discoverAccessories();
|
|
71
|
+
// Connect to MQTT broker
|
|
72
|
+
this.log.info(`Connecting to MQTT broker at ${mqttConfig.broker}:${mqttConfig.port || defaults_1.DEFAULT_MQTT_PORT}`);
|
|
73
|
+
this.mqttClient.connect();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Discover and register accessories based on configuration
|
|
77
|
+
*/
|
|
78
|
+
discoverAccessories() {
|
|
79
|
+
const displayConfig = { ...defaults_1.DEFAULT_DISPLAY_CONFIG, ...this.config.display };
|
|
80
|
+
if (displayConfig.showPowerMonitor) {
|
|
81
|
+
this.registerAccessory('Power Monitor', powerMonitorAccessory_1.PowerMonitorAccessory);
|
|
82
|
+
}
|
|
83
|
+
if (displayConfig.showEnergyMeter) {
|
|
84
|
+
this.registerAccessory('Energy Meter', energyMeterAccessory_1.EnergyMeterAccessory);
|
|
85
|
+
}
|
|
86
|
+
if (displayConfig.showVoltageMonitor) {
|
|
87
|
+
this.registerAccessory('Voltage Monitor', voltageMonitorAccessory_1.VoltageMonitorAccessory);
|
|
88
|
+
}
|
|
89
|
+
if (displayConfig.showCurrentMonitor) {
|
|
90
|
+
this.registerAccessory('Current Monitor', currentMonitorAccessory_1.CurrentMonitorAccessory);
|
|
91
|
+
}
|
|
92
|
+
// Remove accessories that are no longer enabled
|
|
93
|
+
this.removeDisabledAccessories(displayConfig);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register an accessory with Homebridge
|
|
97
|
+
*/
|
|
98
|
+
registerAccessory(name, AccessoryClass) {
|
|
99
|
+
const uuid = this.api.hap.uuid.generate(name);
|
|
100
|
+
const existingAccessory = this.accessories.find((acc) => acc.UUID === uuid);
|
|
101
|
+
if (existingAccessory) {
|
|
102
|
+
// Accessory already exists, restore it
|
|
103
|
+
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
104
|
+
new AccessoryClass(this, existingAccessory, this.mqttClient);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Create new accessory
|
|
108
|
+
this.log.info('Adding new accessory:', name);
|
|
109
|
+
const accessory = new this.api.platformAccessory(name, uuid);
|
|
110
|
+
new AccessoryClass(this, accessory, this.mqttClient);
|
|
111
|
+
// Register the accessory
|
|
112
|
+
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
|
|
113
|
+
this.accessories.push(accessory);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove accessories that are disabled in the configuration
|
|
118
|
+
*/
|
|
119
|
+
removeDisabledAccessories(displayConfig) {
|
|
120
|
+
const enabledAccessories = [
|
|
121
|
+
displayConfig.showPowerMonitor ? 'Power Monitor' : null,
|
|
122
|
+
displayConfig.showEnergyMeter ? 'Energy Meter' : null,
|
|
123
|
+
displayConfig.showVoltageMonitor ? 'Voltage Monitor' : null,
|
|
124
|
+
displayConfig.showCurrentMonitor ? 'Current Monitor' : null,
|
|
125
|
+
].filter((name) => name !== null);
|
|
126
|
+
const accessoriesToRemove = this.accessories.filter((accessory) => !enabledAccessories.includes(accessory.displayName));
|
|
127
|
+
accessoriesToRemove.forEach((accessory) => {
|
|
128
|
+
this.log.info('Removing accessory:', accessory.displayName);
|
|
129
|
+
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
|
|
130
|
+
const index = this.accessories.indexOf(accessory);
|
|
131
|
+
if (index > -1) {
|
|
132
|
+
this.accessories.splice(index, 1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Cleanup resources on shutdown
|
|
138
|
+
*/
|
|
139
|
+
cleanup() {
|
|
140
|
+
this.log.info('Shutting down platform, disconnecting from MQTT');
|
|
141
|
+
if (this.mqttClient) {
|
|
142
|
+
this.mqttClient.disconnect();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.EVNSmartmeterPlatform = EVNSmartmeterPlatform;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the name of the platform that users will use to register the plugin in the Homebridge config.json
|
|
3
|
+
*/
|
|
4
|
+
export declare const PLATFORM_NAME = "EVNSmartmeter";
|
|
5
|
+
/**
|
|
6
|
+
* This must match the name of your plugin as defined the package.json
|
|
7
|
+
*/
|
|
8
|
+
export declare const PLUGIN_NAME = "homebridge-evn-smartmeter";
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* This is the name of the platform that users will use to register the plugin in the Homebridge config.json
|
|
6
|
+
*/
|
|
7
|
+
exports.PLATFORM_NAME = 'EVNSmartmeter';
|
|
8
|
+
/**
|
|
9
|
+
* This must match the name of your plugin as defined the package.json
|
|
10
|
+
*/
|
|
11
|
+
exports.PLUGIN_NAME = 'homebridge-evn-smartmeter';
|