node-red-contrib-senec-cloud-v2 0.2.0 → 0.2.2

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 (37) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/assets/fonts/senec-sans.ttf +0 -0
  3. package/dist/lib/dashboard-html-renderer.d.ts +18 -0
  4. package/dist/lib/dashboard-html-renderer.d.ts.map +1 -0
  5. package/dist/lib/dashboard-html-renderer.js +604 -0
  6. package/dist/lib/dashboard-html-renderer.js.map +1 -0
  7. package/dist/lib/dashboard-layout.d.ts +152 -0
  8. package/dist/lib/dashboard-layout.d.ts.map +1 -0
  9. package/dist/lib/dashboard-layout.js +201 -0
  10. package/dist/lib/dashboard-layout.js.map +1 -0
  11. package/dist/lib/geocoding-client.d.ts +61 -0
  12. package/dist/lib/geocoding-client.d.ts.map +1 -0
  13. package/dist/lib/geocoding-client.js +77 -0
  14. package/dist/lib/geocoding-client.js.map +1 -0
  15. package/dist/lib/senec-image-renderer.d.ts +107 -0
  16. package/dist/lib/senec-image-renderer.d.ts.map +1 -0
  17. package/dist/lib/senec-image-renderer.js +872 -0
  18. package/dist/lib/senec-image-renderer.js.map +1 -0
  19. package/dist/lib/senec-layout.d.ts +212 -0
  20. package/dist/lib/senec-layout.d.ts.map +1 -0
  21. package/dist/lib/senec-layout.js +252 -0
  22. package/dist/lib/senec-layout.js.map +1 -0
  23. package/dist/lib/weather-client.d.ts +62 -0
  24. package/dist/lib/weather-client.d.ts.map +1 -0
  25. package/dist/lib/weather-client.js +234 -0
  26. package/dist/lib/weather-client.js.map +1 -0
  27. package/dist/nodes/senec-data.js +10 -2
  28. package/dist/nodes/senec-data.js.map +1 -1
  29. package/dist/nodes/senec-image.html +73 -53
  30. package/dist/nodes/senec-image.js +189 -14
  31. package/dist/nodes/senec-image.js.map +1 -1
  32. package/dist/nodes/weather.d.ts +2 -0
  33. package/dist/nodes/weather.d.ts.map +1 -0
  34. package/dist/nodes/weather.html +179 -0
  35. package/dist/nodes/weather.js +138 -0
  36. package/dist/nodes/weather.js.map +1 -0
  37. package/package.json +4 -2
