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.
Files changed (3) hide show
  1. package/io-package.json +27 -1
  2. package/main.js +99 -16
  3. 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.6",
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.6';
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.INFO, CAT.NODE, `Heartbeat`, `${mac} | IP: ${n.ip || '?'} | v${n.version || '?'} | ${n.name || 'unnamed'}`);
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
- log(LVL.DEBUG, CAT.CONNECT, `Ping`, `IP: ${clientIp}`);
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.INFO, CAT.NODE, `Heartbeat`, `${mac} | IP: ${ip} | v${version} | ${name}`);
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.INFO, CAT.NODE, `Config acknowledged`, `${mac} | ack: ${ack}`);
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
- w.document.write(
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
- w.document.close();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.metermaster",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "ioBroker adapter for the MeterMaster Android app \u2013 receives meter readings and manages ESP32 display nodes",
5
5
  "author": {
6
6
  "name": "MPunktBPunkt",