gwatt 0.0.1

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/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "gwatt",
3
+ "version": "0.0.1",
4
+ "description": "Agent-first CLI for Growatt solar monitoring — JSON output, device filtering, hourly history",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "growatt",
9
+ "solar",
10
+ "cli",
11
+ "energy",
12
+ "inverter"
13
+ ],
14
+ "module": "src/index.ts",
15
+ "type": "module",
16
+ "bin": {
17
+ "gwat": "src/index.ts"
18
+ },
19
+ "files": [
20
+ "src/**/*",
21
+ "package.json",
22
+ "README.md"
23
+ ],
24
+ "engines": {
25
+ "bun": ">=1.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/bun": "latest"
29
+ },
30
+ "peerDependencies": {
31
+ "typescript": "^5"
32
+ },
33
+ "dependencies": {
34
+ "arg": "^5.0.2"
35
+ }
36
+ }
package/src/api.ts ADDED
@@ -0,0 +1,233 @@
1
+ import { createHash } from "crypto";
2
+ import { type Config } from "./config";
3
+
4
+ const DEFAULT_SERVER = "https://mqtt.growatt.com";
5
+
6
+ const HEADERS = {
7
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
8
+ "Accept": "application/json, text/javascript, */*; q=0.01",
9
+ "X-Requested-With": "XMLHttpRequest",
10
+ "Referer": "https://mqtt.growatt.com/",
11
+ };
12
+
13
+ export function hashPassword(password: string): string {
14
+ let md5 = createHash("md5").update(password).digest("hex");
15
+ for (let i = 0; i < md5.length; i += 2) {
16
+ if (md5[i] === "0") {
17
+ md5 = md5.slice(0, i) + "c" + md5.slice(i + 1);
18
+ }
19
+ }
20
+ return md5;
21
+ }
22
+
23
+ function makeRequest(
24
+ url: string,
25
+ opts: { method?: string; body?: Record<string, string>; headers?: Record<string, string> } = {},
26
+ cookies?: string
27
+ ) {
28
+ const headers = { ...HEADERS, ...opts.headers };
29
+ if (cookies) {
30
+ headers["Cookie"] = cookies;
31
+ }
32
+
33
+ const body = opts.body ? new URLSearchParams(opts.body) : undefined;
34
+ return fetch(url, {
35
+ method: opts.method || "GET",
36
+ headers,
37
+ body: body ? body.toString() : undefined,
38
+ });
39
+ }
40
+
41
+ export async function login(username: string, password: string, serverUrl = DEFAULT_SERVER) {
42
+ const url = `${serverUrl}/newTwoLoginAPI.do`;
43
+ const hashed = hashPassword(password);
44
+
45
+ const resp = await makeRequest(url, {
46
+ method: "POST",
47
+ body: { userName: username, password: hashed },
48
+ headers: {
49
+ "Content-Type": "application/x-www-form-urlencoded",
50
+ },
51
+ });
52
+
53
+ const data = (await resp.json()) as { back?: { success: boolean; msg?: string; data?: any[]; user?: any; deviceCount?: string } };
54
+
55
+ if (!data.back || !data.back.success) {
56
+ throw new Error(`Login failed: ${data.back?.msg || "Unknown error"}`);
57
+ }
58
+
59
+ const cookies = (resp.headers as any).getSetCookie?.() as string[] | undefined;
60
+ const cookieStr = cookies ? cookies.join("; ") : resp.headers.get("set-cookie") || "";
61
+ const token = data.back.user?.token as string | undefined;
62
+ const userId = data.back.user?.id as number | undefined;
63
+ const plant = data.back.data?.[0];
64
+
65
+ return {
66
+ cookies: cookieStr,
67
+ token,
68
+ userId,
69
+ plantId: plant?.plantId as string | undefined,
70
+ plantName: plant?.plantName as string | undefined,
71
+ deviceCount: data.back.deviceCount as string | undefined,
72
+ };
73
+ }
74
+
75
+ export async function getPlantList(config: Config) {
76
+ const url = `${config.serverUrl || DEFAULT_SERVER}/PlantListAPI.do?userId=${config.userId}`;
77
+
78
+ const resp = await makeRequest(url, {}, config.cookies);
79
+ const data = (await resp.json()) as {
80
+ back?: {
81
+ success: boolean;
82
+ totalData?: any;
83
+ data?: any[];
84
+ };
85
+ };
86
+
87
+ if (!data.back || !data.back.success) {
88
+ throw new Error("Failed to fetch plant list");
89
+ }
90
+
91
+ return {
92
+ plants: data.back.data || [],
93
+ total: data.back.totalData,
94
+ };
95
+ }
96
+
97
+ export async function getDevices(config: Config) {
98
+ const url = `${config.serverUrl || DEFAULT_SERVER}/newTwoPlantAPI.do?op=getAllDeviceList&plantId=${config.plantId}&language=1`;
99
+
100
+ const resp = await makeRequest(url, {}, config.cookies);
101
+ const data = (await resp.json()) as any;
102
+
103
+ const devices: Array<{
104
+ name: string;
105
+ serialNumber: string;
106
+ type: string;
107
+ status: number;
108
+ eToday: number;
109
+ eTotal: number;
110
+ currentPower: number;
111
+ powerStr: string;
112
+ eTodayStr: string;
113
+ raw: any;
114
+ }> = [];
115
+
116
+ const list = data?.deviceList;
117
+ if (Array.isArray(list)) {
118
+ for (const item of list) {
119
+ const eToday = parseFloat(item.eToday ?? item.etoday ?? "0");
120
+ const eTotal = parseFloat(item.energy ?? item.etotal ?? item.eTotal ?? "0");
121
+ const currentPower = parseFloat(item.power ?? item.currentPac ?? "0");
122
+ const deviceStatus = item.deviceStatus ?? item.status ?? 0;
123
+
124
+ devices.push({
125
+ name: item.deviceAilas || item.deviceAlias || item.alias || item.deviceName || item.deviceSn || "Unknown",
126
+ serialNumber: item.deviceSn || item.sn || "",
127
+ type: item.deviceType || item.type || "unknown",
128
+ status: deviceStatus,
129
+ eToday,
130
+ eTotal,
131
+ currentPower,
132
+ powerStr: item.powerStr || `${(currentPower / 1000).toFixed(2)}kW`,
133
+ eTodayStr: item.eTodayStr || `${eToday.toFixed(1)}kWh`,
134
+ raw: item,
135
+ });
136
+ }
137
+ }
138
+
139
+ return devices;
140
+ }
141
+
142
+ export async function getHistory(config: Config, type: number, date: string) {
143
+ const url = `${config.serverUrl || DEFAULT_SERVER}/PlantDetailAPI.do?plantId=${config.plantId}&type=${type}&date=${date}`;
144
+
145
+ const resp = await makeRequest(url, {}, config.cookies);
146
+ const data = (await resp.json()) as {
147
+ back?: {
148
+ success: boolean;
149
+ plantData?: any;
150
+ data?: Record<string, string>;
151
+ };
152
+ };
153
+
154
+ if (!data.back || !data.back.success) {
155
+ throw new Error("Failed to fetch history data");
156
+ }
157
+
158
+ const entries = Object.entries(data.back.data || {});
159
+
160
+ // For type=1 (day), values are Watts at each time point; convert to kWh for display
161
+ const isDayView = type === 1;
162
+
163
+ const points = entries.map(([key, value]) => ({
164
+ key,
165
+ value: isDayView ? Math.round((parseFloat(value) / 1000) * 1000) / 1000 : parseFloat(value),
166
+ unit: isDayView ? "kWh" : "kWh",
167
+ }));
168
+
169
+ const total = Math.round(points.reduce((sum, p) => sum + p.value, 0) * 1000) / 1000;
170
+
171
+ return {
172
+ plantData: data.back.plantData,
173
+ points,
174
+ total,
175
+ isDayView,
176
+ };
177
+ }
178
+
179
+ export async function getTodayData(config: Config) {
180
+ const url = `${config.serverUrl || DEFAULT_SERVER}/newTwoPlantAPI.do?op=getUserCenterEnertyDataByPlantid`;
181
+
182
+ const resp = await makeRequest(
183
+ url,
184
+ {
185
+ method: "POST",
186
+ body: {
187
+ language: "1",
188
+ plantId: config.plantId || "",
189
+ },
190
+ headers: {
191
+ "Content-Type": "application/x-www-form-urlencoded",
192
+ },
193
+ },
194
+ config.cookies
195
+ );
196
+
197
+ const data = (await resp.json()) as {
198
+ yearStr?: string;
199
+ weatherMap?: { tmp?: string; cond_txt?: string; cond_code?: string };
200
+ powerValueStr?: string;
201
+ alarmValue?: number;
202
+ plantBean?: {
203
+ plantName?: string;
204
+ nominalPower?: number;
205
+ eToday?: number;
206
+ eTotal?: number;
207
+ formulaMoney?: number;
208
+ formulaMoneyUnitId?: string;
209
+ hasDeviceOnLine?: number;
210
+ hasStorage?: number;
211
+ };
212
+ };
213
+
214
+ return {
215
+ plantName: data.plantBean?.plantName || config.plantName,
216
+ today: data.plantBean?.eToday ?? 0,
217
+ total: data.plantBean?.eTotal ?? 0,
218
+ nominalPower: data.plantBean?.nominalPower ?? 0,
219
+ currency: data.plantBean?.formulaMoneyUnitId || "",
220
+ revenuePerKwh: data.plantBean?.formulaMoney ?? 0,
221
+ todayRevenue: data.plantBean?.eToday && data.plantBean?.formulaMoney
222
+ ? data.plantBean.eToday * data.plantBean.formulaMoney
223
+ : 0,
224
+ totalRevenue: data.plantBean?.eTotal && data.plantBean?.formulaMoney
225
+ ? data.plantBean.eTotal * data.plantBean.formulaMoney
226
+ : 0,
227
+ weather: data.weatherMap?.cond_txt,
228
+ temperature: data.weatherMap?.tmp,
229
+ alarms: data.alarmValue ?? 0,
230
+ devicesOnline: data.plantBean?.hasDeviceOnLine ?? 0,
231
+ hasStorage: data.plantBean?.hasStorage ?? 0,
232
+ };
233
+ }
package/src/config.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+
5
+ const CONFIG_DIR = join(homedir(), ".gwat");
6
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
7
+
8
+ export interface Config {
9
+ userId?: number;
10
+ token?: string;
11
+ cookies?: string;
12
+ serverUrl?: string;
13
+ plantId?: string;
14
+ plantName?: string;
15
+ username?: string;
16
+ }
17
+
18
+ export function readConfig(): Config {
19
+ try {
20
+ const data = readFileSync(CONFIG_FILE, "utf-8");
21
+ return JSON.parse(data) as Config;
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+
27
+ export function writeConfig(config: Config) {
28
+ mkdirSync(CONFIG_DIR, { recursive: true });
29
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
30
+ }
31
+
32
+ export function hasConfig(): boolean {
33
+ try {
34
+ readFileSync(CONFIG_FILE, "utf-8");
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env bun
2
+ import arg from "arg";
3
+ import { readConfig, writeConfig, hasConfig } from "./config";
4
+ import { login, getPlantList, getTodayData, getDevices, getHistory } from "./api";
5
+
6
+ const help = `Usage: gwat <command> [options]
7
+
8
+ Commands:
9
+ login Authenticate with Growatt
10
+ today Current day energy and device breakdown
11
+ history Energy data for day, month, year, or lifetime
12
+ status List all plants and totals
13
+
14
+ Options:
15
+ -u, --username Growatt username
16
+ -p, --password Growatt password
17
+ -s, --server Server URL (default: https://mqtt.growatt.com)
18
+ -d, --day Day to view (YYYY-MM-DD)
19
+ -m, --month Month to view (YYYY-MM)
20
+ -y, --year Year to view (YYYY)
21
+ -t, --total Show lifetime totals
22
+ -n, --device Filter to a specific device name
23
+ -j, --json Output JSON (default: plain text)
24
+ -h, --help Show this help
25
+ -v, --version Show version
26
+
27
+ Examples:
28
+ gwat login -u alice -p secret
29
+ gwat today -j
30
+ gwat today -n Abdulaziz
31
+ gwat history -m 2026-06 -j
32
+ gwat history -t -j
33
+ gwat status -j
34
+ `;
35
+
36
+ const args = arg({
37
+ "--username": String,
38
+ "-u": "--username",
39
+ "--password": String,
40
+ "-p": "--password",
41
+ "--server": String,
42
+ "-s": "--server",
43
+ "--day": String,
44
+ "-d": "--day",
45
+ "--month": String,
46
+ "-m": "--month",
47
+ "--year": String,
48
+ "-y": "--year",
49
+ "--total": Boolean,
50
+ "-t": "--total",
51
+ "--device": String,
52
+ "-n": "--device",
53
+ "--json": Boolean,
54
+ "-j": "--json",
55
+ "--help": Boolean,
56
+ "-h": "--help",
57
+ "--version": Boolean,
58
+ "-v": "--version",
59
+ }, { permissive: true });
60
+
61
+ const [cmd] = args._;
62
+ const isJson = args["--json"] ?? false;
63
+
64
+ if (args["--help"] || (!cmd && !args["--version"])) {
65
+ console.log(help);
66
+ process.exit(0);
67
+ }
68
+
69
+ if (args["--version"]) {
70
+ console.log(isJson ? JSON.stringify({ version: "0.0.1" }) : "gwat 0.0.1");
71
+ process.exit(0);
72
+ }
73
+
74
+ function out(data: unknown) {
75
+ if (isJson) {
76
+ console.log(JSON.stringify(data, null, 2));
77
+ }
78
+ }
79
+
80
+ function fail(err: { error: string; details?: string }) {
81
+ if (isJson) {
82
+ console.log(JSON.stringify(err, null, 2));
83
+ } else {
84
+ console.error(`Error: ${err.error}${err.details ? " - " + err.details : ""}`);
85
+ }
86
+ process.exit(1);
87
+ }
88
+
89
+ async function run() {
90
+ switch (cmd) {
91
+ case "login":
92
+ await handleLogin();
93
+ break;
94
+ case "today":
95
+ await handleToday();
96
+ break;
97
+ case "history":
98
+ await handleHistory();
99
+ break;
100
+ case "status":
101
+ await handleStatus();
102
+ break;
103
+ default:
104
+ fail({ error: "Unknown command", details: cmd });
105
+ }
106
+ }
107
+
108
+ async function handleLogin() {
109
+ const username = args["--username"];
110
+ const password = args["--password"];
111
+ const server = args["--server"];
112
+
113
+ if (!username || !password) {
114
+ fail({ error: "Missing credentials", details: "--username and --password are required" });
115
+ }
116
+
117
+ const result = await login(username, password, server);
118
+
119
+ writeConfig({
120
+ username,
121
+ cookies: result.cookies,
122
+ token: result.token,
123
+ userId: result.userId,
124
+ plantId: result.plantId,
125
+ plantName: result.plantName,
126
+ serverUrl: server || "https://mqtt.growatt.com",
127
+ });
128
+
129
+ out({
130
+ success: true,
131
+ username,
132
+ plantId: result.plantId,
133
+ plantName: result.plantName,
134
+ deviceCount: result.deviceCount,
135
+ });
136
+
137
+ if (!isJson) {
138
+ console.log(`Logged in as ${username}`);
139
+ console.log(`Plant: ${result.plantName || "(unknown)"}`);
140
+ if (result.deviceCount) {
141
+ console.log(`Devices: ${result.deviceCount}`);
142
+ }
143
+ }
144
+ }
145
+
146
+ async function handleToday() {
147
+ if (!hasConfig()) {
148
+ fail({ error: "Not logged in", details: "Run: gwat login -u <username> -p <password>" });
149
+ }
150
+
151
+ const config = readConfig();
152
+ const today = new Date().toISOString().slice(0, 10);
153
+ const deviceFilter = args["--device"];
154
+
155
+ const [data, { plants }, devices, history] = await Promise.all([
156
+ getTodayData(config),
157
+ getPlantList(config),
158
+ getDevices(config),
159
+ getHistory(config, 1, today),
160
+ ]);
161
+
162
+ const plant = plants.find((p: any) => p.plantId === config.plantId);
163
+ const todayEnergyStr = plant?.todayEnergy as string | undefined;
164
+ const todayEnergy = todayEnergyStr
165
+ ? parseFloat(todayEnergyStr.replace(/[^0-9.]/g, ""))
166
+ : data.today;
167
+
168
+ const todayRevenue = todayEnergy && data.revenuePerKwh
169
+ ? todayEnergy * data.revenuePerKwh
170
+ : 0;
171
+
172
+ const sortedHours = history.points.slice().sort((a, b) => a.key.localeCompare(b.key));
173
+
174
+ let filteredDevices = devices;
175
+ if (deviceFilter) {
176
+ const f = deviceFilter.toLowerCase();
177
+ filteredDevices = devices.filter((d) => d.name.toLowerCase().includes(f));
178
+ if (filteredDevices.length === 0) {
179
+ fail({ error: "Device not found", details: `No device matching "${deviceFilter}"` });
180
+ }
181
+ }
182
+
183
+ const isSingleDevice = deviceFilter && filteredDevices.length === 1;
184
+
185
+ if (isSingleDevice) {
186
+ const d = filteredDevices[0];
187
+ const deviceRevenue = d.eToday && data.revenuePerKwh ? d.eToday * data.revenuePerKwh : 0;
188
+ const status = d.status === 1 || d.status === 2 ? "Online" : "Offline";
189
+
190
+ const result = {
191
+ device: {
192
+ name: d.name,
193
+ serial: d.serialNumber,
194
+ type: d.type,
195
+ status,
196
+ todayEnergy: d.eToday,
197
+ power: d.currentPower,
198
+ powerStr: d.powerStr,
199
+ currency: data.currency,
200
+ revenuePerKwh: data.revenuePerKwh,
201
+ todayRevenue: deviceRevenue,
202
+ },
203
+ hours: sortedHours.map((p) => ({
204
+ time: p.key,
205
+ energy: p.value,
206
+ unit: "kWh",
207
+ })),
208
+ };
209
+
210
+ out(result);
211
+
212
+ if (!isJson) {
213
+ console.log(`Device: ${result.device.name}`);
214
+ console.log(`Today: ${result.device.todayEnergy.toFixed(1)} kWh`);
215
+ console.log(`Power: ${result.device.powerStr}`);
216
+ console.log(`Status: ${result.device.status}`);
217
+ if (result.device.currency && result.device.revenuePerKwh) {
218
+ console.log(`Today: ${result.device.todayRevenue.toFixed(0)} ${result.device.currency}`);
219
+ }
220
+ console.log(`
221
+ Hours (${result.hours.length}):`);
222
+ for (const h of result.hours) {
223
+ console.log(` ${h.time.padStart(5)} ${h.energy.toFixed(1).padStart(8)} kWh`);
224
+ }
225
+ }
226
+ return;
227
+ }
228
+
229
+ const result = {
230
+ plant: {
231
+ id: config.plantId,
232
+ name: data.plantName,
233
+ todayEnergy: todayEnergy,
234
+ nominalPower: data.nominalPower,
235
+ capacityKw: data.nominalPower / 1000,
236
+ currency: data.currency,
237
+ revenuePerKwh: data.revenuePerKwh,
238
+ todayRevenue,
239
+ weather: data.weather,
240
+ temperature: data.temperature,
241
+ alarms: data.alarms,
242
+ devicesOnline: data.devicesOnline,
243
+ hasStorage: data.hasStorage,
244
+ },
245
+ devices: filteredDevices.map((d) => ({
246
+ name: d.name,
247
+ serial: d.serialNumber,
248
+ type: d.type,
249
+ status: d.status,
250
+ eToday: d.eToday,
251
+ currentPower: d.currentPower,
252
+ powerStr: d.powerStr,
253
+ eTodayStr: d.eTodayStr,
254
+ })),
255
+ hours: sortedHours.map((p) => ({
256
+ time: p.key,
257
+ energy: p.value,
258
+ unit: "kWh",
259
+ })),
260
+ };
261
+
262
+ out(result);
263
+
264
+ if (!isJson) {
265
+ console.log(`Plant: ${result.plant.name}`);
266
+ console.log(`Today: ${result.plant.todayEnergy.toFixed(1)} kWh`);
267
+ console.log(`Capacity: ${result.plant.capacityKw.toFixed(1)} kW`);
268
+ if (result.plant.currency) {
269
+ console.log(`Today: ${result.plant.todayRevenue.toFixed(0)} ${result.plant.currency}`);
270
+ }
271
+ if (result.plant.weather) {
272
+ console.log(`Weather: ${result.plant.weather}, ${result.plant.temperature}C`);
273
+ }
274
+ console.log(`Devices: ${result.plant.devicesOnline === 0 ? "Offline" : "Online"}`);
275
+ console.log(`
276
+ Devices (${result.devices.length}):`);
277
+ for (const d of result.devices) {
278
+ console.log(` ${d.name}: ${d.eToday.toFixed(1)} kWh (${d.powerStr})`);
279
+ }
280
+ console.log(`
281
+ Hours (${result.hours.length}):`);
282
+ for (const h of result.hours) {
283
+ console.log(` ${h.time.padStart(5)} ${h.energy.toFixed(1).padStart(8)} kWh`);
284
+ }
285
+ }
286
+ }
287
+
288
+ async function handleHistory() {
289
+ if (!hasConfig()) {
290
+ fail({ error: "Not logged in", details: "Run: gwat login -u <username> -p <password>" });
291
+ }
292
+
293
+ const day = args["--day"];
294
+ const month = args["--month"];
295
+ const year = args["--year"];
296
+ const total = args["--total"];
297
+
298
+ if (!day && !month && !year && !total) {
299
+ fail({ error: "Missing range", details: "Specify --day, --month, --year, or --total" });
300
+ }
301
+
302
+ let type: number;
303
+ let date: string;
304
+ let range: string;
305
+
306
+ if (day) {
307
+ type = 1;
308
+ date = day;
309
+ range = "day";
310
+ } else if (month) {
311
+ type = 2;
312
+ date = month;
313
+ range = "month";
314
+ } else if (total) {
315
+ type = 4;
316
+ date = "";
317
+ range = "total";
318
+ } else {
319
+ type = 3;
320
+ date = year!;
321
+ range = "year";
322
+ }
323
+
324
+ const config = readConfig();
325
+ const result = await getHistory(config, type, date);
326
+
327
+ const sorted = result.points.slice().sort((a, b) => a.key.localeCompare(b.key));
328
+
329
+ const payload = {
330
+ range,
331
+ date: date || undefined,
332
+ plantId: config.plantId,
333
+ plantName: result.plantData?.plantName || config.plantName,
334
+ totalEnergy: result.total,
335
+ data: sorted.map((p) => ({
336
+ label: p.key,
337
+ value: p.value,
338
+ unit: "kWh",
339
+ })),
340
+ };
341
+
342
+ out(payload);
343
+
344
+ if (!isJson) {
345
+ console.log(`Plant: ${payload.plantName}`);
346
+ console.log(`Range: ${range}${date ? " " + date : ""}`);
347
+ console.log(`Total: ${payload.totalEnergy.toFixed(1)} kWh`);
348
+ if (sorted.length === 0) {
349
+ console.log("No data available.");
350
+ return;
351
+ }
352
+ console.log();
353
+ for (const p of sorted) {
354
+ console.log(` ${p.key.padStart(8)} ${p.value.toFixed(1).padStart(8)} kWh`);
355
+ }
356
+ }
357
+ }
358
+
359
+ async function handleStatus() {
360
+ if (!hasConfig()) {
361
+ fail({ error: "Not logged in", details: "Run: gwat login -u <username> -p <password>" });
362
+ }
363
+
364
+ const config = readConfig();
365
+ const { plants, total } = await getPlantList(config);
366
+
367
+ const payload = {
368
+ plants: plants.map((p: any) => ({
369
+ id: p.plantId,
370
+ name: p.plantName,
371
+ todayEnergy: p.todayEnergy,
372
+ totalEnergy: p.totalEnergy,
373
+ currentPower: p.currentPower,
374
+ isHaveStorage: p.isHaveStorage,
375
+ })),
376
+ total: total ? {
377
+ todayEnergySum: total.todayEnergySum,
378
+ totalEnergySum: total.totalEnergySum,
379
+ currentPowerSum: total.currentPowerSum,
380
+ co2Sum: total.CO2Sum,
381
+ } : undefined,
382
+ };
383
+
384
+ out(payload);
385
+
386
+ if (!isJson) {
387
+ for (const p of payload.plants) {
388
+ console.log(`${p.name}`);
389
+ console.log(` Today: ${p.todayEnergy || "N/A"}`);
390
+ console.log(` Total: ${p.totalEnergy || "N/A"}`);
391
+ console.log(` Power: ${p.currentPower || "N/A"}`);
392
+ console.log(` ID: ${p.id}`);
393
+ }
394
+ if (payload.total) {
395
+ console.log(`\nCombined`);
396
+ console.log(` Today: ${payload.total.todayEnergySum || "N/A"}`);
397
+ console.log(` Total: ${payload.total.totalEnergySum || "N/A"}`);
398
+ console.log(` Power: ${payload.total.currentPowerSum || "N/A"}`);
399
+ console.log(` CO2: ${payload.total.co2Sum || "N/A"}`);
400
+ }
401
+ }
402
+ }
403
+
404
+ run().catch((err) => {
405
+ fail({ error: err.message });
406
+ });