iobroker.metermaster 0.9.6 → 0.9.8
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/io-package.json +27 -1
- package/main.js +99 -16
- package/package.json +1 -1
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "metermaster",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.8",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.9.8": {
|
|
7
|
+
"en": "Log MeterMaster app connection tests from User-Agent at info level",
|
|
8
|
+
"de": "Verbindungstests der MeterMaster-App per User-Agent auf Info-Level loggen",
|
|
9
|
+
"ru": "Log app connection tests via User-Agent",
|
|
10
|
+
"pt": "Log app connection tests via User-Agent",
|
|
11
|
+
"nl": "Log app connection tests via User-Agent",
|
|
12
|
+
"fr": "Log app connection tests via User-Agent",
|
|
13
|
+
"it": "Log app connection tests via User-Agent",
|
|
14
|
+
"es": "Log app connection tests via User-Agent",
|
|
15
|
+
"pl": "Log app connection tests via User-Agent",
|
|
16
|
+
"uk": "Log app connection tests via User-Agent",
|
|
17
|
+
"zh-cn": "通过 User-Agent 记录应用连接测试"
|
|
18
|
+
},
|
|
19
|
+
"0.9.7": {
|
|
20
|
+
"en": "Print fix (Blob URL); ESP32 discover proxy (getStates/node-discover); node heartbeat/ack logs moved to debug",
|
|
21
|
+
"de": "Druck-Fix (Blob-URL); ESP32-Discover-Proxy (getStates/node-discover); Node-Heartbeat/Ack-Logs auf Debug-Level",
|
|
22
|
+
"ru": "Print fix; ESP32 discover proxy; node logs debug",
|
|
23
|
+
"pt": "Print fix; ESP32 discover proxy; node logs debug",
|
|
24
|
+
"nl": "Print fix; ESP32 discover proxy; node logs debug",
|
|
25
|
+
"fr": "Print fix; ESP32 discover proxy; node logs debug",
|
|
26
|
+
"it": "Print fix; ESP32 discover proxy; node logs debug",
|
|
27
|
+
"es": "Print fix; ESP32 discover proxy; node logs debug",
|
|
28
|
+
"pl": "Print fix; ESP32 discover proxy; node logs debug",
|
|
29
|
+
"uk": "Print fix; ESP32 discover proxy; node logs debug",
|
|
30
|
+
"zh-cn": "打印修复;ESP32 discover 代理;节点日志改为 debug"
|
|
31
|
+
},
|
|
6
32
|
"0.9.6": {
|
|
7
33
|
"en": "Assign display nodes via chips on meter cards in the Data tab",
|
|
8
34
|
"de": "Anzeige-Nodes per Chips auf den Zähler-Kacheln im Daten-Tab zuweisen",
|
package/main.js
CHANGED
|
@@ -4,7 +4,7 @@ const utils = require('@iobroker/adapter-core');
|
|
|
4
4
|
const http = require('node:http');
|
|
5
5
|
const crypto = require('node:crypto');
|
|
6
6
|
const https = require('node:https');
|
|
7
|
-
const CURRENT_VERSION = '0.9.
|
|
7
|
+
const CURRENT_VERSION = '0.9.8';
|
|
8
8
|
const GITHUB_REPO = 'MPunktBPunkt/ioBroker.metermaster';
|
|
9
9
|
const GITHUB_URL = 'https://github.com/MPunktBPunkt/ioBroker.metermaster';
|
|
10
10
|
|
|
@@ -142,7 +142,7 @@ adapter.on('stateChange', async (id, state) => {
|
|
|
142
142
|
|
|
143
143
|
if (field === 'lastSeen') {
|
|
144
144
|
const n = nodesCache[mac];
|
|
145
|
-
log(LVL.
|
|
145
|
+
log(LVL.DEBUG, CAT.NODE, `Heartbeat`, `${mac} | IP: ${n.ip || '?'} | v${n.version || '?'} | ${n.name || 'unnamed'}`);
|
|
146
146
|
await ensureNodeStates(mac);
|
|
147
147
|
}
|
|
148
148
|
});
|
|
@@ -271,6 +271,14 @@ function startHttpServer() {
|
|
|
271
271
|
if (req.method === 'GET' && url === '/api/version') { serveVersion(res); return; }
|
|
272
272
|
if (req.method === 'GET' && url === '/api/nodes') { serveNodesJson(res); return; }
|
|
273
273
|
if (req.method === 'GET' && url === '/api/discover'){ serveDiscoverJson(res); return; }
|
|
274
|
+
if (req.method === 'GET' && url === '/api/node-discover'){ serveNodeDiscoverJson(res); return; }
|
|
275
|
+
if (req.method === 'GET' && (url === '/getStates/metermaster.*' || url === '/getStates/metermaster.0.*.readings.latest')) {
|
|
276
|
+
serveGetStatesLatest(res); return;
|
|
277
|
+
}
|
|
278
|
+
const getStateMatch = req.method === 'GET' ? url.match(/^\/get\/(.+)$/) : null;
|
|
279
|
+
if (getStateMatch) {
|
|
280
|
+
serveGetState(decodeURIComponent(getStateMatch[1]), res); return;
|
|
281
|
+
}
|
|
274
282
|
|
|
275
283
|
// ESP32 node registration (no auth – ESP32 has no credentials)
|
|
276
284
|
if (req.method === 'POST' && url === '/api/register') {
|
|
@@ -319,7 +327,7 @@ function startHttpServer() {
|
|
|
319
327
|
log(LVL.DEBUG, CAT.AUTH, `Auth OK`, `IP: ${clientIp} | User: "${reqUser}"`);
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
if (req.method === 'GET' && url === '/api/ping') { handlePing(res, clientIp); }
|
|
330
|
+
if (req.method === 'GET' && url === '/api/ping') { handlePing(req, res, clientIp); return; }
|
|
323
331
|
else if ((req.method === 'POST' || req.method === 'PUT') && url === '/api/reading') {
|
|
324
332
|
readBody(req, b => handleReading(b, res, clientIp));
|
|
325
333
|
}
|
|
@@ -354,8 +362,14 @@ function startHttpServer() {
|
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
// ─── Ping ─────────────────────────────────────────────────────────────────────
|
|
357
|
-
function handlePing(res, clientIp) {
|
|
358
|
-
|
|
365
|
+
function handlePing(req, res, clientIp) {
|
|
366
|
+
const ua = String(req.headers['user-agent'] || '');
|
|
367
|
+
const appMatch = ua.match(/MeterMaster\/([\d.]+)/i);
|
|
368
|
+
if (appMatch) {
|
|
369
|
+
log(LVL.INFO, CAT.CONNECT, `App connection test`, `MeterMaster ${appMatch[1]} | IP: ${clientIp}`);
|
|
370
|
+
} else {
|
|
371
|
+
log(LVL.DEBUG, CAT.CONNECT, `Ping`, `IP: ${clientIp}${ua ? ` | UA: ${ua.slice(0, 80)}` : ''}`);
|
|
372
|
+
}
|
|
359
373
|
sendJson(res, 200, { ok: true, adapter: 'metermaster', version: CURRENT_VERSION, received: readingsReceived });
|
|
360
374
|
}
|
|
361
375
|
|
|
@@ -713,6 +727,73 @@ function serveDiscoverJson(res) {
|
|
|
713
727
|
sendJson(res, 200, result);
|
|
714
728
|
}
|
|
715
729
|
|
|
730
|
+
// ─── simple-api-kompatibel: nur readings.latest (für ESP32-Nodes mit kleinem JSON-Puffer) ─
|
|
731
|
+
function serveGetStatesLatest(res) {
|
|
732
|
+
const result = {};
|
|
733
|
+
const ns = adapter.namespace;
|
|
734
|
+
const from = `system.adapter.${adapter.name}.${adapter.instance}`;
|
|
735
|
+
const now = Date.now();
|
|
736
|
+
for (const [house, apts] of Object.entries(receivedData)) {
|
|
737
|
+
for (const [apt, meters] of Object.entries(apts)) {
|
|
738
|
+
for (const [meter, data] of Object.entries(meters)) {
|
|
739
|
+
const ts = data.latestDate ? new Date(data.latestDate).getTime() : now;
|
|
740
|
+
result[`${ns}.${house}.${apt}.${meter}.readings.latest`] = {
|
|
741
|
+
val: data.latest,
|
|
742
|
+
ack: true,
|
|
743
|
+
ts,
|
|
744
|
+
q: 0,
|
|
745
|
+
from,
|
|
746
|
+
lc: ts,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
sendJson(res, 200, result);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function serveGetState(stateId, res) {
|
|
755
|
+
try {
|
|
756
|
+
const state = await adapter.getStateAsync(stateId);
|
|
757
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
758
|
+
sendJson(res, 404, { error: 'State not found' });
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
sendJson(res, 200, {
|
|
762
|
+
val: state.val,
|
|
763
|
+
ack: !!state.ack,
|
|
764
|
+
ts: state.ts,
|
|
765
|
+
q: state.q || 0,
|
|
766
|
+
from: state.from || '',
|
|
767
|
+
lc: state.lc || state.ts,
|
|
768
|
+
});
|
|
769
|
+
} catch (e) {
|
|
770
|
+
sendJson(res, 500, { error: e.message });
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ─── ESP32-WebUI: kompaktes Discover-Format {"ok":true,"states":[...]} ───────
|
|
775
|
+
function serveNodeDiscoverJson(res) {
|
|
776
|
+
const states = [];
|
|
777
|
+
const ns = adapter.namespace;
|
|
778
|
+
for (const [house, apts] of Object.entries(receivedData)) {
|
|
779
|
+
for (const [apt, meters] of Object.entries(apts)) {
|
|
780
|
+
for (const [meter, data] of Object.entries(meters)) {
|
|
781
|
+
const sid = `${ns}.${house}.${apt}.${meter}.readings.latest`;
|
|
782
|
+
let lbl = house;
|
|
783
|
+
if (apt) lbl += ' \u00B7 ' + apt;
|
|
784
|
+
if (meter) lbl += ' \u00B7 ' + meter;
|
|
785
|
+
states.push({
|
|
786
|
+
id: sid,
|
|
787
|
+
val: data.latest,
|
|
788
|
+
label: lbl,
|
|
789
|
+
unit: data.unit || '',
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
sendJson(res, 200, { ok: true, states });
|
|
795
|
+
}
|
|
796
|
+
|
|
716
797
|
// ─── Node Config schreiben ────────────────────────────────────────────────────
|
|
717
798
|
async function handleNodeConfig(mac, body, res, clientIp) {
|
|
718
799
|
let data;
|
|
@@ -801,7 +882,7 @@ async function handleNodeRegister(body, res, clientIp) {
|
|
|
801
882
|
await adapter.setStateAsync(`nodes.${mac}.lastSeen`, { val: ts, ack: true });
|
|
802
883
|
if (ack) await adapter.setStateAsync(`nodes.${mac}.configAck`, { val: ack, ack: true });
|
|
803
884
|
|
|
804
|
-
log(LVL.
|
|
885
|
+
log(LVL.DEBUG, CAT.NODE, `Heartbeat`, `${mac} | IP: ${ip} | v${version} | ${name}`);
|
|
805
886
|
|
|
806
887
|
// Aktuelle Config zurückgeben (null wenn noch keine gesetzt)
|
|
807
888
|
const config = nodesCache[mac].config || null;
|
|
@@ -825,7 +906,7 @@ async function handleNodeAck(mac, body, res) {
|
|
|
825
906
|
try {
|
|
826
907
|
await ensureNodeStates(mac);
|
|
827
908
|
await adapter.setStateAsync(`nodes.${mac}.configAck`, { val: ack, ack: true });
|
|
828
|
-
log(LVL.
|
|
909
|
+
log(LVL.DEBUG, CAT.NODE, `Config acknowledged`, `${mac} | ack: ${ack}`);
|
|
829
910
|
sendJson(res, 200, { ok: true });
|
|
830
911
|
} catch (e) {
|
|
831
912
|
sendJson(res, 500, { error: e.message });
|
|
@@ -2149,13 +2230,8 @@ function closeChartModal() {
|
|
|
2149
2230
|
}
|
|
2150
2231
|
|
|
2151
2232
|
function openPrintWindow(title, bodyHtml) {
|
|
2152
|
-
const w = window.open('', '_blank', 'noopener,noreferrer');
|
|
2153
|
-
if (!w) {
|
|
2154
|
-
window.alert(t('error'));
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
2233
|
const generated = new Date().toLocaleString(localeTag(), { hour12: false });
|
|
2158
|
-
|
|
2234
|
+
const html =
|
|
2159
2235
|
'<!DOCTYPE html><html><head><meta charset="utf-8"><title>'+esc(title)+'</title>'+
|
|
2160
2236
|
'<style>'+
|
|
2161
2237
|
'body{font-family:Segoe UI,Helvetica,Arial,sans-serif;color:#111;margin:24px;}'+
|
|
@@ -2172,9 +2248,16 @@ function openPrintWindow(title, bodyHtml) {
|
|
|
2172
2248
|
'<div class="meta">'+esc(t('print_generated'))+': '+esc(generated)+'</div>'+
|
|
2173
2249
|
bodyHtml+
|
|
2174
2250
|
'<script>window.onload=function(){window.focus();window.print();}<\\/script>'+
|
|
2175
|
-
'</body></html>'
|
|
2176
|
-
);
|
|
2177
|
-
|
|
2251
|
+
'</body></html>';
|
|
2252
|
+
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
|
|
2253
|
+
const url = URL.createObjectURL(blob);
|
|
2254
|
+
const w = window.open(url, '_blank');
|
|
2255
|
+
if (!w) {
|
|
2256
|
+
URL.revokeObjectURL(url);
|
|
2257
|
+
window.alert(t('error'));
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
2178
2261
|
}
|
|
2179
2262
|
|
|
2180
2263
|
window.printChartView = function printChartView() {
|
package/package.json
CHANGED