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,152 @@
1
+ /**
2
+ * Energy Dashboard Layout Specification (16:9, 4 cards)
3
+ *
4
+ * This module encodes the "EnergyDashboard.precise" visual spec found in
5
+ * docs/00-input/ux/ as a structured, strongly-typed model shared by both
6
+ * the PNG renderer (src/lib/senec-image-renderer.ts) and the HTML
7
+ * renderer (src/lib/dashboard-html-renderer.ts).
8
+ *
9
+ * It also builds the DashboardModel — a merged, presentation-ready view
10
+ * of SENEC energy data (input 1) and Weather data (input 2).
11
+ */
12
+ import { SenecData, SourceStatus, WeatherData } from '../types/senec';
13
+ /** Reference frame: 1600 x 900, 16:9. */
14
+ export declare const DASHBOARD_ASPECT_RATIO: number;
15
+ export declare const DASHBOARD_REFERENCE_WIDTH = 1600;
16
+ export declare const DASHBOARD_REFERENCE_HEIGHT = 900;
17
+ /**
18
+ * Color palette (from the HTML spec :root variables).
19
+ */
20
+ export declare const DASHBOARD_PALETTE: {
21
+ readonly blue: "#2438c5";
22
+ readonly solar: "#fdb913";
23
+ readonly solarHi: "#ffd84d";
24
+ readonly battery: "#1237e6";
25
+ readonly batteryHi: "#3157ff";
26
+ readonly orange: "#ff8a00";
27
+ readonly orangeHi: "#ffad33";
28
+ readonly gray: "#8a8a8a";
29
+ readonly grayHi: "#a8a8a8";
30
+ readonly green: "#1fb141";
31
+ readonly statusBlue: "#2e7bea";
32
+ readonly ringBg: "#e6eaf0";
33
+ readonly border: "#e8e8e8";
34
+ readonly muted: "#6b7280";
35
+ readonly main: "#111827";
36
+ readonly page: "#f3f4f6";
37
+ readonly white: "#ffffff";
38
+ };
39
+ /**
40
+ * Text-to-circle-diameter ratios (figspec "Text to circle ratios").
41
+ * D = energy-node diameter.
42
+ */
43
+ export declare const TEXT_RATIOS: {
44
+ readonly label: 0.152;
45
+ readonly value: 0.357;
46
+ readonly unit: 0.134;
47
+ readonly multiValue: 0.223;
48
+ readonly hint: 0.107;
49
+ };
50
+ /**
51
+ * Node ratios relative to the energy-node diameter (README "Important
52
+ * ratios").
53
+ */
54
+ export declare const NODE_RATIOS: {
55
+ readonly hubDiameter: 0.375;
56
+ readonly connectorThickness: 0.152;
57
+ };
58
+ /**
59
+ * Relative node positions inside a card's flow-area (figspec table).
60
+ * Values are fractions (0..1) of the flow-area width/height.
61
+ */
62
+ export interface RelPos {
63
+ x: number;
64
+ y: number;
65
+ }
66
+ export declare const FLOW_POSITIONS: Record<string, RelPos>;
67
+ /**
68
+ * A single energy-flow node description shared by both renderers.
69
+ */
70
+ export interface FlowNode {
71
+ id: keyof typeof FLOW_POSITIONS;
72
+ /** Localized label shown at the top of the circle. */
73
+ label: string;
74
+ /** Base gradient/fill color. */
75
+ color: string;
76
+ /** Lighter highlight color (for the radial-gradient highlight). */
77
+ colorHi: string;
78
+ }
79
+ /**
80
+ * The four static energy-flow nodes (excluding the hub).
81
+ */
82
+ export declare const FLOW_NODES: FlowNode[];
83
+ /**
84
+ * Card titles.
85
+ */
86
+ export declare const CARD_TITLES: {
87
+ readonly current: "Aktuell";
88
+ readonly today: "Heute";
89
+ readonly autarky: "Autarkie";
90
+ readonly weather: "Wetter";
91
+ };
92
+ /**
93
+ * A single energy-flow node's rendered values.
94
+ * `value` is a single number (kW/kWh); `dual` (when present) provides the
95
+ * "out / in" split shown on the "Heute" card for grid and battery.
96
+ */
97
+ export interface NodeValue {
98
+ label: string;
99
+ value: string;
100
+ /** For dual (out/in) values, e.g. "0,1 / 0,3". */
101
+ dual?: string;
102
+ /** Hint under a dual value, e.g. "aus / ein". */
103
+ hint?: string;
104
+ unit: string;
105
+ }
106
+ /**
107
+ * The complete presentation model for one dashboard render.
108
+ */
109
+ export interface DashboardModel {
110
+ current: {
111
+ solar: NodeValue;
112
+ grid: NodeValue;
113
+ battery: NodeValue;
114
+ wallbox: NodeValue;
115
+ consumption: NodeValue;
116
+ };
117
+ today: {
118
+ solar: NodeValue;
119
+ grid: NodeValue;
120
+ battery: NodeValue;
121
+ wallbox: NodeValue;
122
+ consumption: NodeValue;
123
+ };
124
+ autarky: {
125
+ todayPercent: number;
126
+ currentPercent: number;
127
+ batterySocPercent: number;
128
+ };
129
+ weather: WeatherData | null;
130
+ /** Status of the two data sources, rendered as a status line. */
131
+ status: {
132
+ senec: SourceStatus;
133
+ weather: SourceStatus;
134
+ };
135
+ }
136
+ /** A neutral "not yet received" status. */
137
+ export declare function unknownStatus(): SourceStatus;
138
+ /** Format a number with a German decimal comma and one decimal place. */
139
+ export declare function deDecimal(value: number): string;
140
+ /**
141
+ * Build the merged dashboard model from SENEC energy data and (optional)
142
+ * weather data.
143
+ *
144
+ * Where the SENEC payload does not expose a value directly (battery
145
+ * charge/discharge split, wallbox), sensible fallbacks are derived from
146
+ * the energy balance so the dashboard always renders meaningfully.
147
+ */
148
+ export declare function buildDashboardModel(senec: SenecData | null, weather: WeatherData | null, status?: {
149
+ senec?: SourceStatus;
150
+ weather?: SourceStatus;
151
+ }): DashboardModel;
152
+ //# sourceMappingURL=dashboard-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout.d.ts","sourceRoot":"","sources":["../../src/lib/dashboard-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEtE,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAC7C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;CAkBpB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;CAMd,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOjD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,OAAO,cAAc,CAAC;IAChC,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,EAMhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;CAKd,CAAC;AAMX;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QACP,KAAK,EAAE,SAAS,CAAC;QACjB,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,SAAS,CAAC;QACnB,OAAO,EAAE,SAAS,CAAC;QACnB,WAAW,EAAE,SAAS,CAAC;KACxB,CAAC;IACF,KAAK,EAAE;QACL,KAAK,EAAE,SAAS,CAAC;QACjB,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,SAAS,CAAC;QACnB,OAAO,EAAE,SAAS,CAAC;QACnB,WAAW,EAAE,SAAS,CAAC;KACxB,CAAC;IACF,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,MAAM,EAAE;QACN,KAAK,EAAE,YAAY,CAAC;QACpB,OAAO,EAAE,YAAY,CAAC;KACvB,CAAC;CACH;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,IAAI,YAAY,CAE5C;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAeD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,MAAM,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,GACxD,cAAc,CAgFhB"}
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * Energy Dashboard Layout Specification (16:9, 4 cards)
4
+ *
5
+ * This module encodes the "EnergyDashboard.precise" visual spec found in
6
+ * docs/00-input/ux/ as a structured, strongly-typed model shared by both
7
+ * the PNG renderer (src/lib/senec-image-renderer.ts) and the HTML
8
+ * renderer (src/lib/dashboard-html-renderer.ts).
9
+ *
10
+ * It also builds the DashboardModel — a merged, presentation-ready view
11
+ * of SENEC energy data (input 1) and Weather data (input 2).
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.CARD_TITLES = exports.FLOW_NODES = exports.FLOW_POSITIONS = exports.NODE_RATIOS = exports.TEXT_RATIOS = exports.DASHBOARD_PALETTE = exports.DASHBOARD_REFERENCE_HEIGHT = exports.DASHBOARD_REFERENCE_WIDTH = exports.DASHBOARD_ASPECT_RATIO = void 0;
15
+ exports.unknownStatus = unknownStatus;
16
+ exports.deDecimal = deDecimal;
17
+ exports.buildDashboardModel = buildDashboardModel;
18
+ /** Reference frame: 1600 x 900, 16:9. */
19
+ exports.DASHBOARD_ASPECT_RATIO = 16 / 9;
20
+ exports.DASHBOARD_REFERENCE_WIDTH = 1600;
21
+ exports.DASHBOARD_REFERENCE_HEIGHT = 900;
22
+ /**
23
+ * Color palette (from the HTML spec :root variables).
24
+ */
25
+ exports.DASHBOARD_PALETTE = {
26
+ blue: '#2438c5',
27
+ solar: '#fdb913',
28
+ solarHi: '#ffd84d',
29
+ battery: '#1237e6',
30
+ batteryHi: '#3157ff',
31
+ orange: '#ff8a00',
32
+ orangeHi: '#ffad33',
33
+ gray: '#8a8a8a',
34
+ grayHi: '#a8a8a8',
35
+ green: '#1fb141',
36
+ statusBlue: '#2e7bea',
37
+ ringBg: '#e6eaf0',
38
+ border: '#e8e8e8',
39
+ muted: '#6b7280',
40
+ main: '#111827',
41
+ page: '#f3f4f6',
42
+ white: '#ffffff',
43
+ };
44
+ /**
45
+ * Text-to-circle-diameter ratios (figspec "Text to circle ratios").
46
+ * D = energy-node diameter.
47
+ */
48
+ exports.TEXT_RATIOS = {
49
+ label: 0.152,
50
+ value: 0.357,
51
+ unit: 0.134,
52
+ multiValue: 0.223,
53
+ hint: 0.107,
54
+ };
55
+ /**
56
+ * Node ratios relative to the energy-node diameter (README "Important
57
+ * ratios").
58
+ */
59
+ exports.NODE_RATIOS = {
60
+ hubDiameter: 0.375,
61
+ connectorThickness: 0.152,
62
+ };
63
+ exports.FLOW_POSITIONS = {
64
+ hub: { x: 0.5, y: 0.5 },
65
+ solar: { x: 0.5, y: 0.16 },
66
+ grid: { x: 0.235, y: 0.49 },
67
+ battery: { x: 0.765, y: 0.49 },
68
+ wallbox: { x: 0.34, y: 0.815 },
69
+ consumption: { x: 0.66, y: 0.815 },
70
+ };
71
+ /**
72
+ * The four static energy-flow nodes (excluding the hub).
73
+ */
74
+ exports.FLOW_NODES = [
75
+ { id: 'solar', label: 'Erzeugung', color: exports.DASHBOARD_PALETTE.solar, colorHi: exports.DASHBOARD_PALETTE.solarHi },
76
+ { id: 'grid', label: 'Netz', color: exports.DASHBOARD_PALETTE.gray, colorHi: exports.DASHBOARD_PALETTE.grayHi },
77
+ { id: 'battery', label: 'Speicher', color: exports.DASHBOARD_PALETTE.battery, colorHi: exports.DASHBOARD_PALETTE.batteryHi },
78
+ { id: 'wallbox', label: 'Wallbox', color: exports.DASHBOARD_PALETTE.orange, colorHi: exports.DASHBOARD_PALETTE.orangeHi },
79
+ { id: 'consumption', label: 'Verbrauch', color: exports.DASHBOARD_PALETTE.orange, colorHi: exports.DASHBOARD_PALETTE.orangeHi },
80
+ ];
81
+ /**
82
+ * Card titles.
83
+ */
84
+ exports.CARD_TITLES = {
85
+ current: 'Aktuell',
86
+ today: 'Heute',
87
+ autarky: 'Autarkie',
88
+ weather: 'Wetter',
89
+ };
90
+ /** A neutral "not yet received" status. */
91
+ function unknownStatus() {
92
+ return { ok: false, timestamp: '', error: 'no data' };
93
+ }
94
+ /** Format a number with a German decimal comma and one decimal place. */
95
+ function deDecimal(value) {
96
+ return value.toFixed(1).replace('.', ',');
97
+ }
98
+ /** Convert Watts to kW rounded to one decimal. */
99
+ function wToKw(watts) {
100
+ return Math.round((watts / 1000) * 10) / 10;
101
+ }
102
+ function round1(value) {
103
+ return Math.round(value * 10) / 10;
104
+ }
105
+ function clampPercent(value) {
106
+ return Math.min(100, Math.max(0, Math.round(value)));
107
+ }
108
+ /**
109
+ * Build the merged dashboard model from SENEC energy data and (optional)
110
+ * weather data.
111
+ *
112
+ * Where the SENEC payload does not expose a value directly (battery
113
+ * charge/discharge split, wallbox), sensible fallbacks are derived from
114
+ * the energy balance so the dashboard always renders meaningfully.
115
+ */
116
+ function buildDashboardModel(senec, weather, status) {
117
+ const s = senec ?? emptySenec();
118
+ // --- Current (now, in kW) ---
119
+ const genNow = wToKw(s.powergenerated.now);
120
+ const consNow = wToKw(s.consumption.now);
121
+ const gridImportNow = wToKw(s.gridimport.now);
122
+ const gridExportNow = wToKw(s.gridexport.now);
123
+ // Net grid: positive => import from grid, negative => export.
124
+ const gridNet = round1(gridImportNow - gridExportNow);
125
+ // Battery: positive => discharging (to house), negative => charging.
126
+ const batteryNow = round1(consNow - genNow + gridNet);
127
+ // --- Today (totals, in kWh) ---
128
+ const genToday = round1(s.powergenerated.today);
129
+ const consToday = round1(s.consumption.today);
130
+ const gridImportToday = round1(s.gridimport.today);
131
+ const gridExportToday = round1(s.gridexport.today);
132
+ // The SENEC dashboard API does not expose real gross battery charge and
133
+ // discharge energy totals for the day, only the current battery power.
134
+ // We can only derive the *net* battery contribution from the daily energy
135
+ // balance:
136
+ // generation + gridImport + batteryDischarge
137
+ // = consumption + gridExport + batteryCharge
138
+ // => netBattery = batteryDischarge - batteryCharge
139
+ // = consumption + gridExport - generation - gridImport
140
+ // A positive net means the battery net-discharged (delivered energy to the
141
+ // house); a negative net means it net-charged. Splitting this single value
142
+ // into independent gross "out / in" totals is not possible from the balance
143
+ // alone (the two halves would be exact negatives), so we expose the net
144
+ // battery energy as a single value instead of a misleading aus/ein split.
145
+ const batteryNetToday = round1(consToday + gridExportToday - genToday - gridImportToday);
146
+ const batteryDischargeToday = Math.max(0, batteryNetToday);
147
+ const batteryChargeToday = Math.max(0, -batteryNetToday);
148
+ // --- Autarky ---
149
+ const autarkyToday = consToday > 0 ? clampPercent(((consToday - gridImportToday) / consToday) * 100) : 0;
150
+ const autarkyCurrent = consNow > 0 ? clampPercent(((consNow - gridImportNow) / consNow) * 100) : 0;
151
+ const batterySoc = clampPercent(s.acculevel.now);
152
+ return {
153
+ current: {
154
+ solar: { label: 'Erzeugung', value: deDecimal(Math.abs(genNow)), unit: 'kW' },
155
+ grid: { label: 'Netz', value: deDecimal(Math.abs(gridNet)), unit: 'kW' },
156
+ battery: { label: 'Speicher', value: deDecimal(Math.abs(batteryNow)), unit: 'kW' },
157
+ wallbox: { label: 'Wallbox', value: deDecimal(0), unit: 'kW' },
158
+ consumption: { label: 'Verbrauch', value: deDecimal(Math.abs(consNow)), unit: 'kW' },
159
+ },
160
+ today: {
161
+ solar: { label: 'Erzeugung', value: deDecimal(genToday), unit: 'kWh' },
162
+ grid: {
163
+ label: 'Netz',
164
+ value: deDecimal(gridImportToday),
165
+ dual: `${deDecimal(gridExportToday)} / ${deDecimal(gridImportToday)}`,
166
+ hint: 'aus / ein',
167
+ unit: 'kWh',
168
+ },
169
+ battery: {
170
+ label: 'Speicher',
171
+ value: deDecimal(batteryDischargeToday),
172
+ dual: `${deDecimal(batteryDischargeToday)} / ${deDecimal(batteryChargeToday)}`,
173
+ hint: 'aus / ein',
174
+ unit: 'kWh',
175
+ },
176
+ wallbox: { label: 'Wallbox', value: deDecimal(0), unit: 'kWh' },
177
+ consumption: { label: 'Verbrauch', value: deDecimal(consToday), unit: 'kWh' },
178
+ },
179
+ autarky: {
180
+ todayPercent: autarkyToday,
181
+ currentPercent: autarkyCurrent,
182
+ batterySocPercent: batterySoc,
183
+ },
184
+ weather: weather ?? null,
185
+ status: {
186
+ senec: status?.senec ?? unknownStatus(),
187
+ weather: status?.weather ?? unknownStatus(),
188
+ },
189
+ };
190
+ }
191
+ function emptySenec() {
192
+ return {
193
+ steuereinheitState: 'OK',
194
+ gridimport: { today: 0, now: 0 },
195
+ powergenerated: { today: 0, now: 0 },
196
+ consumption: { today: 0, now: 0 },
197
+ gridexport: { today: 0, now: 0 },
198
+ acculevel: { now: 0 },
199
+ };
200
+ }
201
+ //# sourceMappingURL=dashboard-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout.js","sourceRoot":"","sources":["../../src/lib/dashboard-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Geocoding Client
3
+ *
4
+ * Resolves a human-readable location name (e.g. "Berlin", "Leipzig, DE")
5
+ * to geographic coordinates using the free Open-Meteo Geocoding API
6
+ * (https://open-meteo.com/en/docs/geocoding-api) which requires no API key.
7
+ *
8
+ * The result is used to populate the latitude/longitude of the weather
9
+ * lookup automatically so users do not have to look up coordinates by hand.
10
+ */
11
+ /**
12
+ * Options for a single geocoding lookup.
13
+ */
14
+ export interface GeocodingOptions {
15
+ /** Location name to search for, e.g. "Berlin". */
16
+ name: string;
17
+ /** IETF language tag for localized place names (default "de"). */
18
+ language?: string;
19
+ /** Maximum number of candidate results to return (default 1, max 100). */
20
+ count?: number;
21
+ /** Request timeout in ms (default 10000). */
22
+ timeout?: number;
23
+ }
24
+ /**
25
+ * A single geocoding match.
26
+ */
27
+ export interface GeocodingResult {
28
+ /** Resolved place name. */
29
+ name: string;
30
+ /** Latitude in decimal degrees. */
31
+ latitude: number;
32
+ /** Longitude in decimal degrees. */
33
+ longitude: number;
34
+ /** Country name, if provided by the API. */
35
+ country?: string;
36
+ /** Primary administrative division (state/region), if provided. */
37
+ admin1?: string;
38
+ /** IANA timezone identifier, if provided. */
39
+ timezone?: string;
40
+ /** A concise, human-readable label combining name/region/country. */
41
+ label: string;
42
+ }
43
+ export declare class GeocodingClient {
44
+ /**
45
+ * Look up coordinates for a location name. Resolves to the best (first)
46
+ * match, or `null` when the API returns no results.
47
+ */
48
+ lookup(options: GeocodingOptions): Promise<GeocodingResult | null>;
49
+ /**
50
+ * Look up coordinates for a location name and return all candidate
51
+ * matches (up to `count`). Returns an empty array when there are none.
52
+ */
53
+ search(options: GeocodingOptions): Promise<GeocodingResult[]>;
54
+ /**
55
+ * Map a raw Open-Meteo geocoding response to normalized results.
56
+ * Exposed statically so it can be unit-tested without network access.
57
+ */
58
+ static mapResults(data: any): GeocodingResult[];
59
+ private static formatLabel;
60
+ }
61
+ //# sourceMappingURL=geocoding-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geocoding-client.d.ts","sourceRoot":"","sources":["../../src/lib/geocoding-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAKxE;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAsBnE;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,eAAe,EAAE;IAoB/C,OAAO,CAAC,MAAM,CAAC,WAAW;CAO3B"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Geocoding Client
4
+ *
5
+ * Resolves a human-readable location name (e.g. "Berlin", "Leipzig, DE")
6
+ * to geographic coordinates using the free Open-Meteo Geocoding API
7
+ * (https://open-meteo.com/en/docs/geocoding-api) which requires no API key.
8
+ *
9
+ * The result is used to populate the latitude/longitude of the weather
10
+ * lookup automatically so users do not have to look up coordinates by hand.
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.GeocodingClient = void 0;
17
+ const axios_1 = __importDefault(require("axios"));
18
+ class GeocodingClient {
19
+ /**
20
+ * Look up coordinates for a location name. Resolves to the best (first)
21
+ * match, or `null` when the API returns no results.
22
+ */
23
+ async lookup(options) {
24
+ const results = await this.search(options);
25
+ return results.length > 0 ? results[0] : null;
26
+ }
27
+ /**
28
+ * Look up coordinates for a location name and return all candidate
29
+ * matches (up to `count`). Returns an empty array when there are none.
30
+ */
31
+ async search(options) {
32
+ const name = String(options.name ?? '').trim();
33
+ if (name.length === 0) {
34
+ throw new Error('Location name is required for geocoding');
35
+ }
36
+ const language = (options.language || 'de').toLowerCase();
37
+ const timeout = options.timeout ?? 10000;
38
+ const count = Math.min(Math.max(Number(options.count) || 1, 1), 100);
39
+ const url = 'https://geocoding-api.open-meteo.com/v1/search';
40
+ const params = {
41
+ name,
42
+ count,
43
+ language,
44
+ format: 'json',
45
+ };
46
+ const response = await axios_1.default.get(url, { params, timeout });
47
+ return GeocodingClient.mapResults(response.data);
48
+ }
49
+ /**
50
+ * Map a raw Open-Meteo geocoding response to normalized results.
51
+ * Exposed statically so it can be unit-tested without network access.
52
+ */
53
+ static mapResults(data) {
54
+ const rows = Array.isArray(data?.results) ? data.results : [];
55
+ return rows
56
+ .filter((row) => isFinite(Number(row?.latitude)) && isFinite(Number(row?.longitude)))
57
+ .map((row) => {
58
+ const name = String(row.name ?? '').trim();
59
+ const admin1 = row.admin1 ? String(row.admin1).trim() : undefined;
60
+ const country = row.country ? String(row.country).trim() : undefined;
61
+ return {
62
+ name,
63
+ latitude: Number(row.latitude),
64
+ longitude: Number(row.longitude),
65
+ country,
66
+ admin1,
67
+ timezone: row.timezone ? String(row.timezone) : undefined,
68
+ label: GeocodingClient.formatLabel(name, admin1, country),
69
+ };
70
+ });
71
+ }
72
+ static formatLabel(name, admin1, country) {
73
+ return [name, admin1, country].filter((p) => p && p.length > 0).join(', ');
74
+ }
75
+ }
76
+ exports.GeocodingClient = GeocodingClient;
77
+ //# sourceMappingURL=geocoding-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geocoding-client.js","sourceRoot":"","sources":["../../src/lib/geocoding-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Energy Dashboard Image Renderer (PNG)
3
+ *
4
+ * Renders the 4-card, 16:9 energy dashboard (as specified in
5
+ * docs/00-input/ux/) to a PNG buffer using the pure-JavaScript
6
+ * `pureimage` library (with optional `canvas` acceleration when
7
+ * available).
8
+ *
9
+ * The layout mirrors the HTML renderer (src/lib/dashboard-html-renderer.ts)
10
+ * and consumes the same shared DashboardModel, which merges SENEC energy
11
+ * data (input 1) with Weather data (input 2).
12
+ *
13
+ * Because pureimage cannot render emoji glyphs, weather / autarky icons
14
+ * are drawn as simple vector shapes.
15
+ */
16
+ import { SenecData, WeatherData } from '../types/senec';
17
+ import { DashboardModel } from './dashboard-layout';
18
+ export declare class SenecImageRenderer {
19
+ private readonly width;
20
+ private readonly height;
21
+ private readonly fontName;
22
+ /** Reference to the loaded pureimage module (when that engine is used). */
23
+ private pureImage;
24
+ /** Cached across instances: registering a font is a process-wide action. */
25
+ private static fontLoaded;
26
+ /**
27
+ * @param width Target width in pixels (default 1600, the reference width).
28
+ * @param height Target height in pixels. If omitted, it is derived from
29
+ * the width to preserve the 16:9 reference aspect ratio.
30
+ */
31
+ constructor(width?: number, height?: number);
32
+ /**
33
+ * Render the dashboard for the given SENEC + weather data.
34
+ *
35
+ * Accepts either a pre-built DashboardModel, or a SenecData object with
36
+ * an optional WeatherData argument (which are merged internally).
37
+ *
38
+ * @returns PNG image as a Buffer.
39
+ */
40
+ render(data: SenecData | DashboardModel, weather?: WeatherData | null): Promise<Buffer>;
41
+ private toModel;
42
+ private isDashboardModel;
43
+ /**
44
+ * Temporarily filter pureimage's "can't project the same paths" warnings
45
+ * from console.warn. Returns a function that restores the original.
46
+ */
47
+ private suppressPureImagePathWarnings;
48
+ private drawDashboard;
49
+ private drawStatusLine;
50
+ private shortTime;
51
+ private drawCardShell;
52
+ private drawFlowCard;
53
+ private flowPoint;
54
+ private drawConnector;
55
+ private drawEnergyNode;
56
+ private drawAutarkyCard;
57
+ private drawRing;
58
+ private drawWeatherCard;
59
+ private drawGlyph;
60
+ /**
61
+ * Map a weather emoji to a simple vector sun/cloud/rain glyph.
62
+ */
63
+ private drawWeatherGlyph;
64
+ private rectPath;
65
+ private createCanvas;
66
+ /**
67
+ * Turn on antialiasing / image smoothing for whichever engine is active.
68
+ * Both node-canvas and pureimage expose `imageSmoothingEnabled`; pureimage
69
+ * also honours an internal `_settings.antialias` flag for paths and text.
70
+ */
71
+ private enableAntialiasing;
72
+ /**
73
+ * pureimage cannot render text without a registered TrueType font.
74
+ */
75
+ private ensurePureImageFont;
76
+ private waitForFontLoaded;
77
+ private encodePureImage;
78
+ /** The reference unit: ~1% of dashboard width (mirrors CSS --u/1cqw). */
79
+ private u;
80
+ private clamp;
81
+ /** Scale a source-resolution stroke width to the target canvas width. */
82
+ private scaleStroke;
83
+ /**
84
+ * Horizontal chord width available inside a circle at a given vertical
85
+ * offset from the center, minus a small inset so text stays clear of the
86
+ * edge.
87
+ */
88
+ private chordWidth;
89
+ private roundedRect;
90
+ private text;
91
+ private textWidth;
92
+ private currentFontSize;
93
+ /**
94
+ * Set the drawing font.
95
+ *
96
+ * pureimage 0.4.x only supports the `<size>px <family>` form, so we never
97
+ * emit a weight prefix and instead convey emphasis through a slightly
98
+ * larger size.
99
+ */
100
+ private setFont;
101
+ /**
102
+ * Draw text, automatically shrinking the font so it fits within
103
+ * `maxWidth`. Preserves the current textAlign/textBaseline.
104
+ */
105
+ private fitText;
106
+ }
107
+ //# sourceMappingURL=senec-image-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"senec-image-renderer.d.ts","sourceRoot":"","sources":["../../src/lib/senec-image-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAgB,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAKL,cAAc,EAKf,MAAM,oBAAoB,CAAC;AAwC5B,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,2EAA2E;IAC3E,OAAO,CAAC,SAAS,CAAa;IAE9B,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,UAAU,CAAS;IAElC;;;;OAIG;gBACS,KAAK,SAAO,EAAE,MAAM,CAAC,EAAE,MAAM;IAMzC;;;;;;;OAOG;IACG,MAAM,CACV,IAAI,EAAE,SAAS,GAAG,cAAc,EAChC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,GAC3B,OAAO,CAAC,MAAM,CAAC;IAkBlB,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,gBAAgB;IAUxB;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAiBrC,OAAO,CAAC,aAAa;IA0CrB,OAAO,CAAC,cAAc;IAkDtB,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,cAAc;IAwGtB,OAAO,CAAC,eAAe;IAoCvB,OAAO,CAAC,QAAQ;IAqChB,OAAO,CAAC,eAAe;IAoHvB,OAAO,CAAC,SAAS;IA8EjB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuExB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;OAEG;YACW,mBAAmB;YAoDnB,iBAAiB;IAU/B,OAAO,CAAC,eAAe;IA4BvB,yEAAyE;IACzE,OAAO,CAAC,CAAC;IAIT,OAAO,CAAC,KAAK;IAIb,yEAAyE;IACzE,OAAO,CAAC,WAAW;IAInB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,IAAI;IAsCZ,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,eAAe;IAKvB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO;IAKf;;;OAGG;IACH,OAAO,CAAC,OAAO;CAmBhB"}