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.
- package/CHANGELOG.md +22 -0
- package/dist/assets/fonts/senec-sans.ttf +0 -0
- package/dist/lib/dashboard-html-renderer.d.ts +18 -0
- package/dist/lib/dashboard-html-renderer.d.ts.map +1 -0
- package/dist/lib/dashboard-html-renderer.js +604 -0
- package/dist/lib/dashboard-html-renderer.js.map +1 -0
- package/dist/lib/dashboard-layout.d.ts +152 -0
- package/dist/lib/dashboard-layout.d.ts.map +1 -0
- package/dist/lib/dashboard-layout.js +201 -0
- package/dist/lib/dashboard-layout.js.map +1 -0
- package/dist/lib/geocoding-client.d.ts +61 -0
- package/dist/lib/geocoding-client.d.ts.map +1 -0
- package/dist/lib/geocoding-client.js +77 -0
- package/dist/lib/geocoding-client.js.map +1 -0
- package/dist/lib/senec-image-renderer.d.ts +107 -0
- package/dist/lib/senec-image-renderer.d.ts.map +1 -0
- package/dist/lib/senec-image-renderer.js +872 -0
- package/dist/lib/senec-image-renderer.js.map +1 -0
- package/dist/lib/senec-layout.d.ts +212 -0
- package/dist/lib/senec-layout.d.ts.map +1 -0
- package/dist/lib/senec-layout.js +252 -0
- package/dist/lib/senec-layout.js.map +1 -0
- package/dist/lib/weather-client.d.ts +62 -0
- package/dist/lib/weather-client.d.ts.map +1 -0
- package/dist/lib/weather-client.js +234 -0
- package/dist/lib/weather-client.js.map +1 -0
- package/dist/nodes/senec-data.js +10 -2
- package/dist/nodes/senec-data.js.map +1 -1
- package/dist/nodes/senec-image.html +73 -53
- package/dist/nodes/senec-image.js +189 -14
- package/dist/nodes/senec-image.js.map +1 -1
- package/dist/nodes/weather.d.ts +2 -0
- package/dist/nodes/weather.d.ts.map +1 -0
- package/dist/nodes/weather.html +179 -0
- package/dist/nodes/weather.js +138 -0
- package/dist/nodes/weather.js.map +1 -0
- 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"}
|