node-red-contrib-senec-cloud-v2 0.2.0
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 +77 -0
- package/LICENSE +21 -0
- package/README.md +279 -0
- package/dist/lib/auth-manager.d.ts +63 -0
- package/dist/lib/auth-manager.d.ts.map +1 -0
- package/dist/lib/auth-manager.js +288 -0
- package/dist/lib/auth-manager.js.map +1 -0
- package/dist/lib/data-parser.d.ts +68 -0
- package/dist/lib/data-parser.d.ts.map +1 -0
- package/dist/lib/data-parser.js +197 -0
- package/dist/lib/data-parser.js.map +1 -0
- package/dist/lib/error-handler.d.ts +39 -0
- package/dist/lib/error-handler.d.ts.map +1 -0
- package/dist/lib/error-handler.js +139 -0
- package/dist/lib/error-handler.js.map +1 -0
- package/dist/lib/senec-api-client.d.ts +45 -0
- package/dist/lib/senec-api-client.d.ts.map +1 -0
- package/dist/lib/senec-api-client.js +145 -0
- package/dist/lib/senec-api-client.js.map +1 -0
- package/dist/nodes/senec-config.d.ts +2 -0
- package/dist/nodes/senec-config.d.ts.map +1 -0
- package/dist/nodes/senec-config.html +47 -0
- package/dist/nodes/senec-config.js +16 -0
- package/dist/nodes/senec-config.js.map +1 -0
- package/dist/nodes/senec-data.d.ts +2 -0
- package/dist/nodes/senec-data.d.ts.map +1 -0
- package/dist/nodes/senec-data.html +82 -0
- package/dist/nodes/senec-data.js +66 -0
- package/dist/nodes/senec-data.js.map +1 -0
- package/dist/nodes/senec-image.d.ts +2 -0
- package/dist/nodes/senec-image.d.ts.map +1 -0
- package/dist/nodes/senec-image.html +94 -0
- package/dist/nodes/senec-image.js +34 -0
- package/dist/nodes/senec-image.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Authentication Manager
|
|
4
|
+
*
|
|
5
|
+
* Handles SENEC Cloud authentication, token management, and refresh logic.
|
|
6
|
+
*
|
|
7
|
+
* Since the SENEC API change (August 2025), authentication uses the
|
|
8
|
+
* SENEC Keycloak SSO with the OAuth2 Authorization Code + PKCE flow,
|
|
9
|
+
* emulating the official SENEC app:
|
|
10
|
+
*
|
|
11
|
+
* 1. GET /auth (login page, collects session cookies)
|
|
12
|
+
* 2. POST username to the login form
|
|
13
|
+
* 3. POST password to the second-step form
|
|
14
|
+
* 4. Receive redirect to senec-app-auth://keycloak.prod?code=...
|
|
15
|
+
* 5. Exchange authorization code for access + refresh tokens
|
|
16
|
+
*
|
|
17
|
+
* Access tokens are short-lived (~5 minutes); refresh tokens last ~30 days.
|
|
18
|
+
* Token refresh uses the standard OAuth2 refresh_token grant.
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
54
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.AuthenticationManager = void 0;
|
|
58
|
+
const axios_1 = __importDefault(require("axios"));
|
|
59
|
+
const crypto = __importStar(require("crypto"));
|
|
60
|
+
const querystring = __importStar(require("querystring"));
|
|
61
|
+
const SSO_BASE = 'https://sso.senec.com/realms/senec/protocol/openid-connect';
|
|
62
|
+
const CLIENT_ID = 'endcustomer-app-frontend';
|
|
63
|
+
const REDIRECT_URI = 'senec-app-auth://keycloak.prod';
|
|
64
|
+
const OAUTH_SCOPE = 'roles meinsenec';
|
|
65
|
+
function base64url(buffer) {
|
|
66
|
+
return buffer
|
|
67
|
+
.toString('base64')
|
|
68
|
+
.replace(/\+/g, '-')
|
|
69
|
+
.replace(/\//g, '_')
|
|
70
|
+
.replace(/=+$/, '');
|
|
71
|
+
}
|
|
72
|
+
class AuthenticationManager {
|
|
73
|
+
constructor(credentials) {
|
|
74
|
+
this.token = null;
|
|
75
|
+
this.tokenExpiry = null;
|
|
76
|
+
this.refreshTokenValue = null;
|
|
77
|
+
this.refreshTokenExpiry = null;
|
|
78
|
+
this.credentials = credentials;
|
|
79
|
+
this.httpClient = axios_1.default.create({
|
|
80
|
+
timeout: 30000,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Authenticate with SENEC Cloud (OAuth2 Authorization Code + PKCE)
|
|
85
|
+
*/
|
|
86
|
+
async authenticate() {
|
|
87
|
+
try {
|
|
88
|
+
const codeVerifier = base64url(crypto.randomBytes(64));
|
|
89
|
+
const codeChallenge = base64url(crypto.createHash('sha256').update(codeVerifier).digest());
|
|
90
|
+
// Cookie jar for the Keycloak login session
|
|
91
|
+
const cookies = {};
|
|
92
|
+
const storeCookies = (headers) => {
|
|
93
|
+
const setCookie = headers['set-cookie'] || [];
|
|
94
|
+
for (const c of setCookie) {
|
|
95
|
+
const [pair] = c.split(';');
|
|
96
|
+
const eq = pair.indexOf('=');
|
|
97
|
+
if (eq > 0) {
|
|
98
|
+
cookies[pair.substring(0, eq).trim()] = pair.substring(eq + 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const cookieHeader = () => Object.entries(cookies)
|
|
103
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
104
|
+
.join('; ');
|
|
105
|
+
// Step 1: GET login page
|
|
106
|
+
const authUrl = `${SSO_BASE}/auth?` +
|
|
107
|
+
querystring.stringify({
|
|
108
|
+
client_id: CLIENT_ID,
|
|
109
|
+
redirect_uri: REDIRECT_URI,
|
|
110
|
+
response_type: 'code',
|
|
111
|
+
scope: OAUTH_SCOPE,
|
|
112
|
+
code_challenge: codeChallenge,
|
|
113
|
+
code_challenge_method: 'S256',
|
|
114
|
+
});
|
|
115
|
+
const loginPage = await this.httpClient.get(authUrl);
|
|
116
|
+
storeCookies(loginPage.headers);
|
|
117
|
+
const extractFormAction = (html) => {
|
|
118
|
+
const match = html.match(/action="([^"]+)"/);
|
|
119
|
+
return match ? match[1].replace(/&/g, '&') : null;
|
|
120
|
+
};
|
|
121
|
+
const usernameFormAction = extractFormAction(loginPage.data);
|
|
122
|
+
if (!usernameFormAction) {
|
|
123
|
+
throw new Error('SENEC login page did not contain a login form');
|
|
124
|
+
}
|
|
125
|
+
const postForm = async (action, fields) => this.httpClient.post(action, querystring.stringify(fields), {
|
|
126
|
+
headers: {
|
|
127
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
128
|
+
Cookie: cookieHeader(),
|
|
129
|
+
},
|
|
130
|
+
maxRedirects: 0,
|
|
131
|
+
validateStatus: (s) => s === 200 || s === 302,
|
|
132
|
+
});
|
|
133
|
+
// Step 2: submit username
|
|
134
|
+
let response = await postForm(usernameFormAction, {
|
|
135
|
+
username: this.credentials.username,
|
|
136
|
+
credentialId: '',
|
|
137
|
+
});
|
|
138
|
+
storeCookies(response.headers);
|
|
139
|
+
// Step 3: submit password (second form step)
|
|
140
|
+
if (response.status === 200) {
|
|
141
|
+
const passwordFormAction = extractFormAction(response.data);
|
|
142
|
+
if (!passwordFormAction) {
|
|
143
|
+
throw new Error('Invalid username (SENEC login rejected the account name)');
|
|
144
|
+
}
|
|
145
|
+
response = await postForm(passwordFormAction, {
|
|
146
|
+
password: this.credentials.password,
|
|
147
|
+
credentialId: '',
|
|
148
|
+
});
|
|
149
|
+
storeCookies(response.headers);
|
|
150
|
+
}
|
|
151
|
+
if (response.status !== 302) {
|
|
152
|
+
// Form re-rendered => wrong credentials
|
|
153
|
+
throw new Error('Invalid username or password (check your SENEC app credentials)');
|
|
154
|
+
}
|
|
155
|
+
// Step 4: extract authorization code from redirect
|
|
156
|
+
const location = response.headers.location || '';
|
|
157
|
+
const codeMatch = location.match(/[?&#]code=([^&]+)/);
|
|
158
|
+
if (!codeMatch) {
|
|
159
|
+
throw new Error('SENEC login did not return an authorization code');
|
|
160
|
+
}
|
|
161
|
+
// Step 5: exchange code for tokens
|
|
162
|
+
const tokenResponse = await this.httpClient.post(`${SSO_BASE}/token`, querystring.stringify({
|
|
163
|
+
grant_type: 'authorization_code',
|
|
164
|
+
client_id: CLIENT_ID,
|
|
165
|
+
redirect_uri: REDIRECT_URI,
|
|
166
|
+
code: codeMatch[1],
|
|
167
|
+
code_verifier: codeVerifier,
|
|
168
|
+
}), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
169
|
+
this.storeTokens(tokenResponse.data);
|
|
170
|
+
if (!this.token || !this.tokenExpiry) {
|
|
171
|
+
throw new Error('Failed to obtain valid token from authentication response');
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
token: this.token,
|
|
175
|
+
expiry: this.tokenExpiry,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.clearToken();
|
|
180
|
+
throw new Error(`Authentication failed: ${this.describeError(error)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Refresh the authentication token using the OAuth2 refresh_token grant.
|
|
185
|
+
* Falls back to a full re-authentication if no valid refresh token exists
|
|
186
|
+
* or the refresh fails.
|
|
187
|
+
*/
|
|
188
|
+
async refreshToken() {
|
|
189
|
+
const refreshUsable = this.refreshTokenValue &&
|
|
190
|
+
(!this.refreshTokenExpiry || Date.now() < this.refreshTokenExpiry.getTime() - 60000);
|
|
191
|
+
if (!refreshUsable) {
|
|
192
|
+
return this.authenticate();
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const response = await this.httpClient.post(`${SSO_BASE}/token`, querystring.stringify({
|
|
196
|
+
grant_type: 'refresh_token',
|
|
197
|
+
client_id: CLIENT_ID,
|
|
198
|
+
refresh_token: this.refreshTokenValue,
|
|
199
|
+
}), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
200
|
+
this.storeTokens(response.data);
|
|
201
|
+
if (!this.token || !this.tokenExpiry) {
|
|
202
|
+
throw new Error('Failed to obtain valid token from refresh response');
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
token: this.token,
|
|
206
|
+
expiry: this.tokenExpiry,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
// If refresh fails, try full re-authentication
|
|
211
|
+
return this.authenticate();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if the current token is valid
|
|
216
|
+
*/
|
|
217
|
+
isTokenValid() {
|
|
218
|
+
if (!this.token || !this.tokenExpiry) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
// Consider token invalid if it expires in less than 30 seconds
|
|
222
|
+
// (SENEC access tokens are only valid for ~5 minutes)
|
|
223
|
+
const bufferTime = 30 * 1000;
|
|
224
|
+
return Date.now() < this.tokenExpiry.getTime() - bufferTime;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Whether a (probably valid) refresh token is available
|
|
228
|
+
*/
|
|
229
|
+
canRefresh() {
|
|
230
|
+
return (!!this.refreshTokenValue &&
|
|
231
|
+
(!this.refreshTokenExpiry || Date.now() < this.refreshTokenExpiry.getTime() - 60000));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get the current token
|
|
235
|
+
*/
|
|
236
|
+
getToken() {
|
|
237
|
+
return this.token;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Clear the stored tokens
|
|
241
|
+
*/
|
|
242
|
+
clearToken() {
|
|
243
|
+
this.token = null;
|
|
244
|
+
this.tokenExpiry = null;
|
|
245
|
+
this.refreshTokenValue = null;
|
|
246
|
+
this.refreshTokenExpiry = null;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Store tokens from a Keycloak token endpoint response
|
|
250
|
+
*/
|
|
251
|
+
storeTokens(data) {
|
|
252
|
+
this.token = data.access_token || null;
|
|
253
|
+
const expiresIn = data.expires_in || 300;
|
|
254
|
+
this.tokenExpiry = new Date(Date.now() + expiresIn * 1000);
|
|
255
|
+
if (data.refresh_token) {
|
|
256
|
+
this.refreshTokenValue = data.refresh_token;
|
|
257
|
+
const refreshExpiresIn = data.refresh_expires_in || 2592000;
|
|
258
|
+
this.refreshTokenExpiry = new Date(Date.now() + refreshExpiresIn * 1000);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Build a user-friendly error description without leaking credentials
|
|
263
|
+
*/
|
|
264
|
+
describeError(error) {
|
|
265
|
+
const axiosError = error;
|
|
266
|
+
if (axiosError && axiosError.isAxiosError) {
|
|
267
|
+
const status = axiosError.response?.status;
|
|
268
|
+
if (status === 401 || status === 403) {
|
|
269
|
+
return 'Invalid username or password (check your SENEC app credentials)';
|
|
270
|
+
}
|
|
271
|
+
if (status === 429) {
|
|
272
|
+
return 'Too many login attempts - rate limited by SENEC Cloud, please wait';
|
|
273
|
+
}
|
|
274
|
+
if (status) {
|
|
275
|
+
return `SENEC Cloud returned HTTP ${status}`;
|
|
276
|
+
}
|
|
277
|
+
if (axiosError.code === 'ECONNABORTED' || axiosError.code === 'ETIMEDOUT') {
|
|
278
|
+
return 'Connection timeout while contacting SENEC Cloud';
|
|
279
|
+
}
|
|
280
|
+
if (axiosError.code === 'ENOTFOUND' || axiosError.code === 'ECONNREFUSED') {
|
|
281
|
+
return 'Cannot reach SENEC Cloud - check your internet connection';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.AuthenticationManager = AuthenticationManager;
|
|
288
|
+
//# sourceMappingURL=auth-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-manager.js","sourceRoot":"","sources":["../../src/lib/auth-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses SENEC Cloud App API dashboard responses (v2 format from
|
|
5
|
+
* app-gateway.prod.senec.dev) and transforms them into the expected
|
|
6
|
+
* payload structure for backward compatibility.
|
|
7
|
+
*/
|
|
8
|
+
import { SenecData, ValidationResult } from '../types/senec';
|
|
9
|
+
export declare class DataParser {
|
|
10
|
+
private debugMode;
|
|
11
|
+
constructor(debugMode?: boolean);
|
|
12
|
+
/**
|
|
13
|
+
* Parse SENEC Cloud App API v2 dashboard response into structured data.
|
|
14
|
+
*
|
|
15
|
+
* Expected input structure (v2/senec/systems/{id}/dashboard):
|
|
16
|
+
* {
|
|
17
|
+
* currently: {
|
|
18
|
+
* powerGenerationInW, powerConsumptionInW,
|
|
19
|
+
* gridFeedInInW, gridDrawInW,
|
|
20
|
+
* batteryChargeInW, batteryDischargeInW,
|
|
21
|
+
* batteryLevelInPercent, selfSufficiencyInPercent, wallboxInW
|
|
22
|
+
* },
|
|
23
|
+
* today: {
|
|
24
|
+
* powerGenerationInWh, powerConsumptionInWh,
|
|
25
|
+
* gridFeedInInWh, gridDrawInWh, ...
|
|
26
|
+
* },
|
|
27
|
+
* timestamp: ISO string
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* @param apiResponse - Raw dashboard API response from SENEC Cloud
|
|
31
|
+
* @param systemInfo - Optional system master data (from /v1/senec/anlagen)
|
|
32
|
+
* @throws {Error} If apiResponse is null, undefined, or invalid
|
|
33
|
+
*/
|
|
34
|
+
parseEnergyData(apiResponse: any, systemInfo?: any): SenecData;
|
|
35
|
+
/**
|
|
36
|
+
* Validate parsed data
|
|
37
|
+
* @param data - Parsed SENEC data to validate
|
|
38
|
+
* @returns Validation result with errors and warnings
|
|
39
|
+
*/
|
|
40
|
+
validateData(data: SenecData): ValidationResult;
|
|
41
|
+
/**
|
|
42
|
+
* Extract value from nested object using dot notation path
|
|
43
|
+
* @param obj - Source object
|
|
44
|
+
* @param path - Dot notation path (e.g., 'grid.import.today')
|
|
45
|
+
* @param defaultValue - Default value if path not found
|
|
46
|
+
* @returns Extracted value or default value
|
|
47
|
+
*/
|
|
48
|
+
private extractValue;
|
|
49
|
+
/**
|
|
50
|
+
* Log debug message
|
|
51
|
+
* @param method - Method name where log originated
|
|
52
|
+
* @param message - Debug message
|
|
53
|
+
*/
|
|
54
|
+
private logDebug;
|
|
55
|
+
/**
|
|
56
|
+
* Log warning message
|
|
57
|
+
* @param method - Method name where warning originated
|
|
58
|
+
* @param message - Warning message
|
|
59
|
+
*/
|
|
60
|
+
private logWarning;
|
|
61
|
+
/**
|
|
62
|
+
* Log error message
|
|
63
|
+
* @param method - Method name where error originated
|
|
64
|
+
* @param error - Error object
|
|
65
|
+
*/
|
|
66
|
+
private logError;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=data-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-parser.d.ts","sourceRoot":"","sources":["../../src/lib/data-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAU;gBAEf,SAAS,GAAE,OAAe;IAItC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,eAAe,CAAC,WAAW,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,GAAG,SAAS;IAkD9D;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,gBAAgB;IA2D/C;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAMhB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;CAMjB"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Data Parser
|
|
4
|
+
*
|
|
5
|
+
* Parses SENEC Cloud App API dashboard responses (v2 format from
|
|
6
|
+
* app-gateway.prod.senec.dev) and transforms them into the expected
|
|
7
|
+
* payload structure for backward compatibility.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.DataParser = void 0;
|
|
11
|
+
class DataParser {
|
|
12
|
+
constructor(debugMode = false) {
|
|
13
|
+
this.debugMode = debugMode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse SENEC Cloud App API v2 dashboard response into structured data.
|
|
17
|
+
*
|
|
18
|
+
* Expected input structure (v2/senec/systems/{id}/dashboard):
|
|
19
|
+
* {
|
|
20
|
+
* currently: {
|
|
21
|
+
* powerGenerationInW, powerConsumptionInW,
|
|
22
|
+
* gridFeedInInW, gridDrawInW,
|
|
23
|
+
* batteryChargeInW, batteryDischargeInW,
|
|
24
|
+
* batteryLevelInPercent, selfSufficiencyInPercent, wallboxInW
|
|
25
|
+
* },
|
|
26
|
+
* today: {
|
|
27
|
+
* powerGenerationInWh, powerConsumptionInWh,
|
|
28
|
+
* gridFeedInInWh, gridDrawInWh, ...
|
|
29
|
+
* },
|
|
30
|
+
* timestamp: ISO string
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* @param apiResponse - Raw dashboard API response from SENEC Cloud
|
|
34
|
+
* @param systemInfo - Optional system master data (from /v1/senec/anlagen)
|
|
35
|
+
* @throws {Error} If apiResponse is null, undefined, or invalid
|
|
36
|
+
*/
|
|
37
|
+
parseEnergyData(apiResponse, systemInfo) {
|
|
38
|
+
try {
|
|
39
|
+
// Validate input
|
|
40
|
+
if (!apiResponse || typeof apiResponse !== 'object') {
|
|
41
|
+
const error = new Error('Invalid API response: response must be a non-null object');
|
|
42
|
+
this.logError('parseEnergyData', error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
// Today values come in Wh from the API; old payload used kWh
|
|
46
|
+
const whToKwh = (value) => Math.round((value / 1000) * 1000) / 1000;
|
|
47
|
+
const parsedData = {
|
|
48
|
+
steuereinheitState: this.extractValue(systemInfo, 'systemType', 'OK'),
|
|
49
|
+
gridimport: {
|
|
50
|
+
today: whToKwh(this.extractValue(apiResponse, 'today.gridDrawInWh', 0)),
|
|
51
|
+
now: this.extractValue(apiResponse, 'currently.gridDrawInW', 0),
|
|
52
|
+
},
|
|
53
|
+
powergenerated: {
|
|
54
|
+
today: whToKwh(this.extractValue(apiResponse, 'today.powerGenerationInWh', 0)),
|
|
55
|
+
now: this.extractValue(apiResponse, 'currently.powerGenerationInW', 0),
|
|
56
|
+
},
|
|
57
|
+
consumption: {
|
|
58
|
+
today: whToKwh(this.extractValue(apiResponse, 'today.powerConsumptionInWh', 0)),
|
|
59
|
+
now: this.extractValue(apiResponse, 'currently.powerConsumptionInW', 0),
|
|
60
|
+
},
|
|
61
|
+
gridexport: {
|
|
62
|
+
today: whToKwh(this.extractValue(apiResponse, 'today.gridFeedInInWh', 0)),
|
|
63
|
+
now: this.extractValue(apiResponse, 'currently.gridFeedInInW', 0),
|
|
64
|
+
},
|
|
65
|
+
acculevel: {
|
|
66
|
+
now: this.extractValue(apiResponse, 'currently.batteryLevelInPercent', 0),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
this.logDebug('parseEnergyData', 'Successfully parsed SENEC energy data');
|
|
70
|
+
return parsedData;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const errorMessage = `Failed to parse energy data: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
74
|
+
this.logError('parseEnergyData', new Error(errorMessage));
|
|
75
|
+
throw new Error(errorMessage);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate parsed data
|
|
80
|
+
* @param data - Parsed SENEC data to validate
|
|
81
|
+
* @returns Validation result with errors and warnings
|
|
82
|
+
*/
|
|
83
|
+
validateData(data) {
|
|
84
|
+
try {
|
|
85
|
+
const errors = [];
|
|
86
|
+
const warnings = [];
|
|
87
|
+
// Validate battery level
|
|
88
|
+
if (data.acculevel.now < 0 || data.acculevel.now > 100) {
|
|
89
|
+
const error = `Invalid battery level: ${data.acculevel.now}% (must be 0-100)`;
|
|
90
|
+
errors.push(error);
|
|
91
|
+
this.logError('validateData', new Error(error));
|
|
92
|
+
}
|
|
93
|
+
// Validate power values (should be non-negative)
|
|
94
|
+
if (data.powergenerated.now < 0) {
|
|
95
|
+
const warning = `Negative solar production: ${data.powergenerated.now}W`;
|
|
96
|
+
warnings.push(warning);
|
|
97
|
+
this.logWarning('validateData', warning);
|
|
98
|
+
}
|
|
99
|
+
if (data.consumption.now < 0) {
|
|
100
|
+
const warning = `Negative consumption: ${data.consumption.now}W`;
|
|
101
|
+
warnings.push(warning);
|
|
102
|
+
this.logWarning('validateData', warning);
|
|
103
|
+
}
|
|
104
|
+
// Check for suspiciously high values (>100kW for home system)
|
|
105
|
+
const maxPower = 100000; // 100kW
|
|
106
|
+
if (data.powergenerated.now > maxPower) {
|
|
107
|
+
const warning = `Unusually high solar production: ${data.powergenerated.now}W`;
|
|
108
|
+
warnings.push(warning);
|
|
109
|
+
this.logWarning('validateData', warning);
|
|
110
|
+
}
|
|
111
|
+
if (data.consumption.now > maxPower) {
|
|
112
|
+
const warning = `Unusually high consumption: ${data.consumption.now}W`;
|
|
113
|
+
warnings.push(warning);
|
|
114
|
+
this.logWarning('validateData', warning);
|
|
115
|
+
}
|
|
116
|
+
const result = {
|
|
117
|
+
valid: errors.length === 0,
|
|
118
|
+
errors,
|
|
119
|
+
warnings,
|
|
120
|
+
};
|
|
121
|
+
if (result.valid) {
|
|
122
|
+
this.logDebug('validateData', 'Data validation passed');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
this.logError('validateData', new Error(`Data validation failed with ${errors.length} error(s)`));
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const errorMessage = `Validation process failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
131
|
+
this.logError('validateData', new Error(errorMessage));
|
|
132
|
+
throw new Error(errorMessage);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Extract value from nested object using dot notation path
|
|
137
|
+
* @param obj - Source object
|
|
138
|
+
* @param path - Dot notation path (e.g., 'grid.import.today')
|
|
139
|
+
* @param defaultValue - Default value if path not found
|
|
140
|
+
* @returns Extracted value or default value
|
|
141
|
+
*/
|
|
142
|
+
extractValue(obj, path, defaultValue) {
|
|
143
|
+
try {
|
|
144
|
+
const keys = path.split('.');
|
|
145
|
+
let current = obj;
|
|
146
|
+
for (const key of keys) {
|
|
147
|
+
if (current && typeof current === 'object' && key in current) {
|
|
148
|
+
current = current[key];
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.logDebug('extractValue', `Path '${path}' not found, using default value`);
|
|
152
|
+
return defaultValue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const result = current !== undefined && current !== null ? current : defaultValue;
|
|
156
|
+
if (result === defaultValue) {
|
|
157
|
+
this.logDebug('extractValue', `Path '${path}' returned null/undefined, using default value`);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
this.logError('extractValue', new Error(`Failed to extract value from path '${path}': ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
163
|
+
return defaultValue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Log debug message
|
|
168
|
+
* @param method - Method name where log originated
|
|
169
|
+
* @param message - Debug message
|
|
170
|
+
*/
|
|
171
|
+
logDebug(method, message) {
|
|
172
|
+
if (this.debugMode) {
|
|
173
|
+
process.stderr.write(`[DataParser.${method}] DEBUG: ${message}\n`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Log warning message
|
|
178
|
+
* @param method - Method name where warning originated
|
|
179
|
+
* @param message - Warning message
|
|
180
|
+
*/
|
|
181
|
+
logWarning(method, message) {
|
|
182
|
+
process.stderr.write(`[DataParser.${method}] WARNING: ${message}\n`);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Log error message
|
|
186
|
+
* @param method - Method name where error originated
|
|
187
|
+
* @param error - Error object
|
|
188
|
+
*/
|
|
189
|
+
logError(method, error) {
|
|
190
|
+
process.stderr.write(`[DataParser.${method}] ERROR: ${error.message}\n`);
|
|
191
|
+
if (this.debugMode && error.stack) {
|
|
192
|
+
process.stderr.write(`${error.stack}\n`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.DataParser = DataParser;
|
|
197
|
+
//# sourceMappingURL=data-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-parser.js","sourceRoot":"","sources":["../../src/lib/data-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler
|
|
3
|
+
*
|
|
4
|
+
* Centralized error handling with classification and retry logic.
|
|
5
|
+
*/
|
|
6
|
+
import { AxiosError } from 'axios';
|
|
7
|
+
import { ErrorType, HandledError } from '../types/senec';
|
|
8
|
+
export declare class ErrorHandler {
|
|
9
|
+
/**
|
|
10
|
+
* Handle and classify an error
|
|
11
|
+
*/
|
|
12
|
+
handleError(error: Error | AxiosError): HandledError;
|
|
13
|
+
/**
|
|
14
|
+
* Classify error by type
|
|
15
|
+
*/
|
|
16
|
+
classifyError(error: Error | AxiosError): ErrorType;
|
|
17
|
+
/**
|
|
18
|
+
* Determine if error should be retried
|
|
19
|
+
*/
|
|
20
|
+
shouldRetry(error: Error | AxiosError): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Calculate retry delay with exponential backoff
|
|
23
|
+
* @param attempt - Retry attempt number (1-based)
|
|
24
|
+
*/
|
|
25
|
+
getRetryDelay(attempt: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Get Retry-After header value if present (for rate limiting)
|
|
28
|
+
*/
|
|
29
|
+
getRetryAfter(error: AxiosError): number | null;
|
|
30
|
+
/**
|
|
31
|
+
* Get user-friendly error message
|
|
32
|
+
*/
|
|
33
|
+
private getErrorMessage;
|
|
34
|
+
/**
|
|
35
|
+
* Type guard for AxiosError
|
|
36
|
+
*/
|
|
37
|
+
private isAxiosError;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/lib/error-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,YAAY;IACvB;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,YAAY;IAcpD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS;IA8BnD;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,OAAO;IAsB/C;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAgBtC;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAsB/C;;OAEG;IACH,OAAO,CAAC,eAAe;IA4BvB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|