-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
122 lines (105 loc) · 3.41 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const http = require('http');
const fetch = require('node-fetch');
require('dotenv').config();
const INT16_MAX = 0x7FFF;
const serverPath = '/solax-api';
const checkConfig = () => {
if (!process.env.SOLAX_URL || !process.env.SOLAX_PASSWORD) {
throw new Error('Params URL and PASSWORD are not set in .env config file.');
}
}
const fetchSolaxData = async () => {
const myHeaders = new fetch.Headers();
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');
const urlencoded = new URLSearchParams();
urlencoded.append('optType', 'ReadRealTimeData');
urlencoded.append('pwd', process.env.SOLAX_PASSWORD);
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: urlencoded,
redirect: 'follow',
};
return (await fetch(process.env.SOLAX_URL, requestOptions)).json();
};
const u16Packer = (values) => {
let acc = 0.0;
let stride = 1;
for (const value of values) {
acc += value * stride;
stride *= 2**16;
}
return acc;
};
const toSigned = (value) => {
if (value > INT16_MAX) {
value -= 2**16;
}
return value;
};
const mapSolaxResponse = (rawData) => {
const dataArr = rawData.Data;
// mapping taken from https://github.com/squishykid/solax/blob/master/solax/inverters/qvolt_hyb_g3_3p.py
return {
pvPower: dataArr[14] + dataArr[15],
acPower: toSigned(dataArr[9]),
string1Voltage: dataArr[10] / 10,
string2Voltage: dataArr[11] / 10,
string1Current: dataArr[12] / 10,
string2Current: dataArr[13] / 10,
string1Power: dataArr[14],
string2Power: dataArr[15],
batterySoc: dataArr[103],
gridPower: toSigned(dataArr[34]),
batteryVoltage: dataArr[39] / 100,
batteryCurrent: toSigned(dataArr[40]) / 100,
batteryPower: toSigned(dataArr[41]),
batteryRemainingEnergy: dataArr[106] / 10,
loadPower: toSigned(dataArr[47]),
todayFeedInEnergy: dataArr[90] / 100,
totalFeedInEnergy: u16Packer([dataArr[86], dataArr[87]]) / 100,
todayBatteryDischargeEnergy: dataArr[78] / 10,
todayBatteryChargeEnergy: dataArr[79] / 10,
totalBatteryDischargeEnergy: u16Packer([dataArr[74], dataArr[75]]) / 10,
totalBatteryChargeEnergy: u16Packer([dataArr[76], dataArr[77]]) / 10,
todayConsumption: dataArr[92] / 100,
totalConsumption: u16Packer([dataArr[88], dataArr[89]]) / 100,
batteryTemperature: dataArr[105],
totalYield: u16Packer([dataArr[68], dataArr[69]]) / 10,
//totalPvEnergy: u16Packer([dataArr[80], dataArr[81]]) / 10,
//todayEnergy: dataArr[82] / 10,
todayYield: dataArr[70] / 10,
outputCurrentPhase1: toSigned(dataArr[3]) / 10,
outputCurrentPhase2: toSigned(dataArr[4]) / 10,
outputCurrentPhase3: toSigned(dataArr[5]) / 10,
powerPhase1: toSigned(dataArr[6]),
powerPhase2: toSigned(dataArr[7]),
powerPhase3: toSigned(dataArr[8]),
inverterTemperature: dataArr[54],
};
};
const clearServerPath = serverPath
.replace(/^\//, '')
.replace(/\/$/, '')
.replace('/', '\/');
const pathRegex = new RegExp(`^\/${clearServerPath}\/?(\\?.*)?$`);
const requestListener = (req, res) => {
if (!req.url || !req.url.match(pathRegex)) {
res.writeHead(404);
res.end();
return;
}
fetchSolaxData()
.then(data => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(mapSolaxResponse(data)));
})
.catch((err) => {
console.error(err);
res.writeHead(500);
res.end(`Error: ${err.message}`);
});
}
checkConfig();
const server = http.createServer(requestListener);
server.listen(process.env.SERVER_PORT || 8080);