mirror of
https://github.com/adrianjagielak/home-assistant-futurehome.git
synced 2025-09-13 07:37:09 +00:00
Add support for 'meter_*' services
This commit is contained in:
parent
a39f2d5928
commit
a41b75da83
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- Improved MQTT components interfaces.
|
- Improved MQTT components interfaces.
|
||||||
- Refactored sensors.
|
- Refactored sensors.
|
||||||
|
- Added support for 'meter_*' services (electricity meter, gas meter, water meter, heating meter, cooling meter).
|
||||||
|
|
||||||
## 0.1.5 (25.07.2025)
|
## 0.1.5 (25.07.2025)
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -12,7 +12,7 @@ export interface BaseComponent {
|
|||||||
* The [category](https://developers.home-assistant.io/docs/core/entity#generic-properties) of the entity.
|
* The [category](https://developers.home-assistant.io/docs/core/entity#generic-properties) of the entity.
|
||||||
* When set, the entity category must be `diagnostic` for sensors.
|
* When set, the entity category must be `diagnostic` for sensors.
|
||||||
*/
|
*/
|
||||||
entity_category?: string;
|
entity_category?: null | 'config' | 'diagnostic';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Picture URL for the entity.
|
* Picture URL for the entity.
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
} from '../fimp/vinculum_pd7_device';
|
} from '../fimp/vinculum_pd7_device';
|
||||||
import { log } from '../logger';
|
import { log } from '../logger';
|
||||||
import { _alarm__components } from '../services/_alarm';
|
import { _alarm__components } from '../services/_alarm';
|
||||||
|
import { _meter__components } from '../services/_meter';
|
||||||
import { _sensor_binary__components } from '../services/_sensor_binary';
|
import { _sensor_binary__components } from '../services/_sensor_binary';
|
||||||
import { _sensor_numeric__components } from '../services/_sensor_numeric';
|
import { _sensor_numeric__components } from '../services/_sensor_numeric';
|
||||||
import { barrier_ctrl__components } from '../services/barrier_ctrl';
|
import { barrier_ctrl__components } from '../services/barrier_ctrl';
|
||||||
@ -150,6 +151,11 @@ const serviceHandlers: {
|
|||||||
fan_ctrl: fan_ctrl__components,
|
fan_ctrl: fan_ctrl__components,
|
||||||
indicator_ctrl: indicator_ctrl__components,
|
indicator_ctrl: indicator_ctrl__components,
|
||||||
media_player: media_player__components,
|
media_player: media_player__components,
|
||||||
|
meter_elec: _meter__components,
|
||||||
|
meter_gas: _meter__components,
|
||||||
|
meter_water: _meter__components,
|
||||||
|
meter_heating: _meter__components,
|
||||||
|
meter_cooling: _meter__components,
|
||||||
out_bin_switch: out_bin_switch__components,
|
out_bin_switch: out_bin_switch__components,
|
||||||
out_lvl_switch: out_lvl_switch__components,
|
out_lvl_switch: out_lvl_switch__components,
|
||||||
scene_ctrl: scene_ctrl__components,
|
scene_ctrl: scene_ctrl__components,
|
||||||
|
@ -177,13 +177,32 @@ const haStateCache: Record<
|
|||||||
|
|
||||||
const attributeTypeKeyMap: Record<string, string> = {
|
const attributeTypeKeyMap: Record<string, string> = {
|
||||||
alarm: 'event',
|
alarm: 'event',
|
||||||
|
meter: 'props.unit',
|
||||||
|
meter_export: 'props.unit',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getNestedValue(obj: any, path: string): any {
|
||||||
|
if (!obj) return undefined;
|
||||||
|
return path
|
||||||
|
.split('.')
|
||||||
|
.reduce((acc, key) => (acc != null ? acc[key] : undefined), obj);
|
||||||
|
}
|
||||||
|
|
||||||
function getTypeKey(attrName: string): string {
|
function getTypeKey(attrName: string): string {
|
||||||
// Default key is 'type', but override for certain attributes
|
|
||||||
return attributeTypeKeyMap[attrName] || 'type';
|
return attributeTypeKeyMap[attrName] || 'type';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractTypeDiscriminator(
|
||||||
|
entry: any,
|
||||||
|
typeKeyPath: string,
|
||||||
|
): string | undefined {
|
||||||
|
// Try to read the discriminator from the whole entry first (meter.props.unit),
|
||||||
|
// then fall back to inside val (e.g. val.type)
|
||||||
|
return (
|
||||||
|
getNestedValue(entry, typeKeyPath) ?? getNestedValue(entry.val, typeKeyPath)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to process multiple values for an attribute, handling typed values
|
* Helper function to process multiple values for an attribute, handling typed values
|
||||||
*/
|
*/
|
||||||
@ -199,27 +218,28 @@ function processAttributeValues(values: any[], attrName?: string): any {
|
|||||||
return tsB - tsA; // Latest first
|
return tsB - tsA; // Latest first
|
||||||
});
|
});
|
||||||
|
|
||||||
const typeKey = getTypeKey(attrName || '');
|
const typeKeyPath = getTypeKey(attrName || '');
|
||||||
|
|
||||||
const hasTypedValues = sortedValues.some(
|
// Build list of entries that carry a discriminator
|
||||||
(v) => v.val && typeof v.val === 'object' && v.val[typeKey],
|
const entriesWithType = sortedValues
|
||||||
);
|
.map((v) => ({ v, key: extractTypeDiscriminator(v, typeKeyPath) }))
|
||||||
|
.filter((x) => !!x.key) as Array<{ v: any; key: string }>;
|
||||||
|
|
||||||
if (!hasTypedValues) {
|
if (entriesWithType.length === 0) {
|
||||||
// No typed values, return the latest value
|
// Not a typed attribute → just return latest value
|
||||||
return sortedValues[0].val;
|
return sortedValues[0].val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group by type, keeping only the latest value for each type
|
// Group by (normalized) discriminator, keeping only the latest per type
|
||||||
const typeMap: Record<string, any> = {};
|
const typeMap: Record<string, any> = {};
|
||||||
|
|
||||||
for (const value of sortedValues) {
|
for (const { v, key } of entriesWithType) {
|
||||||
if (value.val && typeof value.val === 'object' && value.val[typeKey]) {
|
if (!typeMap[key]) {
|
||||||
const key = value.val[typeKey];
|
const payload =
|
||||||
if (!typeMap[key]) {
|
v && typeof v.val === 'object' && v.val !== null
|
||||||
const { [typeKey]: _, ...valueWithoutType } = value.val;
|
? { ...v.val }
|
||||||
typeMap[key] = valueWithoutType;
|
: { val: v.val }; // wrap primitives like meter readings
|
||||||
}
|
typeMap[key] = payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +304,14 @@ export function haUpdateStateValueReport(parameters: {
|
|||||||
value: any;
|
value: any;
|
||||||
attrName: string;
|
attrName: string;
|
||||||
}) {
|
}) {
|
||||||
|
if (
|
||||||
|
parameters.attrName === 'meter' ||
|
||||||
|
parameters.attrName === 'meter_export'
|
||||||
|
) {
|
||||||
|
// Ignore meter readings for now, relying on periodical site state updates
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y"
|
// Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y"
|
||||||
const addr = parameters.topic.replace(/^pt:j1\/mt:evt/, '');
|
const addr = parameters.topic.replace(/^pt:j1\/mt:evt/, '');
|
||||||
const typeKey = getTypeKey(parameters.attrName);
|
const typeKey = getTypeKey(parameters.attrName);
|
||||||
@ -295,13 +323,14 @@ export function haUpdateStateValueReport(parameters: {
|
|||||||
if (
|
if (
|
||||||
parameters.value &&
|
parameters.value &&
|
||||||
typeof parameters.value === 'object' &&
|
typeof parameters.value === 'object' &&
|
||||||
parameters.value[typeKey]
|
getNestedValue(parameters.value, typeKey)
|
||||||
) {
|
) {
|
||||||
// Handle typed value update
|
const key = getNestedValue(parameters.value, typeKey);
|
||||||
const key = parameters.value[typeKey];
|
const valueWithoutType =
|
||||||
const { [typeKey]: _, ...valueWithoutType } = parameters.value;
|
typeof parameters.value === 'object' && parameters.value !== null
|
||||||
|
? { ...parameters.value }
|
||||||
|
: { val: parameters.value };
|
||||||
|
|
||||||
// Get current attribute value
|
|
||||||
const currentAttrValue = payload[addr][parameters.attrName];
|
const currentAttrValue = payload[addr][parameters.attrName];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -2165,5 +2165,176 @@
|
|||||||
},
|
},
|
||||||
"type": null
|
"type": null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "Smart Electric Meter"
|
||||||
|
},
|
||||||
|
"id": 145,
|
||||||
|
"model": "zb - _TZ3040_bb6xaihh - TS0601",
|
||||||
|
"modelAlias": "Smart Electric Meter",
|
||||||
|
"type": {
|
||||||
|
"subtype": "main_elec",
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_elec": {
|
||||||
|
"name": "meter_elec",
|
||||||
|
"addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_elec/ad:145_1",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["kWh", "W"],
|
||||||
|
"sup_extended_vals": ["p_import", "u1", "i1", "freq", "p_factor"]
|
||||||
|
},
|
||||||
|
"intf": [
|
||||||
|
"cmd.meter.get_report",
|
||||||
|
"evt.meter.report",
|
||||||
|
"cmd.meter_ext.get_report",
|
||||||
|
"evt.meter_ext.report",
|
||||||
|
"cmd.meter.reset"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "3-Phase Smart Meter"
|
||||||
|
},
|
||||||
|
"id": 246,
|
||||||
|
"model": "Tibber - Tibber - Pulse Bridge",
|
||||||
|
"modelAlias": "Tibber Pulse Bridge",
|
||||||
|
"type": {
|
||||||
|
"subtype": "main_elec",
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_elec": {
|
||||||
|
"name": "meter_elec",
|
||||||
|
"addr": "/rt:dev/rn:tibber/ad:1/sv:meter_elec/ad:246",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["kWh", "W"],
|
||||||
|
"sup_export_units": ["kWh", "W"],
|
||||||
|
"sup_extended_vals": [
|
||||||
|
"p_import",
|
||||||
|
"p_export",
|
||||||
|
"u1",
|
||||||
|
"u2",
|
||||||
|
"u3",
|
||||||
|
"i1",
|
||||||
|
"i2",
|
||||||
|
"i3",
|
||||||
|
"p1",
|
||||||
|
"p2",
|
||||||
|
"p3",
|
||||||
|
"freq",
|
||||||
|
"p_factor",
|
||||||
|
"e_import",
|
||||||
|
"e_export"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"intf": [
|
||||||
|
"cmd.meter.get_report",
|
||||||
|
"evt.meter.report",
|
||||||
|
"cmd.meter_export.get_report",
|
||||||
|
"evt.meter_export.report",
|
||||||
|
"cmd.meter_ext.get_report",
|
||||||
|
"evt.meter_ext.report",
|
||||||
|
"cmd.meter.reset"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "Smart Gas Meter"
|
||||||
|
},
|
||||||
|
"id": 189,
|
||||||
|
"model": "zb - _TZ3000_h4yw2xn6 - TS0601",
|
||||||
|
"modelAlias": "Smart Gas Meter",
|
||||||
|
"type": {
|
||||||
|
"subtype": null,
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_gas": {
|
||||||
|
"name": "meter_gas",
|
||||||
|
"addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_gas/ad:189",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["cub_m", "pulse_c"]
|
||||||
|
},
|
||||||
|
"intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "Smart Water Meter"
|
||||||
|
},
|
||||||
|
"id": 222,
|
||||||
|
"model": "zw - Qubino - ZMNHWD1",
|
||||||
|
"modelAlias": "Qubino Water Meter",
|
||||||
|
"type": {
|
||||||
|
"subtype": null,
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_water": {
|
||||||
|
"name": "meter_water",
|
||||||
|
"addr": "/rt:dev/rn:zw/ad:1/sv:meter_water/ad:222",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["cub_m", "gallon"]
|
||||||
|
},
|
||||||
|
"intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "Heat Meter"
|
||||||
|
},
|
||||||
|
"id": 298,
|
||||||
|
"model": "Hoiax - Futurehome - Heat Meter",
|
||||||
|
"modelAlias": "Futurehome Heat Meter",
|
||||||
|
"type": {
|
||||||
|
"subtype": null,
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_heat": {
|
||||||
|
"name": "meter_heat",
|
||||||
|
"addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_heat/ad:298",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["kWh", "W"]
|
||||||
|
},
|
||||||
|
"intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client": {
|
||||||
|
"name": "Cooling Meter"
|
||||||
|
},
|
||||||
|
"id": 312,
|
||||||
|
"model": "Hoiax - Futurehome - Cooling Meter",
|
||||||
|
"modelAlias": "Futurehome Cooling Meter",
|
||||||
|
"type": {
|
||||||
|
"subtype": null,
|
||||||
|
"type": "meter"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"meter_cooling": {
|
||||||
|
"name": "meter_cooling",
|
||||||
|
"addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_cooling/ad:312",
|
||||||
|
"enabled": true,
|
||||||
|
"props": {
|
||||||
|
"sup_units": ["kWh", "W"]
|
||||||
|
},
|
||||||
|
"intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1472,5 +1472,260 @@
|
|||||||
"name": "alarm_heat"
|
"name": "alarm_heat"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 145,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_elec/ad:145_1",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "kWh"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 1245.168,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "W"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": 850,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meter_ext",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": {
|
||||||
|
"p_import": 850,
|
||||||
|
"u1": 236.5,
|
||||||
|
"i1": 3.6,
|
||||||
|
"freq": 50.02,
|
||||||
|
"p_factor": 0.98
|
||||||
|
},
|
||||||
|
"val_t": "float_map"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_elec"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 246,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:tibber/ad:1/sv:meter_elec/ad:246",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "kWh"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 15420.891,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "W"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": 2450,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meter_export",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "kWh"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 892.456,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "W"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": 0,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meter_ext",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": {
|
||||||
|
"p_import": 2450,
|
||||||
|
"p_export": 0,
|
||||||
|
"u1": 235.8,
|
||||||
|
"u2": 236.1,
|
||||||
|
"u3": 235.4,
|
||||||
|
"i1": 10.4,
|
||||||
|
"i2": 10.1,
|
||||||
|
"i3": 10.8,
|
||||||
|
"p1": 800,
|
||||||
|
"p2": 825,
|
||||||
|
"p3": 825,
|
||||||
|
"freq": 50.01,
|
||||||
|
"p_factor": 0.97,
|
||||||
|
"e_import": 15420891,
|
||||||
|
"e_export": 892456
|
||||||
|
},
|
||||||
|
"val_t": "float_map"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_elec"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 189,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_gas/ad:189",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "cub_m"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 1847.362,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "pulse_c"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 184736,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_gas"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 222,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:zw/ad:1/sv:meter_water/ad:222",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "cub_m"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 94.127,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "gallon"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 24872.5,
|
||||||
|
"val_t": "float"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_water"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 298,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_heat/ad:298",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "kWh"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 2847.92,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "W"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": 12500,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_heat"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 312,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_cooling/ad:312",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "meter",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "kWh"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:00 +0100",
|
||||||
|
"val": 1456.73,
|
||||||
|
"val_t": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"props": {
|
||||||
|
"unit": "W"
|
||||||
|
},
|
||||||
|
"ts": "2025-01-15 10:30:05 +0100",
|
||||||
|
"val": 8750,
|
||||||
|
"val_t": "int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "meter_cooling"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
610
futurehome/src/services/_meter.ts
Normal file
610
futurehome/src/services/_meter.ts
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
import {
|
||||||
|
VinculumPd7Device,
|
||||||
|
VinculumPd7Service,
|
||||||
|
} from '../fimp/vinculum_pd7_device';
|
||||||
|
import { SensorComponent } from '../ha/mqtt_components/sensor';
|
||||||
|
import {
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorStateClass,
|
||||||
|
} from '../ha/mqtt_components/_enums';
|
||||||
|
import {
|
||||||
|
CommandHandlers,
|
||||||
|
ServiceComponentsCreationResult,
|
||||||
|
} from '../ha/publish_device';
|
||||||
|
import { sendFimpMsg } from '../fimp/fimp';
|
||||||
|
import { HaMqttComponent } from '../ha/mqtt_components/_component';
|
||||||
|
|
||||||
|
// Define meter value to device class mapping
|
||||||
|
const METER_VALUE_DEVICE_CLASS_MAP: Record<string, SensorDeviceClass> = {
|
||||||
|
// Power values
|
||||||
|
p_import: 'power',
|
||||||
|
p1: 'power',
|
||||||
|
p2: 'power',
|
||||||
|
p3: 'power',
|
||||||
|
p_export: 'power',
|
||||||
|
p1_export: 'power',
|
||||||
|
p2_export: 'power',
|
||||||
|
p3_export: 'power',
|
||||||
|
p_import_react: 'reactive_power',
|
||||||
|
p1_import_react: 'reactive_power',
|
||||||
|
p2_import_react: 'reactive_power',
|
||||||
|
p3_import_react: 'reactive_power',
|
||||||
|
p_export_react: 'reactive_power',
|
||||||
|
p1_export_react: 'reactive_power',
|
||||||
|
p2_export_react: 'reactive_power',
|
||||||
|
p3_export_react: 'reactive_power',
|
||||||
|
p_import_apparent: 'apparent_power',
|
||||||
|
p1_import_apparent: 'apparent_power',
|
||||||
|
p2_import_apparent: 'apparent_power',
|
||||||
|
p3_import_apparent: 'apparent_power',
|
||||||
|
p_export_apparent: 'apparent_power',
|
||||||
|
p1_export_apparent: 'apparent_power',
|
||||||
|
p2_export_apparent: 'apparent_power',
|
||||||
|
p3_export_apparent: 'apparent_power',
|
||||||
|
dc_p: 'power',
|
||||||
|
|
||||||
|
// Energy values
|
||||||
|
e_import: 'energy',
|
||||||
|
e1_import: 'energy',
|
||||||
|
e2_import: 'energy',
|
||||||
|
e3_import: 'energy',
|
||||||
|
e_export: 'energy',
|
||||||
|
e1_export: 'energy',
|
||||||
|
e2_export: 'energy',
|
||||||
|
e3_export: 'energy',
|
||||||
|
e_import_react: 'reactive_energy',
|
||||||
|
e_export_react: 'reactive_energy',
|
||||||
|
e_import_apparent: 'energy',
|
||||||
|
e_export_apparent: 'energy',
|
||||||
|
|
||||||
|
// Voltage values
|
||||||
|
u: 'voltage',
|
||||||
|
u1: 'voltage',
|
||||||
|
u2: 'voltage',
|
||||||
|
u3: 'voltage',
|
||||||
|
u_export: 'voltage',
|
||||||
|
u1_export: 'voltage',
|
||||||
|
u2_export: 'voltage',
|
||||||
|
u3_export: 'voltage',
|
||||||
|
dc_u: 'voltage',
|
||||||
|
|
||||||
|
// Current values
|
||||||
|
i: 'current',
|
||||||
|
i1: 'current',
|
||||||
|
i2: 'current',
|
||||||
|
i3: 'current',
|
||||||
|
i_export: 'current',
|
||||||
|
i1_export: 'current',
|
||||||
|
i2_export: 'current',
|
||||||
|
i3_export: 'current',
|
||||||
|
dc_i: 'current',
|
||||||
|
|
||||||
|
// Power factor
|
||||||
|
p_factor: 'power_factor',
|
||||||
|
p1_factor: 'power_factor',
|
||||||
|
p2_factor: 'power_factor',
|
||||||
|
p3_factor: 'power_factor',
|
||||||
|
p_factor_export: 'power_factor',
|
||||||
|
p1_factor_export: 'power_factor',
|
||||||
|
p2_factor_export: 'power_factor',
|
||||||
|
p3_factor_export: 'power_factor',
|
||||||
|
|
||||||
|
// Frequency
|
||||||
|
freq: 'frequency',
|
||||||
|
|
||||||
|
// Gas meter
|
||||||
|
// Using 'gas' device class for gas volumes
|
||||||
|
|
||||||
|
// Water meter
|
||||||
|
// Using 'water' device class for water volumes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define meter value to state class mapping
|
||||||
|
const METER_VALUE_STATE_CLASS_MAP: Record<string, SensorStateClass> = {
|
||||||
|
// Energy values are typically total_increasing
|
||||||
|
e_import: 'total_increasing',
|
||||||
|
e1_import: 'total_increasing',
|
||||||
|
e2_import: 'total_increasing',
|
||||||
|
e3_import: 'total_increasing',
|
||||||
|
e_export: 'total_increasing',
|
||||||
|
e1_export: 'total_increasing',
|
||||||
|
e2_export: 'total_increasing',
|
||||||
|
e3_export: 'total_increasing',
|
||||||
|
e_import_react: 'total_increasing',
|
||||||
|
e_export_react: 'total_increasing',
|
||||||
|
e_import_apparent: 'total_increasing',
|
||||||
|
e_export_apparent: 'total_increasing',
|
||||||
|
|
||||||
|
// Power values are measurements
|
||||||
|
p_import: 'measurement',
|
||||||
|
p1: 'measurement',
|
||||||
|
p2: 'measurement',
|
||||||
|
p3: 'measurement',
|
||||||
|
p_export: 'measurement',
|
||||||
|
p1_export: 'measurement',
|
||||||
|
p2_export: 'measurement',
|
||||||
|
p3_export: 'measurement',
|
||||||
|
p_import_react: 'measurement',
|
||||||
|
p1_import_react: 'measurement',
|
||||||
|
p2_import_react: 'measurement',
|
||||||
|
p3_import_react: 'measurement',
|
||||||
|
p_export_react: 'measurement',
|
||||||
|
p1_export_react: 'measurement',
|
||||||
|
p2_export_react: 'measurement',
|
||||||
|
p3_export_react: 'measurement',
|
||||||
|
p_import_apparent: 'measurement',
|
||||||
|
p1_import_apparent: 'measurement',
|
||||||
|
p2_import_apparent: 'measurement',
|
||||||
|
p3_import_apparent: 'measurement',
|
||||||
|
p_export_apparent: 'measurement',
|
||||||
|
p1_export_apparent: 'measurement',
|
||||||
|
p2_export_apparent: 'measurement',
|
||||||
|
p3_export_apparent: 'measurement',
|
||||||
|
dc_p: 'measurement',
|
||||||
|
|
||||||
|
// Voltage and current are measurements
|
||||||
|
u: 'measurement',
|
||||||
|
u1: 'measurement',
|
||||||
|
u2: 'measurement',
|
||||||
|
u3: 'measurement',
|
||||||
|
u_export: 'measurement',
|
||||||
|
u1_export: 'measurement',
|
||||||
|
u2_export: 'measurement',
|
||||||
|
u3_export: 'measurement',
|
||||||
|
dc_u: 'measurement',
|
||||||
|
i: 'measurement',
|
||||||
|
i1: 'measurement',
|
||||||
|
i2: 'measurement',
|
||||||
|
i3: 'measurement',
|
||||||
|
i_export: 'measurement',
|
||||||
|
i1_export: 'measurement',
|
||||||
|
i2_export: 'measurement',
|
||||||
|
i3_export: 'measurement',
|
||||||
|
dc_i: 'measurement',
|
||||||
|
|
||||||
|
// Power factor is measurement
|
||||||
|
p_factor: 'measurement',
|
||||||
|
p1_factor: 'measurement',
|
||||||
|
p2_factor: 'measurement',
|
||||||
|
p3_factor: 'measurement',
|
||||||
|
p_factor_export: 'measurement',
|
||||||
|
p1_factor_export: 'measurement',
|
||||||
|
p2_factor_export: 'measurement',
|
||||||
|
p3_factor_export: 'measurement',
|
||||||
|
|
||||||
|
// Frequency is measurement
|
||||||
|
freq: 'measurement',
|
||||||
|
|
||||||
|
// Gas/water volumes are typically total_increasing
|
||||||
|
// (using generic names as they depend on meter configuration)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define friendly names for meter values
|
||||||
|
const METER_VALUE_NAMES: Record<string, string> = {
|
||||||
|
// Power
|
||||||
|
p_import: 'Power Import',
|
||||||
|
p1: 'Power Phase 1',
|
||||||
|
p2: 'Power Phase 2',
|
||||||
|
p3: 'Power Phase 3',
|
||||||
|
p_export: 'Power Export',
|
||||||
|
p1_export: 'Power Export Phase 1',
|
||||||
|
p2_export: 'Power Export Phase 2',
|
||||||
|
p3_export: 'Power Export Phase 3',
|
||||||
|
p_import_react: 'Reactive Power Import',
|
||||||
|
p1_import_react: 'Reactive Power Import Phase 1',
|
||||||
|
p2_import_react: 'Reactive Power Import Phase 2',
|
||||||
|
p3_import_react: 'Reactive Power Import Phase 3',
|
||||||
|
p_export_react: 'Reactive Power Export',
|
||||||
|
p1_export_react: 'Reactive Power Export Phase 1',
|
||||||
|
p2_export_react: 'Reactive Power Export Phase 2',
|
||||||
|
p3_export_react: 'Reactive Power Export Phase 3',
|
||||||
|
p_import_apparent: 'Apparent Power Import',
|
||||||
|
p1_import_apparent: 'Apparent Power Import Phase 1',
|
||||||
|
p2_import_apparent: 'Apparent Power Import Phase 2',
|
||||||
|
p3_import_apparent: 'Apparent Power Import Phase 3',
|
||||||
|
p_export_apparent: 'Apparent Power Export',
|
||||||
|
p1_export_apparent: 'Apparent Power Export Phase 1',
|
||||||
|
p2_export_apparent: 'Apparent Power Export Phase 2',
|
||||||
|
p3_export_apparent: 'Apparent Power Export Phase 3',
|
||||||
|
dc_p: 'DC Power',
|
||||||
|
|
||||||
|
// Energy
|
||||||
|
e_import: 'Energy Import',
|
||||||
|
e1_import: 'Energy Import Phase 1',
|
||||||
|
e2_import: 'Energy Import Phase 2',
|
||||||
|
e3_import: 'Energy Import Phase 3',
|
||||||
|
e_export: 'Energy Export',
|
||||||
|
e1_export: 'Energy Export Phase 1',
|
||||||
|
e2_export: 'Energy Export Phase 2',
|
||||||
|
e3_export: 'Energy Export Phase 3',
|
||||||
|
e_import_react: 'Reactive Energy Import',
|
||||||
|
e_export_react: 'Reactive Energy Export',
|
||||||
|
e_import_apparent: 'Apparent Energy Import',
|
||||||
|
e_export_apparent: 'Apparent Energy Export',
|
||||||
|
|
||||||
|
// Voltage
|
||||||
|
u: 'Voltage',
|
||||||
|
u1: 'Voltage Phase 1',
|
||||||
|
u2: 'Voltage Phase 2',
|
||||||
|
u3: 'Voltage Phase 3',
|
||||||
|
u_export: 'Voltage Export',
|
||||||
|
u1_export: 'Voltage Export Phase 1',
|
||||||
|
u2_export: 'Voltage Export Phase 2',
|
||||||
|
u3_export: 'Voltage Export Phase 3',
|
||||||
|
dc_u: 'DC Voltage',
|
||||||
|
|
||||||
|
// Current
|
||||||
|
i: 'Current',
|
||||||
|
i1: 'Current Phase 1',
|
||||||
|
i2: 'Current Phase 2',
|
||||||
|
i3: 'Current Phase 3',
|
||||||
|
i_export: 'Current Export',
|
||||||
|
i1_export: 'Current Export Phase 1',
|
||||||
|
i2_export: 'Current Export Phase 2',
|
||||||
|
i3_export: 'Current Export Phase 3',
|
||||||
|
dc_i: 'DC Current',
|
||||||
|
|
||||||
|
// Power Factor
|
||||||
|
p_factor: 'Power Factor',
|
||||||
|
p1_factor: 'Power Factor Phase 1',
|
||||||
|
p2_factor: 'Power Factor Phase 2',
|
||||||
|
p3_factor: 'Power Factor Phase 3',
|
||||||
|
p_factor_export: 'Power Factor Export',
|
||||||
|
p1_factor_export: 'Power Factor Export Phase 1',
|
||||||
|
p2_factor_export: 'Power Factor Export Phase 2',
|
||||||
|
p3_factor_export: 'Power Factor Export Phase 3',
|
||||||
|
|
||||||
|
// Frequency
|
||||||
|
freq: 'Frequency',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function _meter__components(
|
||||||
|
topicPrefix: string,
|
||||||
|
device: VinculumPd7Device,
|
||||||
|
svc: VinculumPd7Service,
|
||||||
|
svcName: string,
|
||||||
|
): ServiceComponentsCreationResult | undefined {
|
||||||
|
const components: Record<string, HaMqttComponent> = {};
|
||||||
|
const commandHandlers: CommandHandlers = {};
|
||||||
|
|
||||||
|
// Get supported units and extended values from service properties
|
||||||
|
const supportedUnits = svc.props?.sup_units || [];
|
||||||
|
const supportedExportUnits = svc.props?.sup_export_units || [];
|
||||||
|
const supportedExtendedValues = svc.props?.sup_extended_vals || [];
|
||||||
|
|
||||||
|
// Handle regular meter readings (meter.{unit}.val)
|
||||||
|
// These typically come from evt.meter.report
|
||||||
|
if (svc.intf?.includes('evt.meter.report')) {
|
||||||
|
for (const unit of supportedUnits) {
|
||||||
|
const componentId = `${svc.addr}_${unit}`;
|
||||||
|
|
||||||
|
let friendlyName = 'Meter';
|
||||||
|
let deviceClass: SensorDeviceClass | undefined;
|
||||||
|
let stateClass: SensorStateClass = 'total_increasing'; // Default for meter readings
|
||||||
|
|
||||||
|
// Set appropriate device class and name based on unit
|
||||||
|
switch (unit) {
|
||||||
|
case 'kWh':
|
||||||
|
friendlyName = 'Energy';
|
||||||
|
deviceClass = 'energy';
|
||||||
|
break;
|
||||||
|
case 'W':
|
||||||
|
friendlyName = 'Power';
|
||||||
|
deviceClass = 'power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
friendlyName = 'Current';
|
||||||
|
deviceClass = 'current';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
friendlyName = 'Voltage';
|
||||||
|
deviceClass = 'voltage';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'VA':
|
||||||
|
friendlyName = 'Apparent Power';
|
||||||
|
deviceClass = 'apparent_power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'kVAh':
|
||||||
|
friendlyName = 'Apparent Energy';
|
||||||
|
deviceClass = 'energy';
|
||||||
|
break;
|
||||||
|
case 'VAr':
|
||||||
|
friendlyName = 'Reactive Power';
|
||||||
|
deviceClass = 'reactive_power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'kVArh':
|
||||||
|
friendlyName = 'Reactive Energy';
|
||||||
|
deviceClass = 'reactive_energy';
|
||||||
|
break;
|
||||||
|
case 'Hz':
|
||||||
|
friendlyName = 'Frequency';
|
||||||
|
deviceClass = 'frequency';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'power_factor':
|
||||||
|
friendlyName = 'Power Factor';
|
||||||
|
deviceClass = 'power_factor';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'pulse_c':
|
||||||
|
friendlyName = 'Pulse Count';
|
||||||
|
stateClass = 'total_increasing';
|
||||||
|
break;
|
||||||
|
case 'cub_m':
|
||||||
|
friendlyName = 'Volume';
|
||||||
|
deviceClass =
|
||||||
|
svcName === 'meter_gas'
|
||||||
|
? 'gas'
|
||||||
|
: svcName === 'meter_water'
|
||||||
|
? 'water'
|
||||||
|
: 'volume';
|
||||||
|
stateClass = 'total_increasing';
|
||||||
|
break;
|
||||||
|
case 'cub_f':
|
||||||
|
friendlyName = 'Volume';
|
||||||
|
deviceClass =
|
||||||
|
svcName === 'meter_gas'
|
||||||
|
? 'gas'
|
||||||
|
: svcName === 'meter_water'
|
||||||
|
? 'water'
|
||||||
|
: 'volume';
|
||||||
|
stateClass = 'total_increasing';
|
||||||
|
break;
|
||||||
|
case 'gallon':
|
||||||
|
friendlyName = 'Volume';
|
||||||
|
deviceClass =
|
||||||
|
svcName === 'meter_gas'
|
||||||
|
? 'gas'
|
||||||
|
: svcName === 'meter_water'
|
||||||
|
? 'water'
|
||||||
|
: 'volume';
|
||||||
|
stateClass = 'total_increasing';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
friendlyName = `Meter (${unit})`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const component: SensorComponent = {
|
||||||
|
unique_id: componentId,
|
||||||
|
platform: 'sensor',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
name: friendlyName,
|
||||||
|
unit_of_measurement: unit,
|
||||||
|
state_class: stateClass,
|
||||||
|
value_template: `{{ value_json['${svc.addr}'].meter.${unit}.val | default(0) }}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (deviceClass) {
|
||||||
|
component.device_class = deviceClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set suggested display precision based on unit
|
||||||
|
if (unit === 'kWh' || unit === 'kVAh' || unit === 'kVArh') {
|
||||||
|
component.suggested_display_precision = 3;
|
||||||
|
} else if (
|
||||||
|
unit === 'W' ||
|
||||||
|
unit === 'VA' ||
|
||||||
|
unit === 'VAr' ||
|
||||||
|
unit === 'V' ||
|
||||||
|
unit === 'A'
|
||||||
|
) {
|
||||||
|
component.suggested_display_precision = 1;
|
||||||
|
} else if (unit === 'Hz') {
|
||||||
|
component.suggested_display_precision = 2;
|
||||||
|
} else if (unit === 'power_factor') {
|
||||||
|
component.suggested_display_precision = 3;
|
||||||
|
} else if (unit === 'cub_m' || unit === 'cub_f' || unit === 'gallon') {
|
||||||
|
component.suggested_display_precision = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
components[componentId] = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle export meter readings (meter.{unit}.val) for bidirectional meters
|
||||||
|
if (svc.intf?.includes('evt.meter_export.report')) {
|
||||||
|
for (const unit of supportedExportUnits) {
|
||||||
|
const componentId = `${svc.addr}_export_${unit}`;
|
||||||
|
|
||||||
|
let friendlyName = 'Export Meter';
|
||||||
|
let deviceClass: SensorDeviceClass | undefined;
|
||||||
|
let stateClass: SensorStateClass = 'total_increasing'; // Default for meter readings
|
||||||
|
|
||||||
|
// Set appropriate device class and name based on unit
|
||||||
|
switch (unit) {
|
||||||
|
case 'kWh':
|
||||||
|
friendlyName = 'Energy Export';
|
||||||
|
deviceClass = 'energy';
|
||||||
|
break;
|
||||||
|
case 'W':
|
||||||
|
friendlyName = 'Power Export';
|
||||||
|
deviceClass = 'power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
friendlyName = 'Current Export';
|
||||||
|
deviceClass = 'current';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
friendlyName = 'Voltage Export';
|
||||||
|
deviceClass = 'voltage';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'VA':
|
||||||
|
friendlyName = 'Apparent Power Export';
|
||||||
|
deviceClass = 'apparent_power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'kVAh':
|
||||||
|
friendlyName = 'Apparent Energy Export';
|
||||||
|
deviceClass = 'energy';
|
||||||
|
break;
|
||||||
|
case 'VAr':
|
||||||
|
friendlyName = 'Reactive Power Export';
|
||||||
|
deviceClass = 'reactive_power';
|
||||||
|
stateClass = 'measurement';
|
||||||
|
break;
|
||||||
|
case 'kVArh':
|
||||||
|
friendlyName = 'Reactive Energy Export';
|
||||||
|
deviceClass = 'reactive_energy';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
friendlyName = `Export Meter (${unit})`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const component: SensorComponent = {
|
||||||
|
unique_id: componentId,
|
||||||
|
platform: 'sensor',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
name: friendlyName,
|
||||||
|
unit_of_measurement: unit,
|
||||||
|
state_class: stateClass,
|
||||||
|
value_template: `{{ value_json['${svc.addr}'].meter_export.${unit}.val | default(0) }}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (deviceClass) {
|
||||||
|
component.device_class = deviceClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set suggested display precision
|
||||||
|
if (unit === 'kWh' || unit === 'kVAh' || unit === 'kVArh') {
|
||||||
|
component.suggested_display_precision = 3;
|
||||||
|
} else if (
|
||||||
|
unit === 'W' ||
|
||||||
|
unit === 'VA' ||
|
||||||
|
unit === 'VAr' ||
|
||||||
|
unit === 'V' ||
|
||||||
|
unit === 'A'
|
||||||
|
) {
|
||||||
|
component.suggested_display_precision = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
components[componentId] = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle extended meter values (meter_ext.{value_name})
|
||||||
|
// These typically come from evt.meter_ext.report and include detailed power/voltage/current readings
|
||||||
|
if (
|
||||||
|
svc.intf?.includes('evt.meter_ext.report') &&
|
||||||
|
supportedExtendedValues.length > 0
|
||||||
|
) {
|
||||||
|
for (const valueName of supportedExtendedValues) {
|
||||||
|
const componentId = `${svc.addr}_ext_${valueName}`;
|
||||||
|
const deviceClass = METER_VALUE_DEVICE_CLASS_MAP[valueName];
|
||||||
|
const stateClass =
|
||||||
|
METER_VALUE_STATE_CLASS_MAP[valueName] || 'measurement';
|
||||||
|
const friendlyName =
|
||||||
|
METER_VALUE_NAMES[valueName] ||
|
||||||
|
valueName
|
||||||
|
.replace(/_/g, ' ')
|
||||||
|
.replace(/\b\w/g, (l: string) => l.toUpperCase());
|
||||||
|
|
||||||
|
// Determine unit based on value name
|
||||||
|
let unit = '';
|
||||||
|
if (
|
||||||
|
valueName.startsWith('p_') ||
|
||||||
|
valueName.startsWith('p1') ||
|
||||||
|
valueName.startsWith('p2') ||
|
||||||
|
valueName.startsWith('p3') ||
|
||||||
|
valueName === 'dc_p'
|
||||||
|
) {
|
||||||
|
unit = 'W';
|
||||||
|
} else if (
|
||||||
|
valueName.startsWith('e_') ||
|
||||||
|
valueName.startsWith('e1') ||
|
||||||
|
valueName.startsWith('e2') ||
|
||||||
|
valueName.startsWith('e3')
|
||||||
|
) {
|
||||||
|
unit = 'kWh';
|
||||||
|
} else if (
|
||||||
|
valueName.startsWith('u_') ||
|
||||||
|
valueName.startsWith('u1') ||
|
||||||
|
valueName.startsWith('u2') ||
|
||||||
|
valueName.startsWith('u3') ||
|
||||||
|
valueName === 'dc_u'
|
||||||
|
) {
|
||||||
|
unit = 'V';
|
||||||
|
} else if (
|
||||||
|
valueName.startsWith('i_') ||
|
||||||
|
valueName.startsWith('i1') ||
|
||||||
|
valueName.startsWith('i2') ||
|
||||||
|
valueName.startsWith('i3') ||
|
||||||
|
valueName === 'dc_i'
|
||||||
|
) {
|
||||||
|
unit = 'A';
|
||||||
|
} else if (valueName.includes('factor')) {
|
||||||
|
unit = '';
|
||||||
|
} else if (valueName === 'freq') {
|
||||||
|
unit = 'Hz';
|
||||||
|
}
|
||||||
|
|
||||||
|
const component: SensorComponent = {
|
||||||
|
unique_id: componentId,
|
||||||
|
platform: 'sensor',
|
||||||
|
entity_category: 'diagnostic',
|
||||||
|
name: friendlyName,
|
||||||
|
state_class: stateClass,
|
||||||
|
value_template: `{{ value_json['${svc.addr}'].meter_ext.${valueName} | default(0) }}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (unit) {
|
||||||
|
component.unit_of_measurement = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceClass) {
|
||||||
|
component.device_class = deviceClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set suggested display precision
|
||||||
|
if (unit === 'kWh') {
|
||||||
|
component.suggested_display_precision = 3;
|
||||||
|
} else if (unit === 'W' || unit === 'V' || unit === 'A') {
|
||||||
|
component.suggested_display_precision = 1;
|
||||||
|
} else if (unit === 'Hz') {
|
||||||
|
component.suggested_display_precision = 2;
|
||||||
|
} else if (valueName.includes('factor')) {
|
||||||
|
component.suggested_display_precision = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
components[componentId] = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle meter reset button if supported
|
||||||
|
if (svc.intf?.includes('cmd.meter.reset')) {
|
||||||
|
const resetCommandTopic = `${topicPrefix}${svc.addr}/reset/command`;
|
||||||
|
|
||||||
|
components[`${svc.addr}_reset`] = {
|
||||||
|
unique_id: `${svc.addr}_reset`,
|
||||||
|
platform: 'button',
|
||||||
|
name: 'Reset Meter',
|
||||||
|
entity_category: 'config',
|
||||||
|
icon: 'mdi:restart',
|
||||||
|
command_topic: resetCommandTopic,
|
||||||
|
};
|
||||||
|
|
||||||
|
commandHandlers[resetCommandTopic] = async (_payload: string) => {
|
||||||
|
await sendFimpMsg({
|
||||||
|
address: svc.addr,
|
||||||
|
service: svcName,
|
||||||
|
cmd: 'cmd.meter.reset',
|
||||||
|
val_t: 'null',
|
||||||
|
val: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
components,
|
||||||
|
commandHandlers,
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user