Skip to content

Commit 352d7db

Browse files
committed
add charging duration
1 parent ba6bca4 commit 352d7db

1 file changed

Lines changed: 34 additions & 141 deletions

File tree

src/devices/futurehome.ts

Lines changed: 34 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Zcl} from "zigbee-herdsman";
22
import * as exposes from "../lib/exposes";
33
import * as m from "../lib/modernExtend";
44
import * as tuya from "../lib/tuya";
5-
import type {DefinitionWithExtend, Fz, ModernExtend, Tz} from "../lib/types";
5+
import type {DefinitionWithExtend, Fz, KeyValue, ModernExtend, Tz} from "../lib/types";
66

77
const e = exposes.presets;
88
const ea = exposes.access;
@@ -44,118 +44,6 @@ const localValueConverters = {
4444
};
4545

4646
const futurehomeExtend = {
47-
chargerStatus1: (): ModernExtend => {
48-
return {
49-
isModernExtend: true,
50-
fromZigbee: [
51-
{
52-
cluster: "haApplianceControl",
53-
type: ["commandSignalStateNotification", "commandSignalStateRsp"],
54-
convert(model, msg, publish, options, meta) {
55-
const status = msg?.data?.applianceStatus;
56-
if (status === undefined || status === null) return;
57-
let chargerStatus:
58-
| "plugged_out"
59-
| "1X: Off"
60-
| "4: plugged_in"
61-
| "2: plugged_in_charging"
62-
| "3: plugged_in_paused"
63-
| "5X: running"
64-
| "8X: failure" = "plugged_out";
65-
let chargingOn = false;
66-
67-
switch (status) {
68-
case 0x01: // Off
69-
chargerStatus = "1X: Off";
70-
chargingOn = false;
71-
break;
72-
case 0x02: // StandBy → charging
73-
chargerStatus = "2: plugged_in_charging";
74-
chargingOn = true;
75-
break;
76-
case 0x03: // Programmed (paused by user)
77-
chargerStatus = "3: plugged_in_paused";
78-
chargingOn = false;
79-
break;
80-
case 0x04: // ProgrammedWaitingToStart
81-
chargerStatus = "4: plugged_in";
82-
chargingOn = false;
83-
break;
84-
case 0x05: // Running
85-
chargerStatus = "5X: running";
86-
chargingOn = false;
87-
break;
88-
case 0x08: // Failure
89-
chargerStatus = "8X: failure";
90-
chargingOn = false;
91-
break;
92-
default:
93-
chargerStatus = "plugged_out";
94-
chargingOn = false;
95-
}
96-
return {charger_status: chargerStatus, charging_on: chargingOn};
97-
},
98-
} satisfies Fz.Converter<"haApplianceControl", undefined, ["commandSignalStateNotification", "commandSignalStateRsp"]>,
99-
],
100-
toZigbee: [
101-
{
102-
key: ["charger_status"],
103-
convertGet: async (entity, key, meta) => {
104-
await entity.command("haApplianceControl", "signalState", {});
105-
},
106-
} satisfies Tz.Converter,
107-
],
108-
configure: [
109-
async (device, coordinatorEndpoint, logger) => {
110-
for (const endpoint of device.endpoints) {
111-
if (endpoint.supportsInputCluster("haApplianceControl")) {
112-
await endpoint.bind("haApplianceControl", coordinatorEndpoint);
113-
try {
114-
await endpoint.command("haApplianceControl", "signalState", {});
115-
} catch {
116-
// do nothing
117-
}
118-
}
119-
}
120-
},
121-
],
122-
options: [
123-
e
124-
.numeric("charger_status_poll_interval", ea.SET)
125-
.withValueMin(-1)
126-
.withDescription("Polling interval charger status (default: 60s, -1 to disable)"),
127-
],
128-
onEvent: m.poll({
129-
key: "charger_status_poll",
130-
optionKey: "charger_status_poll_interval",
131-
option: e
132-
.numeric("charger_status_poll_interval", ea.SET)
133-
.withValueMin(-1)
134-
.withDescription("Polling interval charger status (default: 60s, -1 to disable)"),
135-
defaultIntervalSeconds: 60,
136-
poll: async (device) => {
137-
const endpoint = device.endpoints.find((e) => e.supportsInputCluster("haApplianceControl"));
138-
if (endpoint) {
139-
await endpoint.command("haApplianceControl", "signalState", {});
140-
}
141-
},
142-
}).onEvent,
143-
exposes: [
144-
exposes
145-
.enum("charger_status", ea.STATE_GET, [
146-
"plugged_out",
147-
"1X: Off",
148-
"2: plugged_in_charging",
149-
"3: plugged_in_paused",
150-
"4: plugged_in",
151-
"5X: running",
152-
"8X: failure",
153-
])
154-
.withDescription("Current EV charger state"),
155-
exposes.binary("charging_on", ea.STATE, "true", "false").withDescription("Indicates if the charger is actively delivering power"),
156-
],
157-
};
158-
},
15947
chargerStatus: (): ModernExtend => {
16048
const extend: ModernExtend = {
16149
isModernExtend: true,
@@ -168,7 +56,7 @@ const futurehomeExtend = {
16856
if (status === undefined || status === null) return;
16957
let chargerStatus:
17058
| "plugged_out"
171-
| "1X: Off"
59+
| "1: Off"
17260
| "4: plugged_in"
17361
| "2: plugged_in_charging"
17462
| "3: plugged_in_paused"
@@ -178,7 +66,7 @@ const futurehomeExtend = {
17866

17967
switch (status) {
18068
case 0x01: // Off
181-
chargerStatus = "1X: Off";
69+
chargerStatus = "1: Off";
18270
chargingOn = false;
18371
break;
18472
case 0x02: // StandBy → charging
@@ -235,7 +123,7 @@ const futurehomeExtend = {
235123
exposes
236124
.enum("charger_status", ea.STATE_GET, [
237125
"plugged_out",
238-
"1X: Off",
126+
"1: Off",
239127
"2: plugged_in_charging",
240128
"3: plugged_in_paused",
241129
"4: plugged_in",
@@ -309,33 +197,37 @@ const futurehomeExtend = {
309197
cluster: "haApplianceControl",
310198
type: ["attributeReport", "readResponse"],
311199
convert: (model, msg, publish, options, meta) => {
312-
// const prev_session_energy = (msg.data.energyMeterNow - msg.data.energyMeterStart) / 1000;
313-
// if (msg.data.energyMeterNow !== undefined && msg.data.energyMeterStart !== undefined) {
314-
// const prev_session_energy = msg.data.energyMeterNow - msg.data.energyMeterStart;
315-
// return {
316-
// previous_session_energy: prev_session_energy,
317-
// };
318-
// }
319-
let start = meta.state?.energy_meter_start !== undefined ? meta.state.energy_meter_start : null;
320-
let now = meta.state?.energy_meter_now !== undefined ? meta.state.energy_meter_now : null;
321-
if (Object.hasOwn(msg.data, "energyMeterStart")) {
322-
start = msg.data.energyMeterStart / 1000;
200+
const result: KeyValue = {};
201+
let energyStart = meta.state?.energy_meter_start !== undefined ? meta.state.energy_meter_start : null;
202+
let energyNow = meta.state?.energy_meter_now !== undefined ? meta.state.energy_meter_now : null;
203+
let startT = meta.state?.start_t !== undefined ? meta.state.start_t : null;
204+
let endT = meta.state?.end_t !== undefined ? meta.state.now_t : null;
205+
if (msg.data.energyMeterStart !== undefined) {
206+
energyStart = msg.data.energyMeterStart / 1000;
207+
}
208+
if (msg.data.energyMeterNow !== undefined) {
209+
energyNow = msg.data.energyMeterNow / 1000;
323210
}
324-
if (Object.hasOwn(msg.data, "energyMeterNow")) {
325-
now = msg.data.energyMeterNow / 1000;
211+
if (msg.data.chargingSessionStartT !== undefined) {
212+
startT = msg.data.chargingSessionStartT;
326213
}
327-
if (start !== null && now !== null) {
328-
const consumed = (now as number) - (start as number);
329-
return {
330-
// toFixed(3) prevents floating-point precision issues in JavaScript (e.g., 0.300000000004)
331-
session_energy: Number.parseFloat(consumed.toFixed(3)),
332-
};
214+
if (msg.data.chargingSessionEndT !== undefined) {
215+
endT = msg.data.chargingSessionEndT;
333216
}
334-
return;
217+
if (energyStart !== null && energyNow !== null) {
218+
result.session_energy = ((energyNow as number) - (energyStart as number)).toFixed(3);
219+
}
220+
if (startT !== null && endT !== null) {
221+
result.charging_duration = (endT as number) - (startT as number);
222+
}
223+
return result;
335224
},
336225
} satisfies Fz.Converter<"haApplianceControl", FuturehomeHaApplianceControl, ["attributeReport", "readResponse"]>,
337226
],
338-
exposes: [exposes.numeric("session_energy", ea.STATE).withLabel("Session energy").withDescription("Previous session").withUnit("kWh")],
227+
exposes: [
228+
exposes.numeric("session_energy", ea.STATE).withLabel("Session energy").withDescription("Previous session").withUnit("kWh"),
229+
exposes.numeric("charging_duration", ea.STATE).withDescription("Charging duration last session").withUnit("s"),
230+
],
339231
};
340232
},
341233
};
@@ -597,7 +489,7 @@ export const definitions: DefinitionWithExtend[] = [
597489
attribute: "energyMeterStart",
598490
description: "energyMeterStart",
599491
unit: "kWh",
600-
access: "STATE_GET",
492+
access: "STATE",
601493
scale: 1000,
602494
reporting: {min: 5, max: "1_HOUR", change: 1},
603495
zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.FUTUREHOME_AS},
@@ -608,7 +500,7 @@ export const definitions: DefinitionWithExtend[] = [
608500
attribute: "energyMeterNow",
609501
description: "energyMeterNow",
610502
unit: "kWh",
611-
access: "STATE_GET",
503+
access: "STATE",
612504
scale: 1000,
613505
reporting: {min: 5, max: "1_HOUR", change: 1},
614506
zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.FUTUREHOME_AS},
@@ -620,6 +512,7 @@ export const definitions: DefinitionWithExtend[] = [
620512
description: "Status",
621513
lookup: {
622514
plugged_out: 0x00,
515+
off: 0x01,
623516
plugged_in_charging: 0x02,
624517
plugged_in_paused: 0x03,
625518
plugged_in: 0x04,
@@ -633,7 +526,7 @@ export const definitions: DefinitionWithExtend[] = [
633526
cluster: "haApplianceControl",
634527
attribute: "chargingSessionStartT",
635528
description: "Start time of charging session. Time in seconds. Should be converted to date and time.",
636-
access: "STATE_GET",
529+
access: "STATE",
637530
scale: 1.0,
638531
unit: "s",
639532
reporting: {min: 5, max: "1_HOUR", change: 1},
@@ -644,7 +537,7 @@ export const definitions: DefinitionWithExtend[] = [
644537
cluster: "haApplianceControl",
645538
attribute: "chargingSessionEndT",
646539
description: "End time of charging session. Time in seconds. Should be converted to date and time.",
647-
access: "STATE_GET",
540+
access: "STATE",
648541
scale: 1.0,
649542
unit: "s",
650543
reporting: {min: 5, max: "1_HOUR", change: 1},

0 commit comments

Comments
 (0)