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
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ /**
3
+ * Weather Client
4
+ *
5
+ * Fetches current weather + a short forecast from the free Open-Meteo API
6
+ * (https://open-meteo.com) which requires no API key, and maps the raw
7
+ * response onto the normalized {@link WeatherData} structure consumed by
8
+ * the dashboard renderer.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.WeatherClient = void 0;
15
+ const axios_1 = __importDefault(require("axios"));
16
+ const WEATHER_CODES = {
17
+ 0: { icon: '☀️', de: 'Klar', en: 'Clear sky' },
18
+ 1: { icon: '🌤️', de: 'Überwiegend klar', en: 'Mainly clear' },
19
+ 2: { icon: '⛅', de: 'Teilweise bewölkt', en: 'Partly cloudy' },
20
+ 3: { icon: '☁️', de: 'Bewölkt', en: 'Overcast' },
21
+ 45: { icon: '🌫️', de: 'Nebel', en: 'Fog' },
22
+ 48: { icon: '🌫️', de: 'Reifnebel', en: 'Depositing rime fog' },
23
+ 51: { icon: '🌦️', de: 'Leichter Nieselregen', en: 'Light drizzle' },
24
+ 53: { icon: '🌦️', de: 'Nieselregen', en: 'Moderate drizzle' },
25
+ 55: { icon: '🌧️', de: 'Starker Nieselregen', en: 'Dense drizzle' },
26
+ 56: { icon: '🌧️', de: 'Gefrierender Nieselregen', en: 'Freezing drizzle' },
27
+ 57: { icon: '🌧️', de: 'Gefrierender Nieselregen', en: 'Freezing drizzle' },
28
+ 61: { icon: '🌧️', de: 'Leichter Regen', en: 'Slight rain' },
29
+ 63: { icon: '🌧️', de: 'Regen', en: 'Moderate rain' },
30
+ 65: { icon: '🌧️', de: 'Starker Regen', en: 'Heavy rain' },
31
+ 66: { icon: '🌧️', de: 'Gefrierender Regen', en: 'Freezing rain' },
32
+ 67: { icon: '🌧️', de: 'Gefrierender Regen', en: 'Freezing rain' },
33
+ 71: { icon: '🌨️', de: 'Leichter Schneefall', en: 'Slight snow' },
34
+ 73: { icon: '🌨️', de: 'Schneefall', en: 'Moderate snow' },
35
+ 75: { icon: '❄️', de: 'Starker Schneefall', en: 'Heavy snow' },
36
+ 77: { icon: '🌨️', de: 'Schneegriesel', en: 'Snow grains' },
37
+ 80: { icon: '🌦️', de: 'Leichte Schauer', en: 'Slight showers' },
38
+ 81: { icon: '🌧️', de: 'Schauer', en: 'Moderate showers' },
39
+ 82: { icon: '⛈️', de: 'Starke Schauer', en: 'Violent showers' },
40
+ 85: { icon: '🌨️', de: 'Schneeschauer', en: 'Slight snow showers' },
41
+ 86: { icon: '❄️', de: 'Starke Schneeschauer', en: 'Heavy snow showers' },
42
+ 95: { icon: '⛈️', de: 'Gewitter', en: 'Thunderstorm' },
43
+ 96: { icon: '⛈️', de: 'Gewitter mit Hagel', en: 'Thunderstorm with hail' },
44
+ 99: { icon: '⛈️', de: 'Gewitter mit Hagel', en: 'Thunderstorm with hail' },
45
+ };
46
+ const DEFAULT_CODE = { icon: '❓', de: 'Unbekannt', en: 'Unknown' };
47
+ /** Weekday short labels per language. Index 0 = Sunday (JS getDay()). */
48
+ const WEEKDAYS = {
49
+ de: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
50
+ en: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
51
+ };
52
+ /** 16-point compass labels per language. */
53
+ const COMPASS = {
54
+ de: [
55
+ 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO',
56
+ 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW',
57
+ ],
58
+ en: [
59
+ 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
60
+ 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW',
61
+ ],
62
+ };
63
+ class WeatherClient {
64
+ /**
65
+ * Fetch current weather + a 5-day forecast for the given coordinates.
66
+ */
67
+ async getWeather(options) {
68
+ // Validate coordinates defensively. The request URL host is a fixed
69
+ // constant (no SSRF), but we still guard against non-finite / out-of
70
+ // -range values being serialized into the query string.
71
+ const lat = Number(options.latitude);
72
+ const lon = Number(options.longitude);
73
+ if (!isFinite(lat) || lat < -90 || lat > 90) {
74
+ throw new Error('Invalid latitude (expected -90..90)');
75
+ }
76
+ if (!isFinite(lon) || lon < -180 || lon > 180) {
77
+ throw new Error('Invalid longitude (expected -180..180)');
78
+ }
79
+ const language = (options.language || 'de').toLowerCase();
80
+ const timeout = options.timeout ?? 10000;
81
+ const retries = Math.max(0, options.retries ?? 2);
82
+ const retryDelay = Math.max(0, options.retryDelay ?? 500);
83
+ const url = 'https://api.open-meteo.com/v1/forecast';
84
+ const params = {
85
+ latitude: options.latitude,
86
+ longitude: options.longitude,
87
+ current: 'temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m,wind_direction_10m',
88
+ daily: 'weather_code,temperature_2m_max,temperature_2m_min',
89
+ timezone: 'auto',
90
+ forecast_days: 5,
91
+ wind_speed_unit: 'kmh',
92
+ };
93
+ const response = await WeatherClient.requestWithRetry(url, params, timeout, retries, retryDelay);
94
+ const mapped = WeatherClient.mapResponse(response.data, language);
95
+ mapped.latitude = options.latitude;
96
+ mapped.longitude = options.longitude;
97
+ mapped.location =
98
+ options.location && options.location.length > 0
99
+ ? options.location
100
+ : WeatherClient.formatCoords(options.latitude, options.longitude);
101
+ return mapped;
102
+ }
103
+ /**
104
+ * A deterministic sample weather payload, used ONLY as a fallback when
105
+ * the Open-Meteo server cannot be reached.
106
+ */
107
+ static sample(options) {
108
+ const lat = options?.latitude ?? 52.52;
109
+ const lon = options?.longitude ?? 13.405;
110
+ return {
111
+ temperature: 18,
112
+ condition: (options?.language || 'de') === 'en' ? 'Partly cloudy' : 'Leicht bewölkt',
113
+ icon: '🌤️',
114
+ tempMax: 20,
115
+ tempMin: 11,
116
+ humidity: 45,
117
+ windSpeed: 15,
118
+ windDirection: 'WSW',
119
+ forecast: [
120
+ { day: 'Di', icon: '🌤️', temp: 20 },
121
+ { day: 'Mi', icon: '☁️', temp: 19 },
122
+ { day: 'Do', icon: '🌧️', temp: 17 },
123
+ { day: 'Fr', icon: '⛅', temp: 21 },
124
+ { day: 'Sa', icon: '☀️', temp: 24 },
125
+ ],
126
+ latitude: lat,
127
+ longitude: lon,
128
+ location: options?.location && options.location.length > 0
129
+ ? options.location
130
+ : WeatherClient.formatCoords(lat, lon),
131
+ };
132
+ }
133
+ /**
134
+ * Perform the Open-Meteo GET request, retrying on transient failures
135
+ * (HTTP 5xx such as 503, 429 rate limiting, timeouts and low-level
136
+ * network errors) using exponential backoff. Non-transient errors
137
+ * (e.g. HTTP 4xx other than 429) fail immediately.
138
+ */
139
+ static async requestWithRetry(url, params, timeout, retries, retryDelay) {
140
+ let lastError;
141
+ for (let attempt = 0; attempt <= retries; attempt++) {
142
+ try {
143
+ return await axios_1.default.get(url, { params, timeout });
144
+ }
145
+ catch (error) {
146
+ lastError = error;
147
+ if (attempt >= retries || !WeatherClient.isTransientError(error)) {
148
+ break;
149
+ }
150
+ // Exponential backoff: retryDelay * 2^attempt.
151
+ const wait = retryDelay * Math.pow(2, attempt);
152
+ await WeatherClient.delay(wait);
153
+ }
154
+ }
155
+ throw lastError;
156
+ }
157
+ /**
158
+ * Classify an axios error as transient (worth retrying) or not.
159
+ * Transient: no HTTP response (network/timeout), HTTP 429, or any 5xx.
160
+ */
161
+ static isTransientError(error) {
162
+ const status = error?.response?.status;
163
+ if (status === undefined) {
164
+ // No response at all -> network error or timeout.
165
+ return true;
166
+ }
167
+ return status === 429 || (status >= 500 && status <= 599);
168
+ }
169
+ static delay(ms) {
170
+ return new Promise((resolve) => setTimeout(resolve, ms));
171
+ }
172
+ static formatCoords(lat, lon) {
173
+ const fmt = (v) => (Math.round(v * 100) / 100).toFixed(2);
174
+ return `${fmt(lat)}, ${fmt(lon)}`;
175
+ }
176
+ /**
177
+ * Map a raw Open-Meteo forecast response to normalized WeatherData.
178
+ * Exposed statically so it can be unit-tested without network access.
179
+ */
180
+ static mapResponse(data, language = 'de') {
181
+ const lang = WEEKDAYS[language] ? language : 'en';
182
+ const current = data?.current ?? {};
183
+ const daily = data?.daily ?? {};
184
+ const currentCode = Number(current.weather_code ?? 0);
185
+ const info = WEATHER_CODES[currentCode] ?? DEFAULT_CODE;
186
+ const condition = lang === 'de' ? info.de : info.en;
187
+ const maxArr = Array.isArray(daily.temperature_2m_max)
188
+ ? daily.temperature_2m_max
189
+ : [];
190
+ const minArr = Array.isArray(daily.temperature_2m_min)
191
+ ? daily.temperature_2m_min
192
+ : [];
193
+ const codeArr = Array.isArray(daily.weather_code) ? daily.weather_code : [];
194
+ const timeArr = Array.isArray(daily.time) ? daily.time : [];
195
+ const forecast = [];
196
+ for (let i = 0; i < Math.min(5, timeArr.length || maxArr.length); i++) {
197
+ const dayCode = Number(codeArr[i] ?? currentCode);
198
+ const dayInfo = WEATHER_CODES[dayCode] ?? DEFAULT_CODE;
199
+ forecast.push({
200
+ day: WeatherClient.weekdayLabel(timeArr[i], lang),
201
+ icon: dayInfo.icon,
202
+ temp: Math.round(Number(maxArr[i] ?? current.temperature_2m ?? 0)),
203
+ });
204
+ }
205
+ return {
206
+ temperature: Math.round(Number(current.temperature_2m ?? 0)),
207
+ condition,
208
+ icon: info.icon,
209
+ tempMax: Math.round(Number(maxArr[0] ?? current.temperature_2m ?? 0)),
210
+ tempMin: Math.round(Number(minArr[0] ?? current.temperature_2m ?? 0)),
211
+ humidity: Math.round(Number(current.relative_humidity_2m ?? 0)),
212
+ windSpeed: Math.round(Number(current.wind_speed_10m ?? 0)),
213
+ windDirection: WeatherClient.compassLabel(Number(current.wind_direction_10m ?? 0), lang),
214
+ forecast,
215
+ };
216
+ }
217
+ static weekdayLabel(isoDate, lang) {
218
+ const labels = WEEKDAYS[lang] ?? WEEKDAYS.en;
219
+ if (!isoDate) {
220
+ return labels[0];
221
+ }
222
+ const d = new Date(`${isoDate}T00:00:00`);
223
+ const idx = isNaN(d.getTime()) ? 0 : d.getDay();
224
+ return labels[idx];
225
+ }
226
+ static compassLabel(degrees, lang) {
227
+ const labels = COMPASS[lang] ?? COMPASS.en;
228
+ const normalized = ((degrees % 360) + 360) % 360;
229
+ const idx = Math.round(normalized / 22.5) % 16;
230
+ return labels[idx];
231
+ }
232
+ }
233
+ exports.WeatherClient = WeatherClient;
234
+ //# sourceMappingURL=weather-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weather-client.js","sourceRoot":"","sources":["../../src/lib/weather-client.ts"],"names":[],"mappings":"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"}
@@ -31,18 +31,26 @@ module.exports = function (RED) {
31
31
  async function fetchData() {
32
32
  if (!node.apiClient)
33
33
  return;
34
+ const timestamp = new Date().toISOString();
34
35
  try {
35
36
  node.status({ fill: 'blue', shape: 'dot', text: 'fetching...' });
36
37
  const parsedData = await node.apiClient.getData();
37
38
  node.send({
38
39
  payload: parsedData,
39
- topic: 'senec/data'
40
+ topic: 'senec/data',
41
+ status: { ok: true, timestamp, fallback: false }
40
42
  });
41
- node.status({ fill: 'green', shape: 'dot', text: 'success' });
43
+ node.status({ fill: 'green', shape: 'dot', text: `ok ${timestamp.slice(11, 16)}` });
42
44
  }
43
45
  catch (error) {
44
46
  node.error('Failed to fetch data: ' + error.message);
45
47
  node.status({ fill: 'red', shape: 'ring', text: 'error' });
48
+ // Report the failed status so downstream (image) can render it.
49
+ node.send({
50
+ payload: null,
51
+ topic: 'senec/status',
52
+ status: { ok: false, timestamp, error: error.message, fallback: false }
53
+ });
46
54
  }
47
55
  }
48
56
  // Handle input messages
@@ -1 +1 @@
1
- {"version":3,"file":"senec-data.js","sourceRoot":"","sources":["../../src/nodes/senec-data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"senec-data.js","sourceRoot":"","sources":["../../src/nodes/senec-data.ts"],"names":[],"mappings":"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"}
@@ -4,17 +4,21 @@
4
4
  color: '#00A0E3',
5
5
  defaults: {
6
6
  name: { value: '' },
7
- width: { value: 800, validate: RED.validators.number() },
8
- height: { value: 480, validate: RED.validators.number() },
9
- layout: { value: 'default' }
7
+ width: { value: 1600, validate: RED.validators.number() },
8
+ height: { value: 0 },
9
+ layout: { value: 'dashboard' },
10
+ outputFormat: { value: 'buffer' },
11
+ filePath: { value: '' }
10
12
  },
11
- inputs: 1,
12
- outputs: 1,
13
+ inputs: 2,
14
+ outputs: 2,
15
+ inputLabels: ['SENEC', 'Weather'],
16
+ outputLabels: ['PNG', 'HTML'],
13
17
  icon: 'font-awesome/fa-image',
14
- label: function() {
18
+ label: function () {
15
19
  return this.name || 'SENEC Image';
16
20
  },
17
- labelStyle: function() {
21
+ labelStyle: function () {
18
22
  return this.name ? 'node_label_italic' : '';
19
23
  }
20
24
  });
@@ -27,68 +31,84 @@
27
31
  </div>
28
32
  <div class="form-row">
29
33
  <label for="node-input-width"><i class="fa fa-arrows-h"></i> Width</label>
30
- <input type="number" id="node-input-width" placeholder="800" min="100">
34
+ <input type="number" id="node-input-width" placeholder="1600" min="200">
31
35
  </div>
32
36
  <div class="form-row">
33
37
  <label for="node-input-height"><i class="fa fa-arrows-v"></i> Height</label>
34
- <input type="number" id="node-input-height" placeholder="480" min="100">
38
+ <input type="number" id="node-input-height" placeholder="0 = auto (16:9)" min="0">
35
39
  </div>
36
40
  <div class="form-row">
37
41
  <label for="node-input-layout"><i class="fa fa-th-large"></i> Layout</label>
38
42
  <select id="node-input-layout">
39
- <option value="default">Default</option>
40
- <option value="compact">Compact</option>
41
- <option value="trmnl">TRMNL Display</option>
43
+ <option value="dashboard">Energy Dashboard (4 cards, 16:9)</option>
42
44
  </select>
43
45
  </div>
46
+ <div class="form-row">
47
+ <label for="node-input-outputFormat"><i class="fa fa-download"></i> PNG Output</label>
48
+ <select id="node-input-outputFormat">
49
+ <option value="buffer">Buffer (PNG)</option>
50
+ <option value="base64">Base64 Data URL</option>
51
+ <option value="file">File</option>
52
+ </select>
53
+ </div>
54
+ <div class="form-row" id="node-row-filePath">
55
+ <label for="node-input-filePath"><i class="fa fa-folder-open"></i> File Path</label>
56
+ <input type="text" id="node-input-filePath" placeholder="/path/to/dashboard.png">
57
+ </div>
44
58
  </script>
45
59
 
46
60
  <script type="text/html" data-help-name="senec-image">
47
- <p>Generates a PNG image visualization of SENEC system data.</p>
48
-
61
+ <p>Renders a 16:9 energy dashboard combining SENEC energy data and weather
62
+ data. Produces a PNG image on output 1 and a fully resizable HTML document
63
+ on output 2, in parallel.</p>
64
+
49
65
  <h3>Inputs</h3>
50
- <dl class="message-properties">
51
- <dt>payload <span class="property-type">object</span></dt>
52
- <dd>SENEC data object (from senec-data node)</dd>
53
- </dl>
54
-
66
+ <ol class="node-ports">
67
+ <li>SENEC
68
+ <dl class="message-properties">
69
+ <dt>payload <span class="property-type">object</span></dt>
70
+ <dd>SENEC data object (from the <b>senec-data</b> node).</dd>
71
+ </dl>
72
+ </li>
73
+ <li>Weather
74
+ <dl class="message-properties">
75
+ <dt>payload <span class="property-type">object</span></dt>
76
+ <dd>Weather data object (from the <b>weather</b> node). Optional; the
77
+ weather card renders a placeholder until data arrives.</dd>
78
+ </dl>
79
+ </li>
80
+ </ol>
81
+
55
82
  <h3>Outputs</h3>
56
- <dl class="message-properties">
57
- <dt>payload <span class="property-type">buffer</span></dt>
58
- <dd>PNG image as a Buffer</dd>
59
-
60
- <dt>contentType <span class="property-type">string</span></dt>
61
- <dd>Set to "image/png"</dd>
62
-
63
- <dt>filename <span class="property-type">string</span></dt>
64
- <dd>Suggested filename for the image</dd>
65
- </dl>
66
-
67
- <h3>Configuration</h3>
68
- <dl class="message-properties">
69
- <dt>Width</dt>
70
- <dd>Image width in pixels (default: 800)</dd>
71
-
72
- <dt>Height</dt>
73
- <dd>Image height in pixels (default: 480)</dd>
74
-
75
- <dt>Layout</dt>
76
- <dd>Image layout template:
77
- <ul>
78
- <li><b>Default</b> - Standard layout with all data</li>
79
- <li><b>Compact</b> - Minimal layout for small displays</li>
80
- <li><b>TRMNL Display</b> - Optimized for TRMNL e-ink display (800x480)</li>
81
- </ul>
82
- </dd>
83
- </dl>
84
-
83
+ <ol class="node-ports">
84
+ <li>PNG
85
+ <dl class="message-properties">
86
+ <dt>payload <span class="property-type">buffer | string</span></dt>
87
+ <dd>PNG image as a Buffer (or base64 data URL / file path depending
88
+ on the PNG Output setting).</dd>
89
+ <dt>contentType <span class="property-type">string</span></dt>
90
+ <dd>Set to "image/png".</dd>
91
+ </dl>
92
+ </li>
93
+ <li>HTML
94
+ <dl class="message-properties">
95
+ <dt>payload <span class="property-type">string</span></dt>
96
+ <dd>Self-contained, resizable HTML document of the dashboard.</dd>
97
+ <dt>contentType <span class="property-type">string</span></dt>
98
+ <dd>Set to "text/html".</dd>
99
+ </dl>
100
+ </li>
101
+ </ol>
102
+
85
103
  <h3>Details</h3>
86
- <p>This node generates a PNG image visualization of your SENEC system data.</p>
87
- <p>Connect it after a senec-data node to visualize the data graphically.</p>
88
- <p>The generated image can be saved to file, sent via HTTP, or displayed on e-ink displays like TRMNL.</p>
89
-
104
+ <p>The node caches the latest SENEC and weather messages and re-renders on
105
+ each input, so it can be driven from two independent sources. Rendering
106
+ begins once SENEC data has been received at least once.</p>
107
+
90
108
  <h3>Example Flow</h3>
91
109
  <pre>
92
- [senec-data] → [senec-image] → [file out]
110
+ [senec-data] ──────────┐
111
+ ├→ [senec-image] ─┬→ [file out] (PNG)
112
+ [weather] ─────────────┘ └→ [http res] (HTML)
93
113
  </pre>
94
114
  </script>