homebridge-viessmann-vicare 2.0.69 β 2.0.71
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 +5 -0
- package/SETUP-GUIDE.md +7 -2
- package/config.schema.json +14 -0
- package/dist/accessories/room-sensor-accessory.d.ts +72 -0
- package/dist/accessories/room-sensor-accessory.d.ts.map +1 -0
- package/dist/accessories/room-sensor-accessory.js +228 -0
- package/dist/platform.d.ts +2 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +89 -8
- package/dist/settings.d.ts +3 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +4 -1
- package/package.json +1 -1
- package/viessmann-report-server.js +66 -7
package/README.md
CHANGED
|
@@ -1033,6 +1033,11 @@ For issues and questions:
|
|
|
1033
1033
|
|
|
1034
1034
|
## π Changelog
|
|
1035
1035
|
|
|
1036
|
+
### [2.0.70] - 2026-05-29
|
|
1037
|
+
- feat: TRV / Room Sensor discovery mode (`features.enableRoomSensorDiscovery`) β scans all gateway devices, logs every API feature path+value tagged `[RoomDiscovery]`, creates a provisional HomeKit `TemperatureSensor` for each device with a temperature reading
|
|
1038
|
+
- feat: report generation timeout now configurable (`reportServerTimeout`, default 300 s, range 60β1800 s) β exposed in Homebridge Config UI X
|
|
1039
|
+
- fix: report server `--timeout` arg forwarded from plugin config to child process
|
|
1040
|
+
|
|
1036
1041
|
### [2.0.68] - 2026-05-29
|
|
1037
1042
|
- fix: report server now logs actual LAN IP (`http://192.168.x.x:PORT`) instead of `localhost`/`0.0.0.0` β URL is reachable from any device on the network
|
|
1038
1043
|
- fix: report generation timeout raised from 60 s to 300 s β fixes timeout error with large CSV files (90+ days, 26 000+ rows)
|
package/SETUP-GUIDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Complete Setup Guide - v2.0.
|
|
1
|
+
# Complete Setup Guide - v2.0.70
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
This guide will walk you through setting up the Viessmann ViCare plugin v2.0.
|
|
5
|
+
This guide will walk you through setting up the Viessmann ViCare plugin v2.0.70 for Homebridge, including all the advanced features like intelligent caching, rate limiting protection, comprehensive configuration options, **complete localization support with custom names**, **CSV history logging**, **HTML diagnostic reports**, **energy system monitoring** (PV, battery, wallbox), and **heating schedule awareness** with visual bands in the HTML report, and **heat pump (WΓ€rmepumpe) support** with automatic device detection.
|
|
6
6
|
|
|
7
7
|
## Prerequisites
|
|
8
8
|
|
|
@@ -1228,6 +1228,11 @@ sudo systemctl restart homebridge
|
|
|
1228
1228
|
|
|
1229
1229
|
## Changelog
|
|
1230
1230
|
|
|
1231
|
+
### v2.0.70 (2026-05-29)
|
|
1232
|
+
- feat: TRV/room sensor discovery mode (enableRoomSensorDiscovery flag)
|
|
1233
|
+
- feat: report generation timeout configurable (reportServerTimeout, 60β1800 s)
|
|
1234
|
+
- fix: --timeout forwarded from plugin config to report server process
|
|
1235
|
+
|
|
1231
1236
|
### v2.0.68 (2026-05-29)
|
|
1232
1237
|
- fix: report server now logs actual LAN IP instead of localhost/0.0.0.0
|
|
1233
1238
|
- fix: report generation timeout raised from 60 s to 300 s (fixes error with 90+ day CSV)
|
package/config.schema.json
CHANGED
|
@@ -433,6 +433,12 @@
|
|
|
433
433
|
"type": "boolean",
|
|
434
434
|
"default": true,
|
|
435
435
|
"description": "Create separate accessories showing burner status, modulation level, and statistics."
|
|
436
|
+
},
|
|
437
|
+
"enableRoomSensorDiscovery": {
|
|
438
|
+
"title": "Enable Room Sensor Discovery (TRV / Smart Climate)",
|
|
439
|
+
"type": "boolean",
|
|
440
|
+
"default": false,
|
|
441
|
+
"description": "Scan all gateway devices for thermostatic radiator valves (TRVs) or per-room temperature sensors. Creates a HomeKit TemperatureSensor for each discovered device and dumps all API feature paths to the Homebridge log. Enable this if you have Viessmann ViCare Smart Climate and want to help implement full TRV support. Disable in production β generates verbose logs."
|
|
436
442
|
}
|
|
437
443
|
}
|
|
438
444
|
},
|
|
@@ -510,6 +516,14 @@
|
|
|
510
516
|
"description": "Duration in ms to block regular polling from overwriting command state after a command is sent. Default: 120000 (2 minutes)."
|
|
511
517
|
}
|
|
512
518
|
}
|
|
519
|
+
},
|
|
520
|
+
"reportServerTimeout": {
|
|
521
|
+
"title": "Report Server Generation Timeout",
|
|
522
|
+
"type": "integer",
|
|
523
|
+
"default": 600,
|
|
524
|
+
"minimum": 120,
|
|
525
|
+
"maximum": 3600,
|
|
526
|
+
"description": "Maximum time in seconds for report generation. RPi 3 benchmark: ~365s for 90 days / ~1460s for 365 days. Default: 600s. Range: 120β3600s."
|
|
513
527
|
}
|
|
514
528
|
},
|
|
515
529
|
"required": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViessmannRoomSensorAccessory
|
|
3
|
+
*
|
|
4
|
+
* DISCOVERY / DIAGNOSTIC STUB β v2.0.69
|
|
5
|
+
*
|
|
6
|
+
* Enabled via: features.enableRoomSensorDiscovery = true
|
|
7
|
+
*
|
|
8
|
+
* Purpose:
|
|
9
|
+
* Users with Viessmann ViCare Smart Climate thermostatic radiator valves (TRVs)
|
|
10
|
+
* or per-room temperature sensors can enable this mode to:
|
|
11
|
+
* 1. Log ALL feature paths of every non-main device in the gateway.
|
|
12
|
+
* 2. Create a HomeKit TemperatureSensor accessory for every device that
|
|
13
|
+
* exposes at least one numeric temperature value.
|
|
14
|
+
*
|
|
15
|
+
* What this solves:
|
|
16
|
+
* The exact API feature paths for TRVs vary by model/generation. By running
|
|
17
|
+
* this discovery mode and sharing the Homebridge logs, users provide the data
|
|
18
|
+
* needed to implement full per-room control in a future release.
|
|
19
|
+
*
|
|
20
|
+
* How to use:
|
|
21
|
+
* 1. Set features.enableRoomSensorDiscovery: true in plugin config.
|
|
22
|
+
* 2. Restart Homebridge.
|
|
23
|
+
* 3. Check logs for lines starting with "[RoomDiscovery]".
|
|
24
|
+
* 4. Share the log output in a GitHub issue β it contains all feature paths
|
|
25
|
+
* and property values for your TRV devices.
|
|
26
|
+
*
|
|
27
|
+
* HomeKit accessories created:
|
|
28
|
+
* - One TemperatureSensor per discovered device with a temperature reading.
|
|
29
|
+
* - Name: "<InstallationPrefix> Room <modelId|deviceId>"
|
|
30
|
+
* - The sensor shows the best available temperature (room > supply > general).
|
|
31
|
+
*
|
|
32
|
+
* This file will be replaced by a full implementation once the API paths
|
|
33
|
+
* are known for common TRV models (Viessmann ViCare Smart Thermostat, etc.).
|
|
34
|
+
*/
|
|
35
|
+
import { PlatformAccessory } from 'homebridge';
|
|
36
|
+
import { ViessmannPlatform } from '../platform';
|
|
37
|
+
import { ViessmannFeature, ViessmannDevice, ViessmannInstallation, ViessmannGateway } from '../viessmann-api-endpoints';
|
|
38
|
+
export interface RoomSensorDiscoveryResult {
|
|
39
|
+
device: ViessmannDevice;
|
|
40
|
+
tempFeaturePath?: string;
|
|
41
|
+
tempPropName?: string;
|
|
42
|
+
tempLabel?: string;
|
|
43
|
+
currentTemp?: number;
|
|
44
|
+
allTempFeatures: Array<{
|
|
45
|
+
path: string;
|
|
46
|
+
prop: string;
|
|
47
|
+
value: number;
|
|
48
|
+
unit: string;
|
|
49
|
+
}>;
|
|
50
|
+
allFeatureCount: number;
|
|
51
|
+
matchedRole?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Scan a device's features and return structured discovery data.
|
|
55
|
+
* Also dumps a detailed log to help reverse-engineer unknown TRV models.
|
|
56
|
+
*/
|
|
57
|
+
export declare function discoverRoomSensorData(platform: ViessmannPlatform, device: ViessmannDevice, features: ViessmannFeature[]): RoomSensorDiscoveryResult;
|
|
58
|
+
export declare class ViessmannRoomSensorAccessory {
|
|
59
|
+
private readonly platform;
|
|
60
|
+
private readonly accessory;
|
|
61
|
+
private readonly installation;
|
|
62
|
+
private readonly gateway;
|
|
63
|
+
private temperatureService;
|
|
64
|
+
private readonly TAG;
|
|
65
|
+
private discoveryResult;
|
|
66
|
+
constructor(platform: ViessmannPlatform, accessory: PlatformAccessory, installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]);
|
|
67
|
+
/**
|
|
68
|
+
* Called by the platform update loop at each refresh cycle.
|
|
69
|
+
*/
|
|
70
|
+
update(features: ViessmannFeature[]): void;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=room-sensor-accessory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room-sensor-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/room-sensor-accessory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAiCxH,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,eAAe,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpF,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,yBAAyB,CAsG3B;AAID,qBAAa,4BAA4B;IAQrC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IAGtC,OAAO,CAAC,eAAe,CAA4B;gBAGhC,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EAC1C,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,gBAAgB,EAAE;IA2C9B;;OAEG;IACI,MAAM,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,IAAI;CA2BlD"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ViessmannRoomSensorAccessory
|
|
4
|
+
*
|
|
5
|
+
* DISCOVERY / DIAGNOSTIC STUB β v2.0.69
|
|
6
|
+
*
|
|
7
|
+
* Enabled via: features.enableRoomSensorDiscovery = true
|
|
8
|
+
*
|
|
9
|
+
* Purpose:
|
|
10
|
+
* Users with Viessmann ViCare Smart Climate thermostatic radiator valves (TRVs)
|
|
11
|
+
* or per-room temperature sensors can enable this mode to:
|
|
12
|
+
* 1. Log ALL feature paths of every non-main device in the gateway.
|
|
13
|
+
* 2. Create a HomeKit TemperatureSensor accessory for every device that
|
|
14
|
+
* exposes at least one numeric temperature value.
|
|
15
|
+
*
|
|
16
|
+
* What this solves:
|
|
17
|
+
* The exact API feature paths for TRVs vary by model/generation. By running
|
|
18
|
+
* this discovery mode and sharing the Homebridge logs, users provide the data
|
|
19
|
+
* needed to implement full per-room control in a future release.
|
|
20
|
+
*
|
|
21
|
+
* How to use:
|
|
22
|
+
* 1. Set features.enableRoomSensorDiscovery: true in plugin config.
|
|
23
|
+
* 2. Restart Homebridge.
|
|
24
|
+
* 3. Check logs for lines starting with "[RoomDiscovery]".
|
|
25
|
+
* 4. Share the log output in a GitHub issue β it contains all feature paths
|
|
26
|
+
* and property values for your TRV devices.
|
|
27
|
+
*
|
|
28
|
+
* HomeKit accessories created:
|
|
29
|
+
* - One TemperatureSensor per discovered device with a temperature reading.
|
|
30
|
+
* - Name: "<InstallationPrefix> Room <modelId|deviceId>"
|
|
31
|
+
* - The sensor shows the best available temperature (room > supply > general).
|
|
32
|
+
*
|
|
33
|
+
* This file will be replaced by a full implementation once the API paths
|
|
34
|
+
* are known for common TRV models (Viessmann ViCare Smart Thermostat, etc.).
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ViessmannRoomSensorAccessory = void 0;
|
|
38
|
+
exports.discoverRoomSensorData = discoverRoomSensorData;
|
|
39
|
+
// ββ Candidate feature paths for room/zone temperature, in priority order ββββββ
|
|
40
|
+
// Extend this list as new devices are discovered.
|
|
41
|
+
const TEMP_PATH_CANDIDATES = [
|
|
42
|
+
// Zone-based (ViCare Smart Climate typical)
|
|
43
|
+
{ path: 'heating.zones.0.sensors.temperature.room', prop: 'value', label: 'zone0.room' },
|
|
44
|
+
{ path: 'heating.zones.1.sensors.temperature.room', prop: 'value', label: 'zone1.room' },
|
|
45
|
+
{ path: 'heating.zones.2.sensors.temperature.room', prop: 'value', label: 'zone2.room' },
|
|
46
|
+
{ path: 'heating.zones.3.sensors.temperature.room', prop: 'value', label: 'zone3.room' },
|
|
47
|
+
// Circuit-based (standard HC)
|
|
48
|
+
{ path: 'heating.circuits.0.sensors.temperature.room', prop: 'value', label: 'hc0.room' },
|
|
49
|
+
{ path: 'heating.circuits.1.sensors.temperature.room', prop: 'value', label: 'hc1.room' },
|
|
50
|
+
// Generic device-level temperature (TRV models)
|
|
51
|
+
{ path: 'temperature.current', prop: 'value', label: 'temp.current' },
|
|
52
|
+
{ path: 'temperature.value', prop: 'value', label: 'temp.value' },
|
|
53
|
+
{ path: 'temperature.basic', prop: 'value', label: 'temp.basic' },
|
|
54
|
+
// Setpoint (secondary interest)
|
|
55
|
+
{ path: 'temperature.setpoint', prop: 'value', label: 'temp.setpoint' },
|
|
56
|
+
{ path: 'heating.zones.0.temperature.setpoint', prop: 'value', label: 'zone0.setpoint' },
|
|
57
|
+
];
|
|
58
|
+
// Valve/TRV-specific feature paths we want to capture but not display
|
|
59
|
+
const VALVE_PATH_PATTERNS = ['valve', 'opening', 'position', 'level'];
|
|
60
|
+
// Roles that indicate a TRV or room sensor device
|
|
61
|
+
const ROOM_DEVICE_ROLES = [
|
|
62
|
+
'room', 'remote-control', 'remote', 'sensor',
|
|
63
|
+
'valve', 'thermostat', 'zone',
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Scan a device's features and return structured discovery data.
|
|
67
|
+
* Also dumps a detailed log to help reverse-engineer unknown TRV models.
|
|
68
|
+
*/
|
|
69
|
+
function discoverRoomSensorData(platform, device, features) {
|
|
70
|
+
const log = platform.log;
|
|
71
|
+
const TAG = '[RoomDiscovery]';
|
|
72
|
+
const roles = device.roles ?? [];
|
|
73
|
+
const modelId = device.modelId || device.id;
|
|
74
|
+
const matchedRole = roles.find(r => ROOM_DEVICE_ROLES.some(k => r.toLowerCase().includes(k)));
|
|
75
|
+
log.info(`${TAG} ββββββββββββββββββββββββββββββββββββββββββββββ`);
|
|
76
|
+
log.info(`${TAG} Device id=${device.id} model=${modelId}`);
|
|
77
|
+
log.info(`${TAG} deviceType: ${device.deviceType}`);
|
|
78
|
+
log.info(`${TAG} roles: [${roles.join(', ')}]`);
|
|
79
|
+
log.info(`${TAG} status: ${device.status}`);
|
|
80
|
+
log.info(`${TAG} matched role: ${matchedRole ?? '(none β showing all features anyway)'}`);
|
|
81
|
+
log.info(`${TAG} total features: ${features.length}`);
|
|
82
|
+
log.info(`${TAG} ββ Enabled features with properties ββββββββββ`);
|
|
83
|
+
const allTempFeatures = [];
|
|
84
|
+
// Log ALL enabled features β this is the diagnostic payload
|
|
85
|
+
for (const f of features.filter(x => x.isEnabled)) {
|
|
86
|
+
const props = f.properties ?? {};
|
|
87
|
+
const cmds = Object.keys(f.commands ?? {});
|
|
88
|
+
const pKeys = Object.keys(props);
|
|
89
|
+
log.info(`${TAG} [${f.isEnabled ? 'β' : 'β'}] ${f.feature}`);
|
|
90
|
+
for (const pk of pKeys) {
|
|
91
|
+
const p = props[pk];
|
|
92
|
+
const val = p?.value;
|
|
93
|
+
const unit = p?.unit ?? '';
|
|
94
|
+
const type = p?.type ?? typeof val;
|
|
95
|
+
if (val !== undefined && val !== null) {
|
|
96
|
+
// Collect numeric temperature-range values
|
|
97
|
+
if (typeof val === 'number' && val > -30 && val < 100 && unit !== '%') {
|
|
98
|
+
allTempFeatures.push({ path: f.feature, prop: pk, value: val, unit });
|
|
99
|
+
}
|
|
100
|
+
const display = Array.isArray(val)
|
|
101
|
+
? `[${val.slice(0, 4).join(', ')}${val.length > 4 ? 'β¦' : ''}]`
|
|
102
|
+
: String(val);
|
|
103
|
+
log.info(`${TAG} ${pk}: ${display} ${unit} (type:${type})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (cmds.length > 0) {
|
|
107
|
+
log.info(`${TAG} commands: ${cmds.join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Try to match a known temperature path
|
|
111
|
+
let bestPath;
|
|
112
|
+
let bestProp;
|
|
113
|
+
let bestLabel;
|
|
114
|
+
let bestValue;
|
|
115
|
+
const featureMap = new Map(features.map(f => [f.feature, f]));
|
|
116
|
+
for (const candidate of TEMP_PATH_CANDIDATES) {
|
|
117
|
+
const feat = featureMap.get(candidate.path);
|
|
118
|
+
if (feat?.isEnabled) {
|
|
119
|
+
const val = feat.properties?.[candidate.prop]?.value;
|
|
120
|
+
if (typeof val === 'number' && val > -30 && val < 100) {
|
|
121
|
+
bestPath = candidate.path;
|
|
122
|
+
bestProp = candidate.prop;
|
|
123
|
+
bestLabel = candidate.label;
|
|
124
|
+
bestValue = val;
|
|
125
|
+
log.info(`${TAG} β MATCH: ${candidate.path}.${candidate.prop} = ${val}Β°C [${candidate.label}]`);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Fallback: use first discovered temp feature in range
|
|
131
|
+
if (!bestPath && allTempFeatures.length > 0) {
|
|
132
|
+
const fb = allTempFeatures[0];
|
|
133
|
+
bestPath = fb.path;
|
|
134
|
+
bestProp = fb.prop;
|
|
135
|
+
bestLabel = 'auto-detected';
|
|
136
|
+
bestValue = fb.value;
|
|
137
|
+
log.info(`${TAG} β FALLBACK: ${fb.path}.${fb.prop} = ${fb.value}${fb.unit}`);
|
|
138
|
+
}
|
|
139
|
+
if (!bestPath) {
|
|
140
|
+
log.info(`${TAG} β No temperature feature found for device ${device.id}`);
|
|
141
|
+
log.info(`${TAG} βΉοΈ Share these logs in a GitHub issue to help implement full TRV support`);
|
|
142
|
+
}
|
|
143
|
+
log.info(`${TAG} ββββββββββββββββββββββββββββββββββββββββββββββ`);
|
|
144
|
+
return {
|
|
145
|
+
device,
|
|
146
|
+
tempFeaturePath: bestPath,
|
|
147
|
+
tempPropName: bestProp,
|
|
148
|
+
tempLabel: bestLabel,
|
|
149
|
+
currentTemp: bestValue,
|
|
150
|
+
allTempFeatures,
|
|
151
|
+
allFeatureCount: features.length,
|
|
152
|
+
matchedRole,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
156
|
+
class ViessmannRoomSensorAccessory {
|
|
157
|
+
platform;
|
|
158
|
+
accessory;
|
|
159
|
+
installation;
|
|
160
|
+
gateway;
|
|
161
|
+
temperatureService;
|
|
162
|
+
TAG = '[RoomSensor]';
|
|
163
|
+
// Discovery result: saved at init, updated at each refresh
|
|
164
|
+
discoveryResult;
|
|
165
|
+
constructor(platform, accessory, installation, gateway, device, features) {
|
|
166
|
+
this.platform = platform;
|
|
167
|
+
this.accessory = accessory;
|
|
168
|
+
this.installation = installation;
|
|
169
|
+
this.gateway = gateway;
|
|
170
|
+
const { Service: Svc, Characteristic: Char } = platform;
|
|
171
|
+
// Run discovery scan
|
|
172
|
+
this.discoveryResult = discoverRoomSensorData(platform, device, features);
|
|
173
|
+
// AccessoryInformation
|
|
174
|
+
accessory.getService(Svc.AccessoryInformation)
|
|
175
|
+
.setCharacteristic(Char.Manufacturer, 'Viessmann')
|
|
176
|
+
.setCharacteristic(Char.Model, device.modelId || 'Room Sensor (Discovery)')
|
|
177
|
+
.setCharacteristic(Char.SerialNumber, `${installation.id}-${device.id}`);
|
|
178
|
+
// TemperatureSensor service
|
|
179
|
+
this.temperatureService =
|
|
180
|
+
accessory.getService(Svc.TemperatureSensor) ||
|
|
181
|
+
accessory.addService(Svc.TemperatureSensor, accessory.displayName);
|
|
182
|
+
// Initial value
|
|
183
|
+
const initTemp = this.discoveryResult.currentTemp ?? 20;
|
|
184
|
+
this.temperatureService
|
|
185
|
+
.getCharacteristic(Char.CurrentTemperature)
|
|
186
|
+
.updateValue(initTemp);
|
|
187
|
+
// StatusActive: true if we found a temperature path
|
|
188
|
+
this.temperatureService
|
|
189
|
+
.getCharacteristic(Char.StatusActive)
|
|
190
|
+
.updateValue(!!this.discoveryResult.tempFeaturePath);
|
|
191
|
+
if (this.discoveryResult.tempFeaturePath) {
|
|
192
|
+
platform.log.info(`${this.TAG} "${accessory.displayName}" β ` +
|
|
193
|
+
`reading ${this.discoveryResult.tempLabel} (${this.discoveryResult.tempFeaturePath}) ` +
|
|
194
|
+
`= ${initTemp}Β°C`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
platform.log.warn(`${this.TAG} "${accessory.displayName}" β no temperature feature found. ` +
|
|
198
|
+
`Check logs for full feature list and open a GitHub issue.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Called by the platform update loop at each refresh cycle.
|
|
203
|
+
*/
|
|
204
|
+
update(features) {
|
|
205
|
+
const { Characteristic: Char } = this.platform;
|
|
206
|
+
const { tempFeaturePath, tempPropName } = this.discoveryResult;
|
|
207
|
+
if (!tempFeaturePath || !tempPropName) {
|
|
208
|
+
this.platform.log.debug(`${this.TAG} "${this.accessory.displayName}" β no path, skipping update`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const feat = features.find(f => f.feature === tempFeaturePath);
|
|
212
|
+
if (!feat?.isEnabled) {
|
|
213
|
+
this.platform.log.debug(`${this.TAG} "${this.accessory.displayName}" β feature not found/enabled`);
|
|
214
|
+
this.temperatureService.getCharacteristic(Char.StatusActive).updateValue(false);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const raw = feat.properties?.[tempPropName]?.value;
|
|
218
|
+
if (typeof raw !== 'number') {
|
|
219
|
+
this.platform.log.debug(`${this.TAG} "${this.accessory.displayName}" β non-numeric value: ${raw}`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const temp = Math.max(-270, Math.min(100, raw));
|
|
223
|
+
this.temperatureService.getCharacteristic(Char.CurrentTemperature).updateValue(temp);
|
|
224
|
+
this.temperatureService.getCharacteristic(Char.StatusActive).updateValue(true);
|
|
225
|
+
this.platform.log.debug(`${this.TAG} "${this.accessory.displayName}" β temp: ${temp}Β°C [${this.discoveryResult.tempLabel}]`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.ViessmannRoomSensorAccessory = ViessmannRoomSensorAccessory;
|
package/dist/platform.d.ts
CHANGED
|
@@ -54,8 +54,10 @@ export declare class ViessmannPlatform implements DynamicPlatformPlugin {
|
|
|
54
54
|
setupDHWAccessory(installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]): Promise<void>;
|
|
55
55
|
setupHeatingCircuitAccessories(installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]): Promise<void>;
|
|
56
56
|
setupEnergyAccessory(installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]): Promise<void>;
|
|
57
|
+
private setupRoomSensorDiscovery;
|
|
57
58
|
updateAllDevices(): Promise<void>;
|
|
58
59
|
private updateCycleComplete;
|
|
60
|
+
private writeApiStatusFile;
|
|
59
61
|
private adjustRefreshInterval;
|
|
60
62
|
private sleep;
|
|
61
63
|
private startHealthMonitoring;
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAGH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAExE,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACrI,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAgB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/platform.js
CHANGED
|
@@ -40,6 +40,8 @@ const boiler_accessory_1 = require("./accessories/boiler-accessory");
|
|
|
40
40
|
const dhw_accessory_1 = require("./accessories/dhw-accessory");
|
|
41
41
|
const heating_circuit_accessory_1 = require("./accessories/heating-circuit-accessory");
|
|
42
42
|
const energy_accessory_1 = require("./accessories/energy-accessory");
|
|
43
|
+
const room_sensor_accessory_1 = require("./accessories/room-sensor-accessory");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
43
45
|
const settings_1 = require("./settings");
|
|
44
46
|
class ViessmannPlatform {
|
|
45
47
|
log;
|
|
@@ -116,7 +118,8 @@ class ViessmannPlatform {
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
|
-
const
|
|
121
|
+
const reportTimeout = this.config.reportServerTimeout ?? 300;
|
|
122
|
+
const serverArgs = ['--port', String(reportPort), '--path', reportServerPath, '--timeout', String(reportTimeout)];
|
|
120
123
|
if (this.config.debug)
|
|
121
124
|
serverArgs.push('--debug');
|
|
122
125
|
const { execFile } = require('child_process');
|
|
@@ -127,13 +130,12 @@ class ViessmannPlatform {
|
|
|
127
130
|
child.on('error', (err) => {
|
|
128
131
|
this.log.warn(`Report server failed to start: ${err.message}`);
|
|
129
132
|
});
|
|
130
|
-
this.log.info(
|
|
131
|
-
this.log.info(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
133
|
+
this.log.info('β'.repeat(60));
|
|
134
|
+
this.log.info('π Viessmann Report Server');
|
|
135
|
+
this.log.info(` Open from any device: http://${lanIP}:${reportPort}`);
|
|
136
|
+
this.log.info('β'.repeat(60));
|
|
137
|
+
this.log.debug(` Data path: ${reportServerPath}`);
|
|
138
|
+
this.log.debug(` Script: ${reportScript}`);
|
|
137
139
|
}
|
|
138
140
|
catch (e) {
|
|
139
141
|
this.log.warn(`Could not start report server: ${e.message}`);
|
|
@@ -547,6 +549,10 @@ class ViessmannPlatform {
|
|
|
547
549
|
await this.setupHeatingCircuitAccessories(installation, gateway, device, features);
|
|
548
550
|
// Setup Energy / Heat Pump accessory (PV, battery, wallbox, electric DHW, WΓ€rmepumpe)
|
|
549
551
|
await this.setupEnergyAccessory(installation, gateway, device, features);
|
|
552
|
+
// TRV / Room Sensor discovery mode (logs all features, creates TemperatureSensor per device)
|
|
553
|
+
if (this.config.features?.enableRoomSensorDiscovery) {
|
|
554
|
+
await this.setupRoomSensorDiscovery(installation, gateway, device, features);
|
|
555
|
+
}
|
|
550
556
|
}
|
|
551
557
|
catch (error) {
|
|
552
558
|
const status = error?.response?.status;
|
|
@@ -688,6 +694,39 @@ class ViessmannPlatform {
|
|
|
688
694
|
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, 'ViessmannPlatform', [accessory]);
|
|
689
695
|
}
|
|
690
696
|
}
|
|
697
|
+
// ββ TRV / Room Sensor Discovery ββββββββββββββββββββββββββββββββββββββββββ
|
|
698
|
+
// Enabled via features.enableRoomSensorDiscovery = true.
|
|
699
|
+
// Creates a TemperatureSensor accessory for each device that exposes a
|
|
700
|
+
// temperature reading. Full feature dump goes to Homebridge logs so we can
|
|
701
|
+
// identify the correct API paths for a given TRV model.
|
|
702
|
+
async setupRoomSensorDiscovery(installation, gateway, device, features) {
|
|
703
|
+
// Run the feature scan (always logs, regardless of whether we find a temp)
|
|
704
|
+
const result = (0, room_sensor_accessory_1.discoverRoomSensorData)(this, device, features);
|
|
705
|
+
// Only create a HomeKit accessory if we found at least one temperature value
|
|
706
|
+
if (result.currentTemp === undefined) {
|
|
707
|
+
this.log.debug(`[RoomDiscovery] Device ${device.id} β no temperature found, skipping HomeKit accessory`);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const customNames = this.config.customNames ?? {};
|
|
711
|
+
const prefix = customNames.installationPrefix || installation.description || String(installation.id);
|
|
712
|
+
const modelLabel = device.modelId || device.id;
|
|
713
|
+
const name = `${prefix} Room ${modelLabel}`;
|
|
714
|
+
const uuid = this.api.hap.uuid.generate(`room-sensor-${installation.id}-${device.id}`);
|
|
715
|
+
let accessory = this.accessories.find(a => a.UUID === uuid);
|
|
716
|
+
if (!accessory) {
|
|
717
|
+
this.log.info(`[RoomDiscovery] Creating new TemperatureSensor: "${name}"`);
|
|
718
|
+
accessory = new this.api.platformAccessory(name, uuid);
|
|
719
|
+
accessory.context = { installation, gateway, device, isRoomSensor: true };
|
|
720
|
+
this.api.registerPlatformAccessories('homebridge-viessmann-vicare', 'ViessmannPlatform', [accessory]);
|
|
721
|
+
this.accessories.push(accessory);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
this.log.debug(`[RoomDiscovery] Restoring cached accessory: "${name}"`);
|
|
725
|
+
}
|
|
726
|
+
const handler = new room_sensor_accessory_1.ViessmannRoomSensorAccessory(this, accessory, installation, gateway, device, features);
|
|
727
|
+
// Attach updateHandler so the main refresh loop can call update()
|
|
728
|
+
accessory.updateHandler = (feats) => handler.update(feats);
|
|
729
|
+
}
|
|
691
730
|
async updateAllDevices() {
|
|
692
731
|
// Prevent overlapping updates
|
|
693
732
|
if (this.isUpdating) {
|
|
@@ -826,9 +865,51 @@ class ViessmannPlatform {
|
|
|
826
865
|
const healthScore = this.viessmannAPI.getAPIHealthScore();
|
|
827
866
|
const healthStatus = this.viessmannAPI.getAPIHealthStatus();
|
|
828
867
|
this.log.debug(`π API Health: ${healthScore}/100 (${healthStatus})`);
|
|
868
|
+
this.writeApiStatusFile(healthScore, healthStatus);
|
|
829
869
|
}
|
|
830
870
|
}
|
|
831
871
|
}
|
|
872
|
+
// ββ API Status File βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
873
|
+
// Written after each successful update cycle.
|
|
874
|
+
// Read by viessmann-report-server.js and auth-manager status page to show
|
|
875
|
+
// API usage percentage, health score, and rate limit info in the web UI.
|
|
876
|
+
// Viessmann documented daily limit: 1450 requests/day.
|
|
877
|
+
writeApiStatusFile(healthScore, healthStatus) {
|
|
878
|
+
try {
|
|
879
|
+
const storagePath = this.api.user.storagePath();
|
|
880
|
+
const metrics = this.viessmannAPI.getAPIMetrics();
|
|
881
|
+
const rateLimitSt = this.viessmannAPI.getRateLimitStatus();
|
|
882
|
+
const DAILY_LIMIT = 1450; // Viessmann API documented daily quota
|
|
883
|
+
const status = {
|
|
884
|
+
timestamp: new Date().toISOString(),
|
|
885
|
+
healthScore,
|
|
886
|
+
healthStatus,
|
|
887
|
+
totalRequests: metrics.totalRequests,
|
|
888
|
+
successfulRequests: metrics.successfulRequests,
|
|
889
|
+
failedRequests: metrics.failedRequests,
|
|
890
|
+
rateLimitHits: metrics.rateLimitHits,
|
|
891
|
+
errorRate: parseFloat(metrics.errorRate.toFixed(1)),
|
|
892
|
+
avgResponseTime: Math.round(metrics.averageResponseTime),
|
|
893
|
+
requestsPerMinute: metrics.requestsPerMinute,
|
|
894
|
+
isRateLimited: rateLimitSt.isLimited,
|
|
895
|
+
rateLimitWaitSec: rateLimitSt.waitSeconds ?? 0,
|
|
896
|
+
dailyQuotaExceeded: rateLimitSt.dailyQuotaExceeded,
|
|
897
|
+
// Estimated daily usage β resets with metrics (every 24h)
|
|
898
|
+
// requestsPerMinute Γ 60 Γ 24 gives a rough projection
|
|
899
|
+
dailyEstimatedReqs: Math.round(metrics.requestsPerMinute * 60 * 24),
|
|
900
|
+
dailyLimitTotal: DAILY_LIMIT,
|
|
901
|
+
dailyUsagePct: Math.min(100, parseFloat((metrics.totalRequests / DAILY_LIMIT * 100).toFixed(1))),
|
|
902
|
+
pluginVersion: '2.0.71',
|
|
903
|
+
refreshInterval: this.config.refreshInterval || 120000,
|
|
904
|
+
};
|
|
905
|
+
const filePath = require('path').join(storagePath, 'viessmann-api-status.json');
|
|
906
|
+
fs.writeFileSync(filePath, JSON.stringify(status, null, 2), 'utf8');
|
|
907
|
+
this.log.debug(`π API status written: health=${healthScore}/100, dailyUsage=${status.dailyUsagePct}%`);
|
|
908
|
+
}
|
|
909
|
+
catch (e) {
|
|
910
|
+
this.log.debug(`π Could not write API status file: ${e.message}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
832
913
|
adjustRefreshInterval(increase) {
|
|
833
914
|
if (this.config.enableRateLimitProtection === false) {
|
|
834
915
|
return;
|
package/dist/settings.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare const PLUGIN_NAME = "homebridge-viessmann-vicare";
|
|
|
9
9
|
/**
|
|
10
10
|
* Plugin version for User-Agent and logging
|
|
11
11
|
*/
|
|
12
|
-
export declare const PLUGIN_VERSION = "2.0.
|
|
12
|
+
export declare const PLUGIN_VERSION = "2.0.71";
|
|
13
13
|
/**
|
|
14
14
|
* Default configuration values
|
|
15
15
|
*/
|
|
@@ -49,12 +49,14 @@ export declare const DEFAULT_CONFIG: {
|
|
|
49
49
|
enableTemperaturePrograms: boolean;
|
|
50
50
|
enableQuickSelections: boolean;
|
|
51
51
|
enableBurnerStatus: boolean;
|
|
52
|
+
enableRoomSensorDiscovery: boolean;
|
|
52
53
|
};
|
|
53
54
|
advanced: {
|
|
54
55
|
maxConsecutiveErrors: number;
|
|
55
56
|
deviceUpdateDelay: number;
|
|
56
57
|
userAgent: string;
|
|
57
58
|
};
|
|
59
|
+
reportServerTimeout: number;
|
|
58
60
|
monitoring: {
|
|
59
61
|
enableHealthReports: boolean;
|
|
60
62
|
healthReportInterval: number;
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAEjD;;GAEG;AACH,eAAO,MAAM,WAAW,gCAAgC,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAEjD;;GAEG;AACH,eAAO,MAAM,WAAW,gCAAgC,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD1B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;mCAOK,MAAM;kCACP,MAAM,iBAAiB,MAAM;mCAE5B,MAAM,iBAAiB,MAAM,YAAY,MAAM;kCAEhD,MAAM,iBAAiB,MAAM,YAAY,MAAM,eAAe,MAAM;kCAEpE,MAAM,iBAAiB,MAAM,YAAY,MAAM,eAAe,MAAM,eAAe,MAAM;;CAGtH,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;6BAmBN,MAAM;+BACJ,MAAM;2BACV,MAAM;2BACN,MAAM;;;;;;;CAS5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;CAmB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;CASvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;CAmBlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;CAK/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;CAY1B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;CAgBhC,CAAC"}
|
package/dist/settings.js
CHANGED
|
@@ -12,7 +12,7 @@ exports.PLUGIN_NAME = 'homebridge-viessmann-vicare';
|
|
|
12
12
|
/**
|
|
13
13
|
* Plugin version for User-Agent and logging
|
|
14
14
|
*/
|
|
15
|
-
exports.PLUGIN_VERSION = '2.0.
|
|
15
|
+
exports.PLUGIN_VERSION = '2.0.71';
|
|
16
16
|
/**
|
|
17
17
|
* Default configuration values
|
|
18
18
|
*/
|
|
@@ -53,12 +53,15 @@ exports.DEFAULT_CONFIG = {
|
|
|
53
53
|
enableTemperaturePrograms: true,
|
|
54
54
|
enableQuickSelections: true,
|
|
55
55
|
enableBurnerStatus: true,
|
|
56
|
+
// TRV / Smart Climate discovery (verbose β use only for diagnostics)
|
|
57
|
+
enableRoomSensorDiscovery: false,
|
|
56
58
|
},
|
|
57
59
|
advanced: {
|
|
58
60
|
maxConsecutiveErrors: 5,
|
|
59
61
|
deviceUpdateDelay: 1000,
|
|
60
62
|
userAgent: `homebridge-viessmann-vicare/${exports.PLUGIN_VERSION}`,
|
|
61
63
|
},
|
|
64
|
+
reportServerTimeout: 600, // seconds β default 10 min; RPi3: ~365s/90days ~1460s/365days
|
|
62
65
|
monitoring: {
|
|
63
66
|
enableHealthReports: true,
|
|
64
67
|
healthReportInterval: 3600000, // 1 hour
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-viessmann-vicare",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.71",
|
|
4
4
|
"description": "Complete Homebridge plugin for Viessmann ViCare heating systems with full control capabilities, advanced rate limiting protection, and intelligent cache management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -27,10 +27,12 @@ const { execFile, spawn } = require('child_process');
|
|
|
27
27
|
const args = process.argv.slice(2);
|
|
28
28
|
const getArg = (flag, def) => { const i = args.indexOf(flag); return i !== -1 && args[i+1] ? args[i+1] : def; };
|
|
29
29
|
|
|
30
|
-
const PORT = parseInt(getArg('--port',
|
|
31
|
-
const HB_PATH = getArg('--path',
|
|
32
|
-
const SCRIPT = getArg('--script',
|
|
30
|
+
const PORT = parseInt(getArg('--port', process.env.REPORT_SERVER_PORT || '3001'), 10);
|
|
31
|
+
const HB_PATH = getArg('--path', process.env.HB_PATH || '/var/lib/homebridge');
|
|
32
|
+
const SCRIPT = getArg('--script', path.join(__dirname, 'viessmann-report.js'));
|
|
33
33
|
const DEBUG = args.includes('--debug') || process.env.DEBUG_REPORT === '1';
|
|
34
|
+
// --timeout <seconds> passed from plugin config (reportServerTimeout, default 300)
|
|
35
|
+
const TIMEOUT_SEC = parseInt(getArg('--timeout', '300'), 10);
|
|
34
36
|
|
|
35
37
|
// ββ Debug logger βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
36
38
|
function dbg(...parts) {
|
|
@@ -93,7 +95,7 @@ function safeNum(val) {
|
|
|
93
95
|
// CSV files (e.g. 90 days = ~26 000 rows). The default 60 s was insufficient.
|
|
94
96
|
// Raised to 300 s (5 minutes) which comfortably handles the largest datasets.
|
|
95
97
|
|
|
96
|
-
const REPORT_TIMEOUT_MS =
|
|
98
|
+
const REPORT_TIMEOUT_MS = Math.min(Math.max(TIMEOUT_SEC, 60), 1800) * 1000; // from --timeout arg (clamped 60β1800s)
|
|
97
99
|
|
|
98
100
|
function generateReport(params) {
|
|
99
101
|
return new Promise((resolve, reject) => {
|
|
@@ -193,7 +195,7 @@ function generateReport(params) {
|
|
|
193
195
|
|
|
194
196
|
// ββ UI HTML ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
195
197
|
|
|
196
|
-
function buildUI(installations) {
|
|
198
|
+
function buildUI(installations, apiStatus) {
|
|
197
199
|
const today = new Date().toLocaleDateString('en-GB', { day:'2-digit', month:'short', year:'numeric' });
|
|
198
200
|
const instOptions = installations.length === 0
|
|
199
201
|
? '<option value="">No CSV found β check path</option>'
|
|
@@ -201,6 +203,51 @@ function buildUI(installations) {
|
|
|
201
203
|
'<option value="' + id + '">' + (id ? 'Installation ' + id : 'Default (no ID)') + '</option>'
|
|
202
204
|
).join('');
|
|
203
205
|
|
|
206
|
+
|
|
207
|
+
// Build API status card from shared status file
|
|
208
|
+
const apiCardHtml = (() => {
|
|
209
|
+
if (!apiStatus) {
|
|
210
|
+
return `<div class="api-card">
|
|
211
|
+
<div class="api-card-title">π API Usage</div>
|
|
212
|
+
<div class="api-row"><span class="api-lbl">Status</span><span class="api-val" style="color:var(--muted)">No data yet β waiting for first update cycle</span></div>
|
|
213
|
+
</div>`;
|
|
214
|
+
}
|
|
215
|
+
const pct = apiStatus.dailyUsagePct ?? 0;
|
|
216
|
+
const health = apiStatus.healthScore ?? 0;
|
|
217
|
+
const pctCls = pct > 80 ? 'bad' : pct > 50 ? 'warn' : 'ok';
|
|
218
|
+
const hCls = health >= 85 ? 'ok' : health >= 60 ? 'warn' : 'bad';
|
|
219
|
+
const rlTxt = apiStatus.isRateLimited
|
|
220
|
+
? `<span class="api-val bad">π« Rate limited β wait ${apiStatus.rateLimitWaitSec}s</span>`
|
|
221
|
+
: (apiStatus.dailyQuotaExceeded
|
|
222
|
+
? `<span class="api-val bad">π« Daily quota exceeded</span>`
|
|
223
|
+
: `<span class="api-val ok">β OK</span>`);
|
|
224
|
+
const age = Math.round((Date.now() - new Date(apiStatus.timestamp).getTime()) / 1000);
|
|
225
|
+
const ageStr = age < 120 ? `${age}s ago` : `${Math.round(age/60)}m ago`;
|
|
226
|
+
return `<div class="api-card">
|
|
227
|
+
<div class="api-card-title">π API Usage (Viessmann limit: ${apiStatus.dailyLimitTotal ?? 1450} req/day)</div>
|
|
228
|
+
<div class="api-row">
|
|
229
|
+
<span class="api-lbl">Daily usage (est.)</span>
|
|
230
|
+
<span class="api-val">
|
|
231
|
+
<span class="bar-wrap"><span class="bar-fill ${pctCls}" style="width:${Math.min(pct,100)}%"></span></span>
|
|
232
|
+
<span class="${pctCls}">${pct}%</span>
|
|
233
|
+
<span style="color:var(--muted);font-size:10px">(~${apiStatus.dailyEstimatedReqs ?? '?'} req/day)</span>
|
|
234
|
+
</span>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="api-row">
|
|
237
|
+
<span class="api-lbl">Health score</span>
|
|
238
|
+
<span class="api-val">
|
|
239
|
+
<span class="bar-wrap"><span class="bar-fill ${hCls}" style="width:${health}%"></span></span>
|
|
240
|
+
<span class="${hCls}">${health}/100</span>
|
|
241
|
+
<span style="color:var(--muted);font-size:10px">${apiStatus.healthStatus ?? ''}</span>
|
|
242
|
+
</span>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="api-row"><span class="api-lbl">Rate limit</span>${rlTxt}</div>
|
|
245
|
+
<div class="api-row"><span class="api-lbl">Avg response</span><span class="api-val">${apiStatus.avgResponseTime ?? 0} ms</span></div>
|
|
246
|
+
<div class="api-row"><span class="api-lbl">Error rate</span><span class="api-val ${apiStatus.errorRate > 10 ? 'bad' : apiStatus.errorRate > 5 ? 'warn' : 'ok'}">${apiStatus.errorRate ?? 0}%</span></div>
|
|
247
|
+
<div class="api-row"><span class="api-lbl">Last updated</span><span class="api-val" style="color:var(--muted)">${ageStr}</span></div>
|
|
248
|
+
</div>`;
|
|
249
|
+
})();
|
|
250
|
+
|
|
204
251
|
return `<!DOCTYPE html>
|
|
205
252
|
<html lang="en">
|
|
206
253
|
<head>
|
|
@@ -254,6 +301,16 @@ input:focus,select:focus{outline:none;border-color:var(--accent)}
|
|
|
254
301
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
255
302
|
.spinner{display:inline-block;width:13px;height:13px;border:2px solid rgba(249,115,22,.3);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite;vertical-align:middle;margin-right:6px}
|
|
256
303
|
footer{margin-top:36px;font-family:'Space Mono',monospace;font-size:10px;color:var(--muted);text-align:center;opacity:.4}
|
|
304
|
+
.api-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px 20px;width:100%;max-width:560px;margin-bottom:14px}
|
|
305
|
+
.api-card-title{font-family:'Space Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:2px;color:var(--accent);margin-bottom:12px}
|
|
306
|
+
.api-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid var(--border);font-size:12px}
|
|
307
|
+
.api-row:last-child{border-bottom:none}
|
|
308
|
+
.api-lbl{color:var(--muted);font-family:'Space Mono',monospace;font-size:11px}
|
|
309
|
+
.api-val{font-weight:600;font-family:'Space Mono',monospace;font-size:11px}
|
|
310
|
+
.api-val.ok{color:var(--good)}.api-val.warn{color:var(--warn)}.api-val.bad{color:var(--bad)}
|
|
311
|
+
.bar-wrap{width:100px;height:6px;background:var(--border);border-radius:3px;overflow:hidden;display:inline-block;vertical-align:middle;margin-right:6px}
|
|
312
|
+
.bar-fill{height:100%;border-radius:3px}
|
|
313
|
+
.bar-fill.ok{background:var(--good)}.bar-fill.warn{background:var(--warn)}.bar-fill.bad{background:var(--bad)}
|
|
257
314
|
</style>
|
|
258
315
|
</head>
|
|
259
316
|
<body>
|
|
@@ -343,6 +400,7 @@ footer{margin-top:36px;font-family:'Space Mono',monospace;font-size:10px;color:v
|
|
|
343
400
|
Generate Report
|
|
344
401
|
</button>
|
|
345
402
|
<div id="status"></div>
|
|
403
|
+
${apiCardHtml}
|
|
346
404
|
<footer>homebridge-viessmann-vicare Β· report server Β· ${HB_PATH} Β· ${LAN_IP}:${PORT}</footer>
|
|
347
405
|
|
|
348
406
|
<script>
|
|
@@ -422,7 +480,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
422
480
|
|
|
423
481
|
if (pathname === '/' || pathname === '') {
|
|
424
482
|
const installations = detectInstallations();
|
|
425
|
-
const
|
|
483
|
+
const apiStatus = readApiStatus(HB_PATH);
|
|
484
|
+
const html = buildUI(installations, apiStatus);
|
|
426
485
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
427
486
|
res.end(html);
|
|
428
487
|
dbg(`β 200 / (${Date.now()-t0}ms, ${installations.length} installation(s))`);
|
|
@@ -490,7 +549,7 @@ if (require.main === module) {
|
|
|
490
549
|
console.log('[ReportServer] βββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
491
550
|
console.log(`[ReportServer] π Open from any device: http://${LAN_IP}:${PORT}`);
|
|
492
551
|
console.log(`[ReportServer] π Data path: ${HB_PATH}`);
|
|
493
|
-
console.log(`[ReportServer] β±οΈ Report timeout: ${REPORT_TIMEOUT_MS/1000}s`);
|
|
552
|
+
console.log(`[ReportServer] β±οΈ Report timeout: ${REPORT_TIMEOUT_MS/1000}s (--timeout ${TIMEOUT_SEC}s)`);
|
|
494
553
|
if (DEBUG) {
|
|
495
554
|
console.log(`[ReportServer] π Debug mode: ON`);
|
|
496
555
|
console.log(`[ReportServer] Script: ${SCRIPT}`);
|