@yyberi/fronius-comm
Version:
Library for communicating with Fronius solar inverters
213 lines • 9.09 kB
JavaScript
import * as http from "http";
import Logger, { LogLevel } from "@yyberi/logger";
import moment from "moment-timezone";
const rootLog = Logger('fronius-comm');
const log = rootLog.child({ name: 'fronius-comm.ts' });
export class FroniusClient {
ip;
scope;
deviceId;
deviceClass;
logLevel;
constructor(ip, scope, deviceId, deviceClass, logLevel = LogLevel.INFO) {
this.ip = ip;
this.scope = scope;
this.deviceId = deviceId;
this.deviceClass = deviceClass;
this.logLevel = logLevel;
log.setLogLevel(logLevel);
}
/**
* Get realtime data from the inverter.
* @returns {Promise<SimpleInverterRealtimeData>} - Returns an object with totalEnergy, yearEnergy, dayEnergy and actPower.
*/
async getRealtimeData() {
return new Promise((resolve, reject) => {
const queryObj = {
Scope: this.scope,
DeviceId: this.deviceId,
DataCollection: `CumulationInverterData`
};
const path = `/solar_api/v1/GetInverterRealtimeData.cgi`;
const searchParams = new URLSearchParams(queryObj);
const options = {
hostname: this.ip,
port: 80,
method: `GET`,
path: `${path}?${searchParams.toString()}`,
timeout: 5000
};
http.get(options, resp => {
let data = "";
resp.on("data", chunk => { data += chunk; });
resp.on("end", () => {
try {
const json = JSON.parse(data);
const body = json.Body;
if (!body || !body.Data)
throw new Error("Missing Body.Data");
const pd = body.Data;
const extract = (paramName) => {
const p = pd[paramName];
if (!p)
throw new Error(`Param ${paramName} missing`);
if (typeof p.Value === 'number')
return p.Value;
if (p.Values) {
const firstKey = Object.keys(p.Values)[0];
const val = p.Values[firstKey];
if (typeof val === 'number')
return val;
}
throw new Error(`Param ${paramName} is not a valid number`);
};
const result = {};
try {
result.totalEnergy = extract("TOTAL_ENERGY");
}
catch {
/* leave result.totalEnergy undefined */
}
try {
result.yearEnergy = extract("YEAR_ENERGY");
}
catch {
/* leave result.yearEnergy undefined */
}
try {
result.dayEnergy = extract("DAY_ENERGY");
}
catch {
/* leave result.dayEnergy undefined */
}
try {
result.actPower = extract("PAC");
}
catch {
// leave result.actPower undefined;
}
resolve(result);
}
catch (err) {
log.error(`Error parsing inverter data: ${err}`);
reject(new Error(`Failed to parse inverter data: ${err}`));
}
finally {
;
}
});
})
.on("timeout", () => reject(new Error('Timeout while connecting to inverter')))
.on("error", err => reject(new Error('Connection error: ' + err.message)));
});
}
async getProductionData(dateOrStart, endDate) {
log.debug(`getProductionData(${dateOrStart}, ${endDate})`);
const start = moment(dateOrStart).utc().toISOString();
const end = endDate ? moment(endDate).utc().toISOString() : moment(dateOrStart).add(1, 'days').utc().toISOString();
return new Promise((resolve, reject) => {
const queryObj = {
Scope: this.scope,
DeviceId: this.deviceId,
DeviceClass: this.deviceClass,
StartDate: start,
EndDate: end,
Channel: 'EnergyReal_WAC_Sum_Produced'
};
const path = '/solar_api/v1/GetArchiveData.cgi';
const searchParams = new URLSearchParams(queryObj);
const queryString = decodeURIComponent(path + '?' + searchParams.toString());
const options = {
hostname: this.ip,
port: 80,
method: 'GET',
path: queryString,
headers: {
Host: this.ip
},
timeout: 30000
};
http.get(options, (resp) => {
let data = "";
resp.on("data", (chunk) => {
data += chunk;
});
resp.on("end", () => {
try {
const json = JSON.parse(data);
const invData = json?.Body?.Data?.["inverter/1"];
if (invData?.Start && invData?.Data?.EnergyReal_WAC_Sum_Produced?.Values) {
resolve({
start: invData.Start,
values: invData.Data.EnergyReal_WAC_Sum_Produced.Values
});
}
else {
resolve({ start, values: {} });
}
}
catch (e) {
reject(new Error("Invalid JSON response"));
}
});
}).on("timeout", () => {
reject(new Error('Timeout'));
}).on("error", (err) => {
reject(err);
});
});
}
async parseEnergyReal_WAC_Sum_Produced_5min(dateOrStart, endDate) {
log.debug(`parseEnergyReal_WAC_Sum_Produced_5min(${dateOrStart}, ${endDate})`);
const { start, values } = endDate ? await this.getProductionData(dateOrStart, endDate) : await this.getProductionData(dateOrStart);
const startTime = new Date(start);
const productionData = [];
const sortedKeys = Object.keys(values)
.map(k => parseInt(k))
.sort((a, b) => a - b);
for (const sec of sortedKeys) {
if (sec === 0)
continue;
const value = values[sec];
const intervalStart = new Date(startTime);
intervalStart.setSeconds(sec);
productionData.push({
time: intervalStart.toISOString(),
value: value
});
}
return productionData;
}
async parseEnergyReal_WAC_Sum_Produced_15min(dateOrStart, endDate) {
log.debug(`parseEnergyReal_WAC_Sum_Produced_15min(${dateOrStart}, ${endDate})`);
const data5min = endDate ? await this.parseEnergyReal_WAC_Sum_Produced_5min(dateOrStart, endDate) : await this.parseEnergyReal_WAC_Sum_Produced_5min(dateOrStart);
const data15min = [];
for (let i = 0; i < data5min.length; i += 3) {
const group = data5min.slice(i, i + 3);
if (group.length === 3) {
const total = group.reduce((sum, item) => sum + item.value, 0);
data15min.push({
time: group[2].time,
value: total
});
}
}
return { data5min, data15min };
}
async parseEnergyReal_WAC_Sum_Produced_All(dateOrStart, endDate) {
log.debug(`parseEnergyReal_WAC_Sum_Produced_All(${dateOrStart}, ${endDate})`);
const { data5min, data15min } = endDate ? await this.parseEnergyReal_WAC_Sum_Produced_15min(dateOrStart, endDate) : await this.parseEnergyReal_WAC_Sum_Produced_15min(dateOrStart);
const data60min = [];
for (let i = 0; i < data5min.length; i += 12) {
const group = data5min.slice(i, i + 12);
if (group.length === 12) {
data60min.push({
time: group[11].time,
value: group.reduce((sum, item) => sum + item.value, 0)
});
}
}
return { data5min, data15min, data60min };
}
}
//# sourceMappingURL=fronius-comm.js.map