Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <html>
- <header>
- </header>
- <body>
- <?php
- // SEMS+ (new portal) -> PVOutput — rewritten for the new signed API
- // Original by Roeland Kindt; PVOutput part from Goodwe2PVoutput (Antonboonstra).
- //
- // WHAT CHANGED vs the old script:
- // old: euapi.sems.com.cn /api/v1/... with an iOS-style Token header (DEAD)
- // new: <region>-semsplus.goodwe.com /web/sems/sems-{user,plant}/api/...
- // every call needs a token:{json} + x-signature header pair.
- //
- // signature (from the portal bundle):
- // r = Date.now() (ms) a = token.uid i = token.token
- // x-signature = base64( sha256hex("$r@$a@$i") . "@" . $r )
- //
- // login body: {account, pwd: base64(md5(password)), agreement:1, isLocal:false, isChinese:false}
- // login path is called on the SEMS+ page origin (no region gateway prefix);
- // data calls use the `api` base returned by login.
- // ---------------- config ----------------
- $keyPVO = "";
- $sidPVO = "";
- // SEMS+ regional host for YOUR account (eu / us / au / cn / hk)
- $SEMS_HOST = "https://eu-semsplus.goodwe.com";
- $powerstation_id = ""; // ("" = auto-detect)
- $email = "[email protected]";
- $password = "xxxx";
- $test = false; // false = actually submit to PVOutput
- $debug = false; // true = var_dump responses
- // ----------------------------------------
- echo "<pre>\n";
- // ---- tiny self-check: known signature vector from a captured session ----
- function sems_signature($tokenJson) {
- $t = json_decode($tokenJson, true) ?: [];
- $uid = $t['uid'] ?? '';
- $tok = $t['token'] ?? '';
- $r = sprintf('%.0f', microtime(true) * 1000);
- $s = hash('sha256', "$r@$uid@$tok");
- return base64_encode("$s@$r");
- }
- // ---- day/night guard ----
- $day = date_sunrise(time(), SUNFUNCS_RET_TIMESTAMP, 50.94, 5.43);
- $night = date_sunset (time(), SUNFUNCS_RET_TIMESTAMP, 50.94, 5.43);
- if (time() < ($day - 600) || time() > ($night + 600)) {
- echo "It is night (" . date("H:i", $day) . ".." . date("H:i", $night) . ") - skipping.\n";
- if (!$test) exit();
- }
- // ---- one curl helper ----
- function sems_call($method, $url, $tokenJson, $body = null) {
- $uuid = $GLOBALS['UUID'];
- $h = [
- "accept: application/json, text/plain, */*",
- "access-control-allow-origin: *",
- "currentlang: en",
- "neutral: 0",
- "origin: " . $GLOBALS['SEMS_HOST'],
- "referer: " . $GLOBALS['SEMS_HOST'] . "/",
- "os: Windows 10.0 desktop",
- "brand: Chrome 153.0.0.0 Google Inc. en-US Europe/Brussels",
- "token: " . $tokenJson,
- "uuid: " . $uuid,
- "x-signature: " . sems_signature($tokenJson),
- "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36",
- ];
- if ($body !== null) $h[] = "content-type: application/json";
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_HTTPHEADER, $h);
- curl_setopt($ch, CURLOPT_TIMEOUT, 60);
- if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); }
- $out = curl_exec($ch);
- if ($out === false) { echo "curl error: " . curl_error($ch) . "\n"; curl_close($ch); return null; }
- curl_close($ch);
- return json_decode($out, true);
- }
- // ---- login ----
- $UUID = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
- mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0x0fff)|0x4000,
- mt_rand(0,0x3fff)|0x8000, mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff));
- $emptyToken = '{"uid":"","timestamp":0,"token":"","client":"semsPlusWeb","version":"","language":"en"}';
- $loginBody = json_encode([
- "account" => $email,
- "pwd" => base64_encode(md5($password)), // <-- the new password encoding
- "agreement" => 1,
- "isLocal" => false, // false for non-CN regions
- "isChinese" => false,
- ]);
- $lr = sems_call('POST', $SEMS_HOST . '/web/sems/sems-user/api/v1/auth/cross-login', $emptyToken, $loginBody);
- if ($debug) var_dump($lr);
- if (!is_array($lr) || ($lr['code'] ?? '') !== '00000') {
- echo "<br/>SEMS+ login failed. Enable debug mode.<br/>";
- exit();
- }
- $token = $lr['data'];
- $tokenJson = json_encode($token);
- $API = rtrim($token['api'] ?: 'https://eu-gateway.semsportal.com/web/sems', '/');
- echo "<br/>logged in as " . $token['uid'] . " (region " . ($token['region'] ?? '?') . ")<br/>";
- // ---- resolve station id ----
- if (trim($powerstation_id) === "") {
- $r = sems_call('POST', "$API/sems-plant/api/stations/simple-query", $tokenJson,
- '{"current":1,"size":3,"shared":false,"type":0}');
- if ($debug) var_dump($r);
- $powerstation_id = $r['data']['dataList'][0]['id'] ?? "";
- echo "retrieved powerstation_id = $powerstation_id<br/>";
- } else {
- echo "Powerstation id provided: $powerstation_id<br/>";
- }
- if ($powerstation_id === "") { echo "No powerstation id.<br/>"; exit(); }
- // ---- resolve inverter SN ----
- $r = sems_call('GET',
- "$API/sems-plant/api/web/device/station/getDevicesByType?stationId=$powerstation_id&deviceType=INVERTER",
- $tokenJson);
- $sn = $r['data'][0]['sn'] ?? "";
- if ($debug) var_dump($r);
- echo "inverter sn = $sn<br/>";
- // ---- flatten a factor-group list ([{factors:[{code,data}]}]) into code=>data ----
- function factor_map($groups) {
- $f = [];
- foreach (($groups ?? []) as $g)
- foreach (($g['factors'] ?? []) as $fac)
- if (isset($fac['data'])) $f[$fac['code']] = $fac['data'];
- return $f;
- }
- // ---- live power (kW) + energy today (kWh) in ONE call (telecounting) ----
- // realtime_data.pAc, day.proPvStatsToday (+ week/month/year/total if wanted)
- $tc = sems_call('GET',
- "$API/sems-plant/api/equipments/$sn/telecounting?deviceType=INVERTER&pwId=$powerstation_id",
- $tokenJson);
- if ($debug) var_dump($tc);
- $tcf = factor_map($tc['data']);
- $pac_kw = (float)($tcf['pAc'] ?? 0);
- $kwh_today = (float)($tcf['proPvStatsToday'] ?? 0);
- // ---- temperature + PV voltages from the inverter telemetry ----
- $tel = sems_call('GET',
- "$API/sems-plant/api/equipments/$sn/telemetry?deviceType=INVERTER&pwId=$powerstation_id",
- $tokenJson);
- if ($debug) var_dump($tel);
- $f = factor_map($tel['data']);
- $power = intval(round($pac_kw * 1000)); // W
- $energy_cum = intval(round($kwh_today * 1000)); // Wh today
- $temperature = round((float)($f['Temperature'] ?? 0), 1); // °C
- $vdc = round((float)($f['MPPT-1:Vpv'] ?? 0), 1); // V (MPP1)
- $vpv2 = round((float)($f['MPPT-2:Vpv'] ?? 0), 1);
- echo "<h1>Energy = $energy_cum Wh - Power = $power W - DC voltage 1 = $vdc, DC voltage 2 = $vpv2 - Temperature = $temperature degr.C</h1>";
- // ---- submit to PVOutput ----
- $url = 'http://pvoutput.org/service/r2/addstatus.jsp?key=' . $keyPVO . '&sid=' . $sidPVO
- . '&d=' . date("Ymd") . '&t=' . date("H:i")
- . '&v1=' . $energy_cum . '&v2=' . $power . '&v5=' . $temperature . '&v6=' . $vdc . '&c1=0';
- if ($test) {
- echo "URL = $url \n";
- } else {
- $responds = file_get_contents($url);
- echo "Responds PVOutput was: $responds \n";
- if ($responds == "OK 200: Added Status") {
- echo "The values were succesfully retrieved and submitted to PVOutput \n";
- } else {
- echo "Issue submitting to PVOutput, let's try again...<br>\n";
- sleep(4);
- $responds = file_get_contents($url);
- echo ($responds == "OK 200: Added Status")
- ? "The values were succesfully retrieved and submitted to PVOutput \n"
- : "PVOutput submit failed for a second time. Responds: $responds \n";
- }
- }
- echo '</pre>';
- ?>
- </body>
- </html>
Add Comment
Please, Sign In to add comment