@@ -1,31 +1,206 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const senec_image_renderer_1 = require("../lib/senec-image-renderer");
4
+ const dashboard_html_renderer_1 = require("../lib/dashboard-html-renderer");
5
+ const dashboard_layout_1 = require("../lib/dashboard-layout");
6
+ /**
7
+ * Coerce an arbitrary payload into a SenecData object. Kept permissive so
8
+ * partial payloads still render.
9
+ */
10
+ function coerceSenecData(payload) {
11
+ const num = (v) => (typeof v === 'number' && isFinite(v) ? v : 0);
12
+ const branch = (b) => ({
13
+ today: num(b?.today),
14
+ now: num(b?.now),
15
+ });
16
+ return {
17
+ steuereinheitState: payload?.steuereinheitState ?? 'OK',
18
+ gridimport: branch(payload?.gridimport),
19
+ powergenerated: branch(payload?.powergenerated),
20
+ consumption: branch(payload?.consumption),
21
+ gridexport: branch(payload?.gridexport),
22
+ acculevel: { now: num(payload?.acculevel?.now) },
23
+ };
24
+ }
25
+ /** Heuristic: does this payload look like a SENEC energy payload? */
26
+ function looksLikeSenec(payload) {
27
+ return (payload &&
28
+ typeof payload === 'object' &&
29
+ ('powergenerated' in payload ||
30
+ 'acculevel' in payload ||
31
+ 'gridimport' in payload ||
32
+ 'consumption' in payload));
33
+ }
34
+ /** Heuristic: does this payload look like a weather payload? */
35
+ function looksLikeWeather(payload) {
36
+ return (payload &&
37
+ typeof payload === 'object' &&
38
+ ('temperature' in payload || 'forecast' in payload || 'condition' in payload));
39
+ }
40
+ /**
41
+ * Clamp an untrusted string to a maximum length to bound the size of the
42
+ * rendered HTML/PNG (defense against oversized upstream payloads).
43
+ */
44
+ function safeStr(value, max = 64) {
45
+ const s = value === undefined || value === null ? '' : String(value);
46
+ return s.length > max ? s.slice(0, max) : s;
47
+ }
48
+ function safeNum(value) {
49
+ const n = Number(value);
50
+ return isFinite(n) ? n : 0;
51
+ }
52
+ function coerceWeatherData(payload) {
53
+ return {
54
+ temperature: safeNum(payload?.temperature),
55
+ condition: safeStr(payload?.condition, 64),
56
+ icon: safeStr(payload?.icon, 16),
57
+ tempMax: safeNum(payload?.tempMax),
58
+ tempMin: safeNum(payload?.tempMin),
59
+ humidity: safeNum(payload?.humidity),
60
+ windSpeed: safeNum(payload?.windSpeed),
61
+ windDirection: safeStr(payload?.windDirection, 8),
62
+ // Cap the forecast to 5 entries to bound render size.
63
+ forecast: Array.isArray(payload?.forecast)
64
+ ? payload.forecast.slice(0, 5).map((f) => ({
65
+ day: safeStr(f?.day, 8),
66
+ icon: safeStr(f?.icon, 16),
67
+ temp: safeNum(f?.temp),
68
+ }))
69
+ : [],
70
+ location: payload?.location !== undefined ? safeStr(payload.location, 48) : undefined,
71
+ latitude: payload?.latitude !== undefined ? safeNum(payload.latitude) : undefined,
72
+ longitude: payload?.longitude !== undefined ? safeNum(payload.longitude) : undefined,
73
+ };
74
+ }
3
75
  module.exports = function (RED) {
4
76
  function SenecImageNode(config) {
5
77
  RED.nodes.createNode(this, config);
6
78
  const node = this;
7
- node.width = config.width || 800;
8
- node.height = config.height || 480;
9
- node.layout = config.layout || 'default';
10
- node.on('input', async function (msg) {
79
+ node.width = config.width || 1600;
80
+ node.height = config.height || 0; // 0 => derive from 16:9 aspect ratio
81
+ node.layout = config.layout || 'dashboard';
82
+ node.outputFormat = config.outputFormat || 'buffer';
83
+ node.filePath = config.filePath;
84
+ node.lastSenec = null;
85
+ node.lastWeather = null;
86
+ node.lastSenecStatus = (0, dashboard_layout_1.unknownStatus)();
87
+ node.lastWeatherStatus = (0, dashboard_layout_1.unknownStatus)();
88
+ /**
89
+ * Determine which input the message arrived on.
90
+ *
91
+ * Node-RED delivers all inputs to the single input handler. We identify
92
+ * the source by (in priority order):
93
+ * 1. Explicit port index provided by the runtime (msg._input?.index).
94
+ * 2. The message topic ("weather/..." vs "senec/...").
95
+ * 3. The payload shape.
96
+ */
97
+ function classify(msg) {
98
+ const idx = typeof msg?._input?.index === 'number'
99
+ ? msg._input.index
100
+ : typeof msg?.inputIndex === 'number'
101
+ ? msg.inputIndex
102
+ : undefined;
103
+ if (idx === 0) {
104
+ return 'senec';
105
+ }
106
+ if (idx === 1) {
107
+ return 'weather';
108
+ }
109
+ const topic = String(msg?.topic || '').toLowerCase();
110
+ if (topic.includes('weather') || topic.includes('wetter')) {
111
+ return 'weather';
112
+ }
113
+ if (topic.includes('senec') || topic.includes('energy')) {
114
+ return 'senec';
115
+ }
116
+ if (looksLikeWeather(msg?.payload) && !looksLikeSenec(msg?.payload)) {
117
+ return 'weather';
118
+ }
119
+ if (looksLikeSenec(msg?.payload)) {
120
+ return 'senec';
121
+ }
122
+ return 'unknown';
123
+ }
124
+ node.on('input', async function (msg, send, done) {
125
+ send = send || node.send.bind(node);
126
+ done =
127
+ done ||
128
+ function (err) {
129
+ if (err) {
130
+ node.error(err, msg);
131
+ }
132
+ };
11
133
  try {
12
- if (!msg.payload || typeof msg.payload !== 'object') {
13
- node.error('Input payload must be a SENEC data object');
134
+ const source = classify(msg);
135
+ const incomingStatus = msg.status && typeof msg.status === 'object' ? msg.status : undefined;
136
+ // A status-only message (payload null/absent) still updates health.
137
+ const hasData = msg.payload && typeof msg.payload === 'object';
138
+ if (!hasData && !incomingStatus) {
139
+ node.status({ fill: 'red', shape: 'ring', text: 'invalid payload' });
140
+ done(new Error('Input payload must be a SENEC or Weather data object'));
141
+ return;
142
+ }
143
+ // Update cached data + status based on the source.
144
+ if (source === 'weather') {
145
+ if (hasData) {
146
+ node.lastWeather = coerceWeatherData(msg.payload);
147
+ }
148
+ node.lastWeatherStatus =
149
+ incomingStatus ??
150
+ { ok: hasData, timestamp: new Date().toISOString(), fallback: false };
151
+ }
152
+ else {
153
+ if (hasData) {
154
+ node.lastSenec = coerceSenecData(msg.payload);
155
+ }
156
+ node.lastSenecStatus =
157
+ incomingStatus ??
158
+ { ok: hasData, timestamp: new Date().toISOString(), fallback: false };
159
+ }
160
+ // Require at least SENEC data before rendering.
161
+ if (!node.lastSenec) {
162
+ node.status({ fill: 'yellow', shape: 'ring', text: 'waiting for SENEC data' });
163
+ done();
14
164
  return;
15
165
  }
16
166
  node.status({ fill: 'blue', shape: 'dot', text: 'generating...' });
17
- // TODO: Implement image generation using pureimage
18
- // For now, send a placeholder response
19
- const imageBuffer = Buffer.from('PNG image generation not yet implemented', 'utf-8');
20
- msg.payload = imageBuffer;
21
- msg.contentType = 'image/png';
22
- msg.filename = `senec-${Date.now()}.png`;
23
- node.send(msg);
167
+ const statuses = {
168
+ senec: node.lastSenecStatus,
169
+ weather: node.lastWeatherStatus,
170
+ };
171
+ const model = (0, dashboard_layout_1.buildDashboardModel)(node.lastSenec, node.lastWeather, statuses);
172
+ const renderer = new senec_image_renderer_1.SenecImageRenderer(node.width, node.height && node.height > 0 ? node.height : undefined);
173
+ const imageBuffer = await renderer.render(model);
174
+ const html = (0, dashboard_html_renderer_1.renderDashboardHtml)(model);
175
+ const filename = `energy-dashboard-${Date.now()}.png`;
176
+ // Build the PNG message for output 1.
177
+ const pngMsg = { ...msg, contentType: 'image/png', filename, status: statuses };
178
+ if (node.outputFormat === 'base64') {
179
+ pngMsg.payload = `data:image/png;base64,${imageBuffer.toString('base64')}`;
180
+ }
181
+ else if (node.outputFormat === 'file' && node.filePath) {
182
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
183
+ const fs = require('fs').promises;
184
+ await fs.writeFile(node.filePath, imageBuffer);
185
+ pngMsg.payload = node.filePath;
186
+ }
187
+ else {
188
+ pngMsg.payload = imageBuffer;
189
+ }
190
+ // Build the HTML message for output 2.
191
+ const htmlMsg = {
192
+ ...msg,
193
+ payload: html,
194
+ contentType: 'text/html',
195
+ status: statuses,
196
+ };
197
+ send([pngMsg, htmlMsg]);
24
198
  node.status({ fill: 'green', shape: 'dot', text: 'success' });
199
+ done();
25
200
  }
26
201
  catch (error) {
27
- node.error('Failed to generate image: ' + error.message);
28
202
  node.status({ fill: 'red', shape: 'ring', text: 'error' });
203
+ done(new Error('Failed to generate image: ' + error.message));
29
204
  }
30
205
  });
31
206
  }
@@ -1 +1 @@
1
- {"version":3,"file":"senec-image.js","sourceRoot":"","sources":["../../src/nodes/senec-image.ts"],"names":[],"mappings":";;AAiBA,MAAM,CAAC,OAAO,GAAG,UAAS,GAAQ;IAChC,SAAS,cAAc,CAAuB,MAAyB;QACrE,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAEnC,MAAM,IAAI,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC;QAEzC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,WAAU,GAAQ;YACtC,IAAI,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;oBACpD,IAAI,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;oBACxD,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;gBAEnE,mDAAmD;gBACnD,uCAAuC;gBACvC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE,OAAO,CAAC,CAAC;gBAErF,GAAG,CAAC,OAAO,GAAG,WAAW,CAAC;gBAC1B,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;gBAC9B,GAAG,CAAC,QAAQ,GAAG,SAAS,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;gBAEzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YAEhE,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,IAAI,CAAC,KAAK,CAAC,4BAA4B,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzD,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AACxD,CAAC,CAAC"}
1
+ {"version":3,"file":"senec-image.js","sourceRoot":"","sources":["../../src/nodes/senec-image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=weather.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weather.d.ts","sourceRoot":"","sources":["../../src/nodes/weather.ts"],"names":[],"mappings":""}
@@ -0,0 +1,179 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('weather', {
3
+ category: 'SENEC',
4
+ color: '#7FC7EE',
5
+ defaults: {
6
+ name: { value: '' },
7
+ latitude: { value: '', validate: RED.validators.number(true) },
8
+ longitude: { value: '', validate: RED.validators.number(true) },
9
+ language: { value: 'de' },
10
+ location: { value: '' },
11
+ interval: { value: 0, validate: RED.validators.number() }
12
+ },
13
+ inputs: 1,
14
+ outputs: 1,
15
+ icon: 'font-awesome/fa-cloud',
16
+ label: function () {
17
+ return this.name || 'Weather';
18
+ },
19
+ labelStyle: function () {
20
+ return this.name ? 'node_label_italic' : '';
21
+ },
22
+ oneditprepare: function () {
23
+ var langEl = $('#node-input-language');
24
+ var statusEl = $('#node-geocode-status');
25
+ var resultsEl = $('#node-geocode-results');
26
+
27
+ function setStatus(text, color) {
28
+ statusEl.text(text || '').css('color', color || '');
29
+ }
30
+
31
+ function applyResult(r) {
32
+ $('#node-input-latitude').val(r.latitude);
33
+ $('#node-input-longitude').val(r.longitude);
34
+ if (r.label) {
35
+ $('#node-input-location').val(r.label);
36
+ }
37
+ resultsEl.empty();
38
+ setStatus('Set to ' + (r.label || (r.latitude + ', ' + r.longitude)), '#4a9c4a');
39
+ }
40
+
41
+ function renderResults(results) {
42
+ resultsEl.empty();
43
+ if (!results || results.length === 0) {
44
+ return;
45
+ }
46
+ // Show candidate names so the user can pick the right place,
47
+ // which also helps when the typed input is ambiguous or wrong.
48
+ var hint = $('<div>')
49
+ .css({ 'font-size': '12px', color: '#888', 'margin-bottom': '4px' })
50
+ .text(results.length === 1 ? 'Match found:' : 'Did you mean:');
51
+ resultsEl.append(hint);
52
+ results.forEach(function (r) {
53
+ var coords = Number(r.latitude).toFixed(3) + ', ' + Number(r.longitude).toFixed(3);
54
+ var btn = $('<button type="button" class="red-ui-button"></button>')
55
+ .css({ display: 'block', width: '100%', 'text-align': 'left', 'margin-bottom': '3px' })
56
+ .html('<b>' + $('<div>').text(r.label).html() + '</b> <span style="color:#888">(' + coords + ')</span>')
57
+ .on('click', function () { applyResult(r); });
58
+ resultsEl.append(btn);
59
+ });
60
+ }
61
+
62
+ $('#node-geocode-lookup').on('click', function () {
63
+ var q = ($('#node-input-location').val() || '').toString().trim();
64
+ resultsEl.empty();
65
+ if (!q) {
66
+ setStatus('Enter a location name first', '#c00');
67
+ return;
68
+ }
69
+ setStatus('Searching...', '#888');
70
+ var lang = (langEl.val() || 'de');
71
+ $.getJSON('senec/geocode', { q: q, lang: lang })
72
+ .done(function (data) {
73
+ var results = (data && data.results) || [];
74
+ if (results.length === 0) {
75
+ setStatus('No matches for "' + q + '". Try a different spelling.', '#c00');
76
+ return;
77
+ }
78
+ if (results.length === 1) {
79
+ applyResult(results[0]);
80
+ return;
81
+ }
82
+ setStatus(results.length + ' matches found - pick one below.', '#888');
83
+ renderResults(results);
84
+ })
85
+ .fail(function (jqxhr) {
86
+ var msg = (jqxhr.responseJSON && jqxhr.responseJSON.error) || 'Lookup failed';
87
+ setStatus(msg, '#c00');
88
+ });
89
+ });
90
+ }
91
+ });
92
+ </script>
93
+
94
+ <script type="text/html" data-template-name="weather">
95
+ <div class="form-row">
96
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
97
+ <input type="text" id="node-input-name" placeholder="Weather">
98
+ </div>
99
+ <div class="form-row">
100
+ <label for="node-input-language"><i class="fa fa-language"></i> Language</label>
101
+ <select id="node-input-language">
102
+ <option value="de">Deutsch</option>
103
+ <option value="en">English</option>
104
+ </select>
105
+ </div>
106
+ <div class="form-row">
107
+ <label for="node-input-location"><i class="fa fa-map-signs"></i> Location</label>
108
+ <input type="text" id="node-input-location" placeholder="Berlin" style="width: 55%;">
109
+ <button type="button" class="red-ui-button" id="node-geocode-lookup" style="margin-left: 6px;">
110
+ <i class="fa fa-search"></i> Look up
111
+ </button>
112
+ </div>
113
+ <div class="form-row" style="margin-top: -8px;">
114
+ <label>&nbsp;</label>
115
+ <span id="node-geocode-status" style="font-size: 12px;"></span>
116
+ </div>
117
+ <div class="form-row">
118
+ <label>&nbsp;</label>
119
+ <div id="node-geocode-results" style="max-width: 70%;"></div>
120
+ </div>
121
+ <div class="form-row">
122
+ <label for="node-input-latitude"><i class="fa fa-map-marker"></i> Latitude</label>
123
+ <input type="text" id="node-input-latitude" placeholder="auto from location (52.52)">
124
+ </div>
125
+ <div class="form-row">
126
+ <label for="node-input-longitude"><i class="fa fa-map-marker"></i> Longitude</label>
127
+ <input type="text" id="node-input-longitude" placeholder="auto from location (13.405)">
128
+ </div>
129
+ <div class="form-row">
130
+ <label for="node-input-interval"><i class="fa fa-clock-o"></i> Interval</label>
131
+ <input type="number" id="node-input-interval" placeholder="0 = manual (seconds)" min="0">
132
+ </div>
133
+ </script>
134
+
135
+ <script type="text/html" data-help-name="weather">
136
+ <p>Fetches current weather and a 5-day forecast from the free
137
+ <a href="https://open-meteo.com" target="_blank">Open-Meteo</a> API
138
+ (no API key required).</p>
139
+
140
+ <h3>Inputs</h3>
141
+ <dl class="message-properties">
142
+ <dt>payload <span class="property-type">any</span></dt>
143
+ <dd>Any message triggers a weather fetch (e.g. from an inject node).</dd>
144
+ </dl>
145
+
146
+ <h3>Outputs</h3>
147
+ <dl class="message-properties">
148
+ <dt>payload <span class="property-type">object</span></dt>
149
+ <dd>Normalized weather data (temperature, condition, icon, humidity,
150
+ wind, min/max, and a 5-day forecast). Connect this to input 2 of the
151
+ <b>SENEC Image</b> node.</dd>
152
+ </dl>
153
+
154
+ <h3>Configuration</h3>
155
+ <dl class="message-properties">
156
+ <dt>Location</dt>
157
+ <dd>A place name (e.g. <i>Berlin</i>). Click <b>Look up</b> to resolve
158
+ its coordinates automatically via the free Open-Meteo Geocoding API.
159
+ If several places match (or the name is misspelled), the closest
160
+ candidate names are listed so you can pick the right one. When
161
+ Latitude/Longitude are left empty, the node also resolves the location
162
+ automatically at runtime.</dd>
163
+ <dt>Latitude / Longitude</dt>
164
+ <dd>Geographic coordinates of the location to report weather for.
165
+ Optional when a Location name is provided.</dd>
166
+ <dt>Language</dt>
167
+ <dd>Language for condition text, place names and weekday/wind labels.</dd>
168
+ <dt>Interval</dt>
169
+ <dd>Automatic polling interval in seconds. Set to 0 to fetch only on
170
+ input.</dd>
171
+ </dl>
172
+
173
+ <h3>Example Flow</h3>
174
+ <pre>
175
+ [inject] → [weather] ──┐
176
+ ├→ [senec-image] → [file out]
177
+ [senec-data] ──────────┘
178
+ </pre>
179
+ </script>
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const weather_client_1 = require("../lib/weather-client");
4
+ const geocoding_client_1 = require("../lib/geocoding-client");
5
+ module.exports = function (RED) {
6
+ /**
7
+ * Admin HTTP endpoint used by the node editor to resolve a location
8
+ * name to coordinates. Requires an authenticated editor session with
9
+ * read access. Example: GET senec/geocode?q=Berlin&lang=de
10
+ */
11
+ RED.httpAdmin.get('/senec/geocode', RED.auth.needsPermission('weather.read'), async function (req, res) {
12
+ const name = String(req.query.q ?? '').trim();
13
+ const language = String(req.query.lang ?? 'de');
14
+ if (name.length === 0) {
15
+ res.status(400).json({ error: 'Missing query parameter "q"' });
16
+ return;
17
+ }
18
+ try {
19
+ const geocoder = new geocoding_client_1.GeocodingClient();
20
+ const results = await geocoder.search({ name, language, count: 5 });
21
+ res.json({ results });
22
+ }
23
+ catch (error) {
24
+ res.status(502).json({ error: error.message || 'Geocoding failed' });
25
+ }
26
+ });
27
+ function WeatherNode(config) {
28
+ RED.nodes.createNode(this, config);
29
+ const node = this;
30
+ node.latitude = parseFloat(String(config.latitude));
31
+ node.longitude = parseFloat(String(config.longitude));
32
+ node.language = config.language || 'de';
33
+ node.location = config.location || '';
34
+ node.interval = parseInt(String(config.interval), 10) || 0;
35
+ node.timer = null;
36
+ node.client = new weather_client_1.WeatherClient();
37
+ node.geocoder = new geocoding_client_1.GeocodingClient();
38
+ node.lastGood = null;
39
+ node.resolved = false;
40
+ const hasCoords = !isNaN(node.latitude) && !isNaN(node.longitude);
41
+ const hasLocation = node.location.trim().length > 0;
42
+ if (!hasCoords && !hasLocation) {
43
+ node.status({ fill: 'red', shape: 'ring', text: 'set location or lat/lon' });
44
+ node.error('Weather node requires either a location name or numeric latitude/longitude');
45
+ return;
46
+ }
47
+ /**
48
+ * Ensure numeric coordinates are available. When lat/lon were not
49
+ * configured but a location name was, resolve them via geocoding and
50
+ * cache the result so the lookup only happens once.
51
+ */
52
+ async function ensureCoordinates() {
53
+ if ((!isNaN(node.latitude) && !isNaN(node.longitude)) || node.resolved) {
54
+ return;
55
+ }
56
+ node.status({ fill: 'blue', shape: 'dot', text: 'geocoding...' });
57
+ const match = await node.geocoder.lookup({
58
+ name: node.location,
59
+ language: node.language,
60
+ });
61
+ if (!match) {
62
+ throw new Error(`No coordinates found for location "${node.location}"`);
63
+ }
64
+ node.latitude = match.latitude;
65
+ node.longitude = match.longitude;
66
+ // Prefer the resolved, human-readable label for display.
67
+ node.location = match.label || node.location;
68
+ node.resolved = true;
69
+ }
70
+ async function fetchWeather(send, done) {
71
+ const timestamp = new Date().toISOString();
72
+ try {
73
+ await ensureCoordinates();
74
+ node.status({ fill: 'blue', shape: 'dot', text: 'fetching...' });
75
+ const weather = await node.client.getWeather({
76
+ latitude: node.latitude,
77
+ longitude: node.longitude,
78
+ language: node.language,
79
+ location: node.location,
80
+ });
81
+ node.lastGood = weather;
82
+ send({
83
+ payload: weather,
84
+ topic: 'weather/data',
85
+ status: { ok: true, timestamp, fallback: false },
86
+ });
87
+ node.status({ fill: 'green', shape: 'dot', text: `ok ${timestamp.slice(11, 16)}` });
88
+ if (done) {
89
+ done();
90
+ }
91
+ }
92
+ catch (error) {
93
+ // Server not reachable (or geocoding failed): fall back to the last
94
+ // good result, or the built-in sample only if we have never received
95
+ // live data and we already have coordinates to sample with.
96
+ const fallback = node.lastGood ||
97
+ weather_client_1.WeatherClient.sample({
98
+ latitude: isNaN(node.latitude) ? undefined : node.latitude,
99
+ longitude: isNaN(node.longitude) ? undefined : node.longitude,
100
+ language: node.language,
101
+ location: node.location,
102
+ });
103
+ send({
104
+ payload: fallback,
105
+ topic: 'weather/data',
106
+ status: {
107
+ ok: false,
108
+ timestamp,
109
+ error: error.message,
110
+ fallback: true,
111
+ },
112
+ });
113
+ node.status({ fill: 'yellow', shape: 'ring', text: 'offline (fallback)' });
114
+ node.warn('Weather fetch failed, using fallback: ' + error.message);
115
+ if (done) {
116
+ done();
117
+ }
118
+ }
119
+ }
120
+ node.on('input', async function (_msg, send, done) {
121
+ send = send || node.send.bind(node);
122
+ await fetchWeather(send, done);
123
+ });
124
+ if (node.interval > 0) {
125
+ node.timer = setInterval(() => {
126
+ fetchWeather(node.send.bind(node));
127
+ }, node.interval * 1000);
128
+ }
129
+ node.on('close', function () {
130
+ if (node.timer) {
131
+ clearInterval(node.timer);
132
+ node.timer = null;
133
+ }
134
+ });
135
+ }
136
+ RED.nodes.registerType('weather', WeatherNode);
137
+ };
138
+ //# sourceMappingURL=weather.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weather.js","sourceRoot":"","sources":["../../src/nodes/weather.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "node-red-contrib-senec-cloud-v2",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Node-RED nodes for SENEC Cloud API integration - supports new API, CCU RedMatic optimized",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
- "build": "tsc && npm run copy-html",
7
+ "build": "tsc && npm run copy-html && npm run copy-assets",
8
8
  "copy-html": "node -e \"const fs=require('fs');const path=require('path');const src='src/nodes';const dst='dist/nodes';fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)));console.log('HTML files copied to dist/nodes');\"",
9
+ "copy-assets": "node -e \"const fs=require('fs');const path=require('path');const src='src/assets/fonts';const dst='dist/assets/fonts';if(fs.existsSync(src)){fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)));console.log('Font assets copied to dist/assets/fonts');}else{console.log('No font assets to copy');}\"",
9
10
  "watch": "tsc --watch",
10
11
  "test": "jest",
11
12
  "test:watch": "jest --watch",
@@ -30,6 +31,7 @@
30
31
  "nodes": {
31
32
  "senec-config": "dist/nodes/senec-config.js",
32
33
  "senec-data": "dist/nodes/senec-data.js",
34
+ "weather": "dist/nodes/weather.js",
33
35
  "senec-image": "dist/nodes/senec-image.js"
34
36
  }
35
37
  },