forked from nickbock/com.homewizard
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathapp.js
More file actions
180 lines (158 loc) Β· 6.46 KB
/
app.js
File metadata and controls
180 lines (158 loc) Β· 6.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
* HomeWizard App for Homey
* Copyright (C) 2025 Jeroen Tebbens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
'use strict';
const Homey = require('homey');
const v8 = require('v8');
const Testing = false;
// Helper: log V8 heap + (if available) process RSS. Memory Warning Limit on Homey
// could be RSS-based rather than heap β try both so we can correlate which bucket
// is actually growing.
function logMem(label) {
try {
const hs = v8.getHeapStatistics();
const heap = (hs.used_heap_size / 1024 / 1024).toFixed(1);
const total = (hs.total_heap_size / 1024 / 1024).toFixed(1);
const ext = (hs.external_memory / 1024 / 1024).toFixed(1);
let rssPart = '';
try {
const mu = process.memoryUsage();
const rss = (mu.rss / 1024 / 1024).toFixed(1);
const ab = (mu.arrayBuffers / 1024 / 1024).toFixed(1);
rssPart = ` rss=${rss}MB ab=${ab}MB`;
} catch (_) { /* sandbox blocks rss */ }
console.log(`[MEM] ${label}: heap=${heap}/${total}MB ext=${ext}MB${rssPart}`);
} catch (e) {
console.log(`[MEM] ${label}: unavailable (${e.message})`);
}
}
// Global device counter β each driver bumps on onInit so the runtime log shows
// which driver-types contribute how many instances. Useful for triaging crashes
// from users whose device mix differs from ours (e.g. the other user has 2Γ
// plugin_battery, we don't).
const _deviceCounts = {};
function bumpDeviceCount(driverId) {
_deviceCounts[driverId] = (_deviceCounts[driverId] || 0) + 1;
}
function logDeviceCounts() {
const entries = Object.entries(_deviceCounts);
if (entries.length === 0) return;
const summary = entries
.sort((a, b) => b[1] - a[1])
.map(([k, v]) => `${k}=${v}`)
.join(' ');
console.log(`[MEM] devices: ${summary}`);
}
class HomeWizardApp extends Homey.App {
// Exposed for drivers: this.homey.app.logMem('my-driver:event-name')
logMem(label) { logMem(label); }
bumpDeviceCount(driverId) { bumpDeviceCount(driverId); }
async onInit() {
this.log('HomeWizard app ready!');
this.baseloadMonitor = null;
this.p1Source = null;
// π CRASH DIAGNOSTICS: Global error handlers
this._setupGlobalErrorHandlers();
// π MEMORY DIAGNOSTICS: Log heap every 5s for first 3 minutes (startup cascade),
// then every 60s indefinitely (runtime tracking β needed because reported crashes
// happen 5β20 minutes into runtime, well after the original 3-min monitor stopped).
let _memCount = 0;
logMem('app-start');
this._memInterval = setInterval(() => {
_memCount++;
logMem(`T+${_memCount * 5}s`);
if (_memCount >= 36) { // 3 minutes β switch to low-frequency runtime monitor
clearInterval(this._memInterval);
console.log('[MEM] Startup monitor stopped β switching to 60s runtime monitor');
let _runtimeCount = 0;
this._memInterval = setInterval(() => {
_runtimeCount++;
logMem(`run+${_runtimeCount}min`);
if (_runtimeCount === 1 || _runtimeCount % 10 === 0) logDeviceCounts();
}, 60_000);
}
}, 5000);
if (process.env.DEBUG === '1' && Testing) {
try {
require('inspector').waitForDebugger();
}
catch (error) {
require('inspector').open(9225, '0.0.0.0', true);
}
}
// On version change: remove orphaned settings keys left by deleted devices.
// Deferred 30s so all drivers and devices have finished initializing.
const currentVersion = require('./app.json').version;
const lastVersion = this.homey.settings.get('_hw_app_version');
if (lastVersion !== currentVersion) {
this.log(`[MIGRATE] Version change: ${lastVersion || 'new install'} β ${currentVersion}`);
setTimeout(() => this._runSettingsMigration(currentVersion), 30_000);
}
}
_runSettingsMigration(currentVersion) {
try {
const driver = this.homey.drivers.getDriver('battery-policy');
const activeIds = new Set(driver.getDevices().map(d => d.getData().id));
if (typeof this.homey.settings.getAll !== 'function') return;
const all = this.homey.settings.getAll();
let removed = 0;
for (const key of Object.keys(all)) {
if (key.startsWith('batt_mode_hist_')) {
const id = key.replace('batt_mode_hist_', '');
if (!activeIds.has(id)) {
try { this.homey.settings.unset(key); removed++; } catch (_) {}
}
}
}
if (removed > 0) this.log(`[MIGRATE] Removed ${removed} orphaned batt_mode_hist key(s)`);
} catch (e) {
this.error('[MIGRATE] Settings migration failed:', e.message);
}
try { this.homey.settings.set('_hw_app_version', currentVersion); } catch (_) {}
}
_setupGlobalErrorHandlers() {
// Track unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('π₯ UNHANDLED PROMISE REJECTION:');
console.error(' Promise:', promise);
console.error(' Reason:', reason?.stack || reason);
// Log to Homey
this.error('π₯ Unhandled Promise Rejection:', reason?.stack || reason);
});
// Track uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('π₯ UNCAUGHT EXCEPTION:');
console.error(' Error:', err?.stack || err);
// Log to Homey
this.error('π₯ Uncaught Exception:', err?.stack || err);
});
// Track warning events (like MaxListenersExceededWarning)
process.on('warning', (warning) => {
console.warn('β οΈ PROCESS WARNING:', warning.name, warning.message);
console.warn(' Stack:', warning.stack);
this.log('β οΈ Warning:', warning.name, warning.message);
});
this.log('β
Global error handlers installed');
}
async onUninit() {
if (this._memInterval) {
clearInterval(this._memInterval);
this._memInterval = null;
}
}
}
module.exports = HomeWizardApp;