homebridge-viessmann-vicare 2.0.70 → 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/config.schema.json +4 -4
- package/dist/platform.d.ts +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +43 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +2 -2
- package/package.json +1 -1
- package/viessmann-report-server.js +59 -2
package/config.schema.json
CHANGED
|
@@ -520,10 +520,10 @@
|
|
|
520
520
|
"reportServerTimeout": {
|
|
521
521
|
"title": "Report Server Generation Timeout",
|
|
522
522
|
"type": "integer",
|
|
523
|
-
"default":
|
|
524
|
-
"minimum":
|
|
525
|
-
"maximum":
|
|
526
|
-
"description": "Maximum time in seconds
|
|
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."
|
|
527
527
|
}
|
|
528
528
|
},
|
|
529
529
|
"required": [
|
package/dist/platform.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export declare class ViessmannPlatform implements DynamicPlatformPlugin {
|
|
|
57
57
|
private setupRoomSensorDiscovery;
|
|
58
58
|
updateAllDevices(): Promise<void>;
|
|
59
59
|
private updateCycleComplete;
|
|
60
|
+
private writeApiStatusFile;
|
|
60
61
|
private adjustRefreshInterval;
|
|
61
62
|
private sleep;
|
|
62
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
|
@@ -41,6 +41,7 @@ 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
43
|
const room_sensor_accessory_1 = require("./accessories/room-sensor-accessory");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
44
45
|
const settings_1 = require("./settings");
|
|
45
46
|
class ViessmannPlatform {
|
|
46
47
|
log;
|
|
@@ -864,9 +865,51 @@ class ViessmannPlatform {
|
|
|
864
865
|
const healthScore = this.viessmannAPI.getAPIHealthScore();
|
|
865
866
|
const healthStatus = this.viessmannAPI.getAPIHealthStatus();
|
|
866
867
|
this.log.debug(`📊 API Health: ${healthScore}/100 (${healthStatus})`);
|
|
868
|
+
this.writeApiStatusFile(healthScore, healthStatus);
|
|
867
869
|
}
|
|
868
870
|
}
|
|
869
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
|
+
}
|
|
870
913
|
adjustRefreshInterval(increase) {
|
|
871
914
|
if (this.config.enableRateLimitProtection === false) {
|
|
872
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
|
*/
|
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
|
*/
|
|
@@ -61,7 +61,7 @@ exports.DEFAULT_CONFIG = {
|
|
|
61
61
|
deviceUpdateDelay: 1000,
|
|
62
62
|
userAgent: `homebridge-viessmann-vicare/${exports.PLUGIN_VERSION}`,
|
|
63
63
|
},
|
|
64
|
-
reportServerTimeout:
|
|
64
|
+
reportServerTimeout: 600, // seconds — default 10 min; RPi3: ~365s/90days ~1460s/365days
|
|
65
65
|
monitoring: {
|
|
66
66
|
enableHealthReports: true,
|
|
67
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": {
|
|
@@ -195,7 +195,7 @@ function generateReport(params) {
|
|
|
195
195
|
|
|
196
196
|
// ── UI HTML ────────────────────────────────────────────────────────────────
|
|
197
197
|
|
|
198
|
-
function buildUI(installations) {
|
|
198
|
+
function buildUI(installations, apiStatus) {
|
|
199
199
|
const today = new Date().toLocaleDateString('en-GB', { day:'2-digit', month:'short', year:'numeric' });
|
|
200
200
|
const instOptions = installations.length === 0
|
|
201
201
|
? '<option value="">No CSV found — check path</option>'
|
|
@@ -203,6 +203,51 @@ function buildUI(installations) {
|
|
|
203
203
|
'<option value="' + id + '">' + (id ? 'Installation ' + id : 'Default (no ID)') + '</option>'
|
|
204
204
|
).join('');
|
|
205
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
|
+
|
|
206
251
|
return `<!DOCTYPE html>
|
|
207
252
|
<html lang="en">
|
|
208
253
|
<head>
|
|
@@ -256,6 +301,16 @@ input:focus,select:focus{outline:none;border-color:var(--accent)}
|
|
|
256
301
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
257
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}
|
|
258
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)}
|
|
259
314
|
</style>
|
|
260
315
|
</head>
|
|
261
316
|
<body>
|
|
@@ -345,6 +400,7 @@ footer{margin-top:36px;font-family:'Space Mono',monospace;font-size:10px;color:v
|
|
|
345
400
|
Generate Report
|
|
346
401
|
</button>
|
|
347
402
|
<div id="status"></div>
|
|
403
|
+
${apiCardHtml}
|
|
348
404
|
<footer>homebridge-viessmann-vicare · report server · ${HB_PATH} · ${LAN_IP}:${PORT}</footer>
|
|
349
405
|
|
|
350
406
|
<script>
|
|
@@ -424,7 +480,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
424
480
|
|
|
425
481
|
if (pathname === '/' || pathname === '') {
|
|
426
482
|
const installations = detectInstallations();
|
|
427
|
-
const
|
|
483
|
+
const apiStatus = readApiStatus(HB_PATH);
|
|
484
|
+
const html = buildUI(installations, apiStatus);
|
|
428
485
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
429
486
|
res.end(html);
|
|
430
487
|
dbg(`← 200 / (${Date.now()-t0}ms, ${installations.length} installation(s))`);
|