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
| <?php
// Code written in a night by Roeland Kindt for SEMS portal retrieval
// PVoutput portion copied from older Goodwe2PVoutput script, thanks Antonboonstra!
//
// Updated for the new (signed) SEMS+ API:
// - host: eu-gateway.semsportal.com
// - login: /web/sems/sems-user/api/v1/auth/cross-login pwd = base64(md5(password))
// - the token: header must ALWAYS be sent (empty JSON on login), else code 100004
// - device data: /web/sems/sems-plant/api/web/device/centralized/page
// - v1 (daily energy) uses proToday, NOT proTotal (proTotal is lifetime kWh)
define('BASE', 'https://eu-gateway.semsportal.com');
// === CONFIGURATION ===
$sems_email = '';
$sems_password = '';
$pv_key = ""; // Retrieve API key from PVOutput, create at http://pvoutput.org/account.jsp under API settings
$pv_sid = ""; // Fill in your System ID, retrieve from http://pvoutput.org/account.jsp (at the bottom)
$inverterNo = 0; // In case of multiple Goodwe inverters: 0 = first, 1 = second, etc.
$test = true; // if true prints output but does _not_ submit to PVOutput, set to false when ready
$debug = true; // prints extra debug info when true
$SSLExpired = true; // work around in case ssl certificate is invalid, disable once it is fixed
// === HELPERS ===
function api($url, $method = 'GET', $body = null, $token = null) {
global $debug, $SSLExpired;
$ch = curl_init();
$headers = ['Content-Type: application/json'];
// ALWAYS send a token header; on login use the empty-token JSON
$headers[] = 'token: ' . ($token ?: '{"uid":"","timestamp":0,"token":"","client":"semsPlusWeb","version":"","language":"en"}');
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => $SSLExpired ? 0 : 1,
CURLOPT_SSL_VERIFYHOST => $SSLExpired ? 0 : 2,
CURLOPT_HTTPHEADER => $headers
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, is_string($body) ? $body : json_encode($body));
}
$res = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($debug) echo ">>> $method $url\n HTTP $http: $res\n ERR: $err\n\n";
return json_decode($res, true);
}
// 1. Login
$r = api(BASE . '/web/sems/sems-user/api/v1/auth/cross-login', 'POST', [
'account' => $sems_email,
'pwd' => base64_encode(md5($sems_password)),
'isChinese' => false,
'agreement' => 1,
'isLocal' => true
]);
if (!$r || $r['code'] !== '00000') exit("ERROR:1 Login failed\n");
$d = $r['data'];
$token = json_encode([
'uid' => $d['uid'],
'timestamp' => $d['timestamp'],
'token' => $d['token'],
'client' => $d['client'],
'version' => $d['version'],
'language' => $d['language'],
'region' => $d['region']
]);
// 2. Device list -> pAc, proToday, SN, cavityTemperature
$r = api(BASE . "/web/sems/sems-plant/api/web/device/centralized/page", 'POST', '{"deviceTypeList":["INVERTER"],"current":1,"size":5}', $token);
if (!$r || $r['code'] !== '00000') exit("ERROR:2 Devices failed: " . ($r['description'] ?? '') . "\n");
$dev = $r['data']['dataList'][0]['children'][$inverterNo];
$pAc = $dev['pAc'];
$proToday = $dev['proToday']; // daily energy (kWh) -> v1
$sn = $dev['sn'];
$cavityTemperature = $dev['cavityTemperature'];
// 3. Map to PVOutput units
$power_w = intval(floatval($pAc) * 1000); // W
$energy_wh = round(floatval($proToday) * 1000); // Wh generated today
$temperature = round(floatval($cavityTemperature), 1); // degC
// 4. Output (cron-friendly: one line with key values)
if ($debug) echo "SN=$sn Energy={$energy_wh}Wh Power={$power_w}W Temp={$temperature}degC\n";
if ($test) { echo "TEST: would submit d=" . date('Ymd') . " t=" . date('H:i') . " v1=$energy_wh v2=$power_w v5=$temperature c1=1\n"; exit(0); }
// 5. Submit to PVOutput
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://pvoutput.org/service/r2/addstatus.jsp',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_SSL_VERIFYPEER => $SSLExpired ? 0 : 1,
CURLOPT_POSTFIELDS => http_build_query([
'd' => date('Ymd'),
't' => date('H:i'),
'v1' => $energy_wh,
'v2' => $power_w,
'v5' => $temperature,
'c1' => 1
]),
CURLOPT_HTTPHEADER => [
'X-Pvoutput-Apikey: ' . $pv_key,
'X-Pvoutput-SystemId: ' . $pv_sid
]
]);
$pv = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code === 200) {
echo "OK d=" . date('Ymd') . " t=" . date('H:i') . " v1=$energy_wh v2=$power_w v5=$temperature\n";
exit(0);
}
echo "ERROR:4 PVOutput HTTP $code $pv\n";
exit(1); |