mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[semsportal] initial contribution (#10100)
Signed-off-by: Iwan Bron <bron@olisa.eu>
This commit is contained in:
parent
4b5d64340f
commit
66a540289d
@ -249,6 +249,7 @@
|
||||
/bundles/org.openhab.binding.sagercaster/ @clinique
|
||||
/bundles/org.openhab.binding.samsungtv/ @paulianttila
|
||||
/bundles/org.openhab.binding.satel/ @druciak
|
||||
/bundles/org.openhab.binding.semsportal/ @itb3
|
||||
/bundles/org.openhab.binding.senechome/ @vctender @KorbinianP
|
||||
/bundles/org.openhab.binding.seneye/ @nikotanghe
|
||||
/bundles/org.openhab.binding.sensebox/ @hakan42
|
||||
|
@ -1221,6 +1221,11 @@
|
||||
<artifactId>org.openhab.binding.satel</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.semsportal</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.senechome</artifactId>
|
||||
|
13
bundles/org.openhab.binding.semsportal/NOTICE
Normal file
13
bundles/org.openhab.binding.semsportal/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
60
bundles/org.openhab.binding.semsportal/README.md
Normal file
60
bundles/org.openhab.binding.semsportal/README.md
Normal file
@ -0,0 +1,60 @@
|
||||
# SEMSPortal Binding
|
||||
|
||||
This binding can help you include statistics of your SEMS / GoodWe solar panel installation into openHAB.
|
||||
It is a read-only connection that maps collected parameters to openHAB channels.
|
||||
It provides current, day, month and total yields, as well as some income statistics if you have configured these in the SEMS portal.
|
||||
It requires a power station that is connected through the internet to the SEMS portal.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding provides two Thing types: a bridge to the SEMS Portal, and the Power Stations which are found at the Portal.
|
||||
The Portal (semsportal:portal) represents your account in the SEMS portal.
|
||||
The Power Station (semsportal:station) is an installation of a Power Station or inverter that reports to the SEMS portal and is available to your account.
|
||||
|
||||
## Discovery
|
||||
|
||||
Once you have configured a Portal Bridge, the binding will discover all Power Stations that are available to this account.
|
||||
You can trigger discovery in the add new Thing section of openHAB.
|
||||
Select the SEMS binding and press the Scan button.
|
||||
The discovered Power Stations will appear as new Things.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The configuration of the Portal Thing (Bridge) is pretty straight forward.
|
||||
You need to have your power station set up in the SEMS portal, and you need to have an account that is allowed to view the power station data.
|
||||
You should log in at least once in the portal with this account to activate it.
|
||||
The Portal needs the username and password to connect and retrieve the data.
|
||||
You can configure the update frequency between 1 and 60 minutes.
|
||||
The default is 5 minutes.
|
||||
|
||||
Power Stations have no settings and will be auto discovered when you add a Portal Bridge.
|
||||
|
||||
## Channels
|
||||
|
||||
The Portal(Bridge) has no channels.
|
||||
The Power Station Thing has the following channels:
|
||||
|
||||
| channel | type | description |
|
||||
| ------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| lastUpdate | DateTime | Last time the powerStation sent information to the portal |
|
||||
| currentOutput | Number:Power | The current output of the powerStation in Watt |
|
||||
| todayTotal | Number:Energy | Todays total generation of the station in kWh |
|
||||
| monthTotal | Number:Energy | This month's total generation of the station in kWh |
|
||||
| overallTotal | Number:Energy | The total generation of the station since installation, in kWh |
|
||||
| todayIncome | Number | Todays income as reported by the portal, if you have configured the power rates of your energy provider |
|
||||
| totalIncome | Number | The total income as reported by the portal, if you have configured the power rates of your energy provider |
|
||||
|
||||
## Parameters
|
||||
|
||||
The PowerStation Thing has no parameters.
|
||||
Only the Bridge has the following configuration parameters:
|
||||
|
||||
| Parameter | Required? | Description |
|
||||
| ----------- |:---------:| ---------------------------------------------------------------------------------------------------------- |
|
||||
| username | X | Account name (email address) at the SEMS portal. Account must have been used at least once to log in. |
|
||||
| password | X | Password of the SEMS portal |
|
||||
| update | | Number of minutes between two updates. Between 1 and 60 minutes, defaults to 5 minutes |
|
||||
|
||||
## Credits
|
||||
|
||||
This binding has been created using the information provided by RogerG007 in this forum topic: https://community.openhab.org/t/connecting-goodwe-solar-panel-inverter-to-openhab/85480
|
17
bundles/org.openhab.binding.semsportal/pom.xml
Normal file
17
bundles/org.openhab.binding.semsportal/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.semsportal</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: SEMSPortal Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.semsportal-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-semsportal" description="SEMSPortal Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.semsportal/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception indicating there was a problem communicating with the portal. It can indicate either no response at all, or
|
||||
* a response that was not expected.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationException extends Exception {
|
||||
private static final long serialVersionUID = 4175625868879971138L;
|
||||
|
||||
public CommunicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception indicating that the configuration of the portal was wrong, like an unknown account or invalid password.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationException extends Exception {
|
||||
private static final long serialVersionUID = -803416460838670618L;
|
||||
|
||||
public ConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.binding.semsportal.internal.dto.BaseResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.LoginRequest;
|
||||
import org.openhab.binding.semsportal.internal.dto.LoginResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.SEMSToken;
|
||||
import org.openhab.binding.semsportal.internal.dto.Station;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationListRequest;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationListResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationStatus;
|
||||
import org.openhab.binding.semsportal.internal.dto.StatusRequest;
|
||||
import org.openhab.binding.semsportal.internal.dto.StatusResponse;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link PortalHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PortalHandler extends BaseBridgeHandler {
|
||||
private Logger logger = LoggerFactory.getLogger(PortalHandler.class);
|
||||
// the settings that are needed when you do not have avalid session token yet
|
||||
private static final SEMSToken SESSIONLESS_TOKEN = new SEMSToken("v2.1.0", "ios", "en");
|
||||
// the url of the SEMS portal API
|
||||
private static final String BASE_URL = "https://www.semsportal.com/";
|
||||
// url for the login request, to get a valid session token
|
||||
private static final String LOGIN_URL = BASE_URL + "api/v2/Common/CrossLogin";
|
||||
// url to get the status of a specific power station
|
||||
private static final String STATUS_URL = BASE_URL + "api/v2/PowerStation/GetMonitorDetailByPowerstationId";
|
||||
private static final String LIST_URL = BASE_URL + "api/PowerStationMonitor/QueryPowerStationMonitorForApp";
|
||||
// the token holds the credential information for the portal
|
||||
private static final String HTTP_HEADER_TOKEN = "Token";
|
||||
|
||||
// used to parse json from / to the SEMS portal API
|
||||
private final Gson gson;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
// configuration as provided by the openhab framework: initialize with defaults to prevent compiler check errors
|
||||
private SEMSPortalConfiguration config = new SEMSPortalConfiguration();
|
||||
private boolean loggedIn;
|
||||
private SEMSToken sessionToken = SESSIONLESS_TOKEN;// gets the default, it is needed for the login
|
||||
private @Nullable StationStatus currentStatus;
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public PortalHandler(Bridge bridge, HttpClientFactory httpClientFactory) {
|
||||
super(bridge);
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
gson = new GsonBuilder().setDateFormat(SEMSPortalBindingConstants.DATE_FORMAT).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("No supported commands. Ignoring command {} for channel {}", command, channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(SEMSPortalConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
login();
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Error when loggin in. Check your username and password");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> localPollingJob = pollingJob;
|
||||
if (localPollingJob != null) {
|
||||
localPollingJob.cancel(true);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void login() {
|
||||
loggedIn = false;
|
||||
String payload = gson.toJson(new LoginRequest(config.username, config.password));
|
||||
String response = sendPost(LOGIN_URL, payload);
|
||||
if (response == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Invalid response from SEMS portal");
|
||||
return;
|
||||
}
|
||||
LoginResponse loginResponse = gson.fromJson(response, LoginResponse.class);
|
||||
if (loginResponse == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Check username / password");
|
||||
return;
|
||||
}
|
||||
if (loginResponse.isOk()) {
|
||||
logger.debug("Successfuly logged in to SEMS portal");
|
||||
if (loginResponse.getToken() != null) {
|
||||
sessionToken = loginResponse.getToken();
|
||||
}
|
||||
loggedIn = true;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.CONFIGURATION_ERROR, "Check username / password");
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String sendPost(String url, String payload) {
|
||||
try {
|
||||
Request request = httpClient.POST(url).header(HttpHeader.CONTENT_TYPE, MediaType.APPLICATION_JSON)
|
||||
.header(HTTP_HEADER_TOKEN, gson.toJson(sessionToken))
|
||||
.content(new StringContentProvider(payload, StandardCharsets.UTF_8.name()),
|
||||
MediaType.APPLICATION_JSON);
|
||||
request.getHeaders().remove(HttpHeader.ACCEPT_ENCODING);
|
||||
ContentResponse response = request.send();
|
||||
logger.trace("received response: {}", response.getContentAsString());
|
||||
return response.getContentAsString();
|
||||
} catch (Exception e) {
|
||||
logger.debug("{} when posting to url {}", e.getClass().getSimpleName(), url, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
|
||||
public @Nullable StationStatus getStationStatus(String stationUUID)
|
||||
throws CommunicationException, ConfigurationException {
|
||||
if (!loggedIn) {
|
||||
logger.debug("Not logged in. Not updating.");
|
||||
return null;
|
||||
}
|
||||
String response = sendPost(STATUS_URL, gson.toJson(new StatusRequest(stationUUID)));
|
||||
if (response == null) {
|
||||
throw new CommunicationException("No response received from portal");
|
||||
}
|
||||
BaseResponse semsResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if (semsResponse == null) {
|
||||
throw new CommunicationException("Portal reponse not understood");
|
||||
}
|
||||
if (semsResponse.isOk()) {
|
||||
StatusResponse statusResponse = gson.fromJson(response, StatusResponse.class);
|
||||
if (statusResponse == null) {
|
||||
throw new CommunicationException("Portal reponse not understood");
|
||||
}
|
||||
currentStatus = statusResponse.getStatus();
|
||||
updateStatus(ThingStatus.ONLINE); // we got a valid response, register as online
|
||||
return currentStatus;
|
||||
} else if (semsResponse.isSessionInvalid()) {
|
||||
logger.debug("Session is invalidated. Attempting new login.");
|
||||
login();
|
||||
return getStationStatus(stationUUID);
|
||||
} else if (semsResponse.isError()) {
|
||||
throw new ConfigurationException(
|
||||
"ERROR status code received from SEMS portal. Please check your station ID");
|
||||
} else {
|
||||
throw new CommunicationException(String.format("Unknown status code received from SEMS portal: %s - %s",
|
||||
semsResponse.getCode(), semsResponse.getMsg()));
|
||||
}
|
||||
}
|
||||
|
||||
public long getUpdateInterval() {
|
||||
return config.update;
|
||||
}
|
||||
|
||||
public List<Station> getAllStations() {
|
||||
String payload = gson.toJson(new StationListRequest());
|
||||
String response = sendPost(LIST_URL, payload);
|
||||
if (response == null) {
|
||||
logger.debug("Received empty list stations response from SEMS portal");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
StationListResponse listResponse = gson.fromJson(response, StationListResponse.class);
|
||||
if (listResponse == null) {
|
||||
logger.debug("Unable to read list stations response from SEMS portal");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (listResponse.isOk()) {
|
||||
logger.debug("Received list of {} stations from SEMS portal", listResponse.getStations().size());
|
||||
loggedIn = true;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
return listResponse.getStations();
|
||||
} else {
|
||||
logger.debug("Received error response with code {} and message {} from SEMS portal", listResponse.getCode(),
|
||||
listResponse.getMsg());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SEMSPortalBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SEMSPortalBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "semsportal";
|
||||
static final String DATE_FORMAT = "MM.dd.yyyy HH:mm:ss";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_PORTAL = new ThingTypeUID(BINDING_ID, "portal");
|
||||
public static final ThingTypeUID THING_TYPE_STATION = new ThingTypeUID(BINDING_ID, "station");
|
||||
|
||||
// the default update interval for statusses at the portal
|
||||
public static final int DEFAULT_UPDATE_INTERVAL_MINUTES = 5;
|
||||
|
||||
// station properties
|
||||
public static final String STATION_UUID = "stationUUID";
|
||||
public static final String STATION_NAME = "stationName";
|
||||
public static final String STATION_CAPACITY = "stationCapacity";
|
||||
public static final String STATION_REPRESENTATION_PROPERTY = STATION_UUID;
|
||||
public static final String STATION_LABEL_FORMAT = "Power Station %s";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_CURRENT_OUTPUT = "currentOutput";
|
||||
public static final String CHANNEL_LASTUPDATE = "lastUpdate";
|
||||
public static final String CHANNEL_TODAY_TOTAL = "todayTotal";
|
||||
public static final String CHANNEL_MONTH_TOTAL = "monthTotal";
|
||||
public static final String CHANNEL_OVERALL_TOTAL = "overallTotal";
|
||||
public static final String CHANNEL_TODAY_INCOME = "todayIncome";
|
||||
public static final String CHANNEL_TOTAL_INCOME = "totalIncome";
|
||||
|
||||
protected static final List<String> ALL_CHANNELS = Arrays.asList(CHANNEL_LASTUPDATE, CHANNEL_CURRENT_OUTPUT,
|
||||
CHANNEL_TODAY_TOTAL, CHANNEL_MONTH_TOTAL, CHANNEL_OVERALL_TOTAL, CHANNEL_TODAY_INCOME,
|
||||
CHANNEL_TOTAL_INCOME);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SEMSPortalConfiguration} class contains fields mapping thing
|
||||
* configuration parameters.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SEMSPortalConfiguration {
|
||||
|
||||
/**
|
||||
* We need username and password of the SEMS portal to access the solar plant
|
||||
* data.
|
||||
*
|
||||
* In the first version, you need to provide the station ID as well. Later we
|
||||
* can discover it from the SEMS portal.
|
||||
*/
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
public int update = SEMSPortalBindingConstants.DEFAULT_UPDATE_INTERVAL_MINUTES;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import static org.openhab.binding.semsportal.internal.SEMSPortalBindingConstants.*;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.semsportal.internal.discovery.StationDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SEMSPortalHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.semsportal", service = ThingHandlerFactory.class)
|
||||
public class SEMSPortalHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_STATION, THING_TYPE_PORTAL);
|
||||
private HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public SEMSPortalHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_PORTAL.equals(thingTypeUID)) {
|
||||
PortalHandler handler = new PortalHandler((Bridge) thing, httpClientFactory);
|
||||
Hashtable<String, Object> dictionary = new Hashtable<>();
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), new StationDiscoveryService(handler),
|
||||
dictionary);
|
||||
return handler;
|
||||
}
|
||||
if (THING_TYPE_STATION.equals(thingTypeUID)) {
|
||||
return new StationHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationStatus;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* Helper class to convert the POJOs of the SEMS portal response classes into openHAB State objects.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StateHelper {
|
||||
|
||||
private StateHelper() {
|
||||
// hide constructor, no initialisation possible
|
||||
}
|
||||
|
||||
public static State getOperational(@Nullable StationStatus currentStatus) {
|
||||
if (currentStatus == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
return OnOffType.from(currentStatus.isOperational());
|
||||
}
|
||||
|
||||
public static State getLastUpdate(@Nullable StationStatus currentStatus) {
|
||||
return currentStatus == null ? UnDefType.UNDEF : new DateTimeType(currentStatus.getLastUpdate());
|
||||
}
|
||||
|
||||
public static State getCurrentOutput(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getCurrentOutput() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new QuantityType<>(status.getCurrentOutput(), Units.WATT);
|
||||
}
|
||||
|
||||
public static State getDayTotal(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getDayTotal() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new QuantityType<>(status.getDayTotal(), Units.KILOWATT_HOUR);
|
||||
}
|
||||
|
||||
public static State getMonthTotal(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getMonthTotal() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new QuantityType<>(status.getMonthTotal(), Units.KILOWATT_HOUR);
|
||||
}
|
||||
|
||||
public static State getOverallTotal(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getOverallTotal() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new QuantityType<>(status.getOverallTotal(), Units.KILOWATT_HOUR);
|
||||
}
|
||||
|
||||
public static State getDayIncome(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getDayIncome() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new DecimalType(status.getDayIncome());
|
||||
}
|
||||
|
||||
public static State getTotalIncome(@Nullable StationStatus status) {
|
||||
if (status == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
if (status.getTotalIncome() == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
return new DecimalType(status.getTotalIncome());
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import static org.openhab.binding.semsportal.internal.SEMSPortalBindingConstants.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationStatus;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link StationHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StationHandler extends BaseThingHandler {
|
||||
private Logger logger = LoggerFactory.getLogger(StationHandler.class);
|
||||
private static final long MAX_STATUS_AGE_MINUTES = 1;
|
||||
|
||||
private @Nullable StationStatus currentStatus;
|
||||
private LocalDateTime lastUpdate = LocalDateTime.MIN;
|
||||
|
||||
public StationHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (isPortalOK()) {
|
||||
if (command instanceof RefreshType) {
|
||||
scheduler.execute(() -> {
|
||||
ensureRecentStatus();
|
||||
updateChannelState(channelUID);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPortalOK() {
|
||||
PortalHandler portal = getPortal();
|
||||
return portal != null && portal.isLoggedIn();
|
||||
}
|
||||
|
||||
private void updateChannelState(ChannelUID channelUID) {
|
||||
if (!isPortalOK()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"Unable to update station info. Check Bridge status for details.");
|
||||
return;
|
||||
}
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_CURRENT_OUTPUT:
|
||||
updateState(channelUID.getId(), StateHelper.getCurrentOutput(currentStatus));
|
||||
break;
|
||||
case CHANNEL_TODAY_TOTAL:
|
||||
updateState(channelUID.getId(), StateHelper.getDayTotal(currentStatus));
|
||||
break;
|
||||
case CHANNEL_MONTH_TOTAL:
|
||||
updateState(channelUID.getId(), StateHelper.getMonthTotal(currentStatus));
|
||||
break;
|
||||
case CHANNEL_OVERALL_TOTAL:
|
||||
updateState(channelUID.getId(), StateHelper.getOverallTotal(currentStatus));
|
||||
break;
|
||||
case CHANNEL_TODAY_INCOME:
|
||||
updateState(channelUID.getId(), StateHelper.getDayIncome(currentStatus));
|
||||
break;
|
||||
case CHANNEL_TOTAL_INCOME:
|
||||
updateState(channelUID.getId(), StateHelper.getTotalIncome(currentStatus));
|
||||
break;
|
||||
case CHANNEL_LASTUPDATE:
|
||||
updateState(channelUID.getId(), StateHelper.getLastUpdate(currentStatus));
|
||||
break;
|
||||
default:
|
||||
logger.debug("No mapping found for channel {}", channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureRecentStatus() {
|
||||
if (lastUpdate.isBefore(LocalDateTime.now().minusMinutes(MAX_STATUS_AGE_MINUTES))) {
|
||||
updateStation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
scheduler.scheduleWithFixedDelay(() -> ensureRecentStatus(), 0, getUpdateInterval(), TimeUnit.MINUTES);
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Unable to update station info. Check Bridge status for details.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long getUpdateInterval() {
|
||||
PortalHandler portal = getPortal();
|
||||
if (portal == null) {
|
||||
return SEMSPortalBindingConstants.DEFAULT_UPDATE_INTERVAL_MINUTES;
|
||||
}
|
||||
return portal.getUpdateInterval();
|
||||
}
|
||||
|
||||
private void updateStation() {
|
||||
if (!isPortalOK()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"Unable to update station info. Check Bridge status for details.");
|
||||
return;
|
||||
}
|
||||
PortalHandler portal = getPortal();
|
||||
if (portal != null) {
|
||||
try {
|
||||
currentStatus = portal.getStationStatus(getStationUUID());
|
||||
StationStatus localCurrentStatus = currentStatus;
|
||||
if (localCurrentStatus != null && localCurrentStatus.isOperational()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE, "Station not operational");
|
||||
}
|
||||
updateAllChannels();
|
||||
} catch (CommunicationException commEx) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, commEx.getMessage());
|
||||
} catch (ConfigurationException confEx) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, confEx.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Unable to find portal for thing {}", getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
private String getStationUUID() {
|
||||
String uuid = getThing().getProperties().get(STATION_UUID);
|
||||
return uuid == null ? "" : uuid;
|
||||
}
|
||||
|
||||
private void updateAllChannels() {
|
||||
for (String channelName : ALL_CHANNELS) {
|
||||
Channel channel = thing.getChannel(channelName);
|
||||
if (channel != null) {
|
||||
updateChannelState(channel.getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable PortalHandler getPortal() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null && bridge.getHandler() != null && bridge.getHandler() instanceof PortalHandler) {
|
||||
return (PortalHandler) bridge.getHandler();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.semsportal.internal.SEMSPortalBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.semsportal.internal.PortalHandler;
|
||||
import org.openhab.binding.semsportal.internal.dto.Station;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The discovery service can discover the power stations that are registered to the portal that it belongs to. It will
|
||||
* find unique power stations and add them as a discovery result;
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StationDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final int DISCOVERY_TIME = 10;
|
||||
private PortalHandler portal;
|
||||
private ThingUID bridgeUID;
|
||||
|
||||
public StationDiscoveryService(PortalHandler bridgeHandler) {
|
||||
super(Set.of(THING_TYPE_STATION), DISCOVERY_TIME);
|
||||
this.portal = bridgeHandler;
|
||||
this.bridgeUID = bridgeHandler.getThing().getUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
for (Station station : portal.getAllStations()) {
|
||||
DiscoveryResult discovery = DiscoveryResultBuilder.create(createThingUUID(station)).withBridge(bridgeUID)
|
||||
.withProperties(buildProperties(station))
|
||||
.withRepresentationProperty(STATION_REPRESENTATION_PROPERTY)
|
||||
.withLabel(String.format(STATION_LABEL_FORMAT, station.getName())).withThingType(THING_TYPE_STATION)
|
||||
.build();
|
||||
thingDiscovered(discovery);
|
||||
}
|
||||
stopScan();
|
||||
}
|
||||
|
||||
private ThingUID createThingUUID(Station station) {
|
||||
return new ThingUID(THING_TYPE_STATION, station.getStationId(), bridgeUID.getId());
|
||||
}
|
||||
|
||||
private @Nullable Map<String, Object> buildProperties(Station station) {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, station.getType());
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, station.getSerialNumber());
|
||||
properties.put(STATION_NAME, station.getName());
|
||||
properties.put(STATION_CAPACITY, station.getCapacity());
|
||||
properties.put(STATION_UUID, station.getStationId());
|
||||
properties.put(STATION_CAPACITY, station.getCapacity());
|
||||
return properties;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The base response contains the generic properties of each response from the portal. Depending on the request, the
|
||||
* data component contains different information. The subclasses of the BaseResponse contain the mapping of the data
|
||||
* with respect to their request context.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BaseResponse {
|
||||
public static final String OK = "0";
|
||||
|
||||
public static final String NO_SESSION = "100001";
|
||||
public static final String SESSION_EXPIRED = "100002";
|
||||
public static final String INVALID = "100005";
|
||||
public static final String EXCEPTION = "innerexception";
|
||||
|
||||
private @Nullable String code;
|
||||
private @Nullable String msg;
|
||||
|
||||
public @Nullable String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public @Nullable String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return OK.equals(code);
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return EXCEPTION.equals(code);
|
||||
}
|
||||
|
||||
public boolean isSessionInvalid() {
|
||||
return NO_SESSION.equals(code) || SESSION_EXPIRED.equals(code);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* POJO containing details about the inverter. Only a very small subset of the available properties is mapped
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class InverterDetails {
|
||||
@SerializedName("last_refresh_time")
|
||||
private Date lastUpdate;
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* POJO for mapping the SEMS portal data response /data/kpi
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class KeyPerformanceIndicators {
|
||||
@SerializedName("pac")
|
||||
private Double currentOutput;
|
||||
@SerializedName("month_generation")
|
||||
private Double monthPower;
|
||||
@SerializedName("total_power")
|
||||
private Double totalPower;
|
||||
@SerializedName("day_income")
|
||||
private Double dayIncome;
|
||||
@SerializedName("total_income")
|
||||
private Double totalIncome;
|
||||
@SerializedName("yield_rate")
|
||||
private Double yieldRate;
|
||||
private String currency;
|
||||
|
||||
public Double getCurrentOutput() {
|
||||
return currentOutput;
|
||||
}
|
||||
|
||||
public Double getMonthPower() {
|
||||
return monthPower;
|
||||
}
|
||||
|
||||
public Double getTotalPower() {
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
public Double getDayIncome() {
|
||||
return dayIncome;
|
||||
}
|
||||
|
||||
public Double getTotalIncome() {
|
||||
return totalIncome;
|
||||
}
|
||||
|
||||
public Double getYieldRate() {
|
||||
return yieldRate;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The login request to the portal. Response can be deserialized in a {@link LoginResponse}
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class LoginRequest {
|
||||
private String account;
|
||||
private String pwd;
|
||||
|
||||
public LoginRequest(String account, String pwd) {
|
||||
this.account = account;
|
||||
this.pwd = pwd;
|
||||
}
|
||||
|
||||
public void setPwd(String pwd) {
|
||||
this.pwd = pwd;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getPwd() {
|
||||
return pwd;
|
||||
}
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Response to a {@link LoginRequest} to the portal.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class LoginResponse extends BaseResponse {
|
||||
@SerializedName("data")
|
||||
private SEMSToken token;
|
||||
|
||||
public SEMSToken getToken() {
|
||||
return token;
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
/**
|
||||
* A token is returned in a successful {@Link LoginRequest} and is needed to authorize any subsequent requests.
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class SEMSToken {
|
||||
private String uid;
|
||||
private long timestamp;
|
||||
private String token;
|
||||
private String client;
|
||||
private String version;
|
||||
private String language;
|
||||
|
||||
public SEMSToken(String version, String client, String language) {
|
||||
this.version = version;
|
||||
this.client = client;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* POJO for mapping the portal data response to the {@link StatusRequest} and the {@Link StationListRequest}
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class Station {
|
||||
@SerializedName("powerstation_id")
|
||||
private String stationId;
|
||||
@SerializedName("stationname")
|
||||
private String name;
|
||||
@SerializedName("sn")
|
||||
private String serialNumber;
|
||||
private String type;
|
||||
private Double capacity;
|
||||
private int status;
|
||||
@SerializedName("out_pac")
|
||||
private Double currentPower;
|
||||
@SerializedName("eday")
|
||||
private Double dayTotal;
|
||||
@SerializedName("emonth")
|
||||
private Double monthTotal;
|
||||
@SerializedName("etotal")
|
||||
private Double overallTotal;
|
||||
@SerializedName("d")
|
||||
private InverterDetails details;
|
||||
|
||||
public String getStationId() {
|
||||
return stationId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Double getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public Double getCurrentPower() {
|
||||
return currentPower;
|
||||
}
|
||||
|
||||
public Double getDayTotal() {
|
||||
return dayTotal;
|
||||
}
|
||||
|
||||
public Double getMonthTotal() {
|
||||
return monthTotal;
|
||||
}
|
||||
|
||||
public Double getOverallTotal() {
|
||||
return overallTotal;
|
||||
}
|
||||
|
||||
public InverterDetails getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Request to list all available power stations in an account. Answer can be deserialized in a
|
||||
* {@link StationListResponse}
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StationListRequest {
|
||||
// Properties are private but used by Gson to construct the request
|
||||
@SerializedName("page_size")
|
||||
private int pageSize = 5;
|
||||
@SerializedName("page_index")
|
||||
private int pageIndex = 1;
|
||||
@SerializedName("order_by")
|
||||
private String orderBy = "";
|
||||
@SerializedName("powerstation_status")
|
||||
private String powerstationStatus = "";
|
||||
// @SerializedName("key")
|
||||
// private String key = "";
|
||||
@SerializedName("powerstation_id")
|
||||
private String powerstationId = "";
|
||||
@SerializedName("powerstation_type")
|
||||
private String powerstationType = "";
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* POJO containing the response to the {@link StationListRequest}
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class StationListResponse extends BaseResponse {
|
||||
|
||||
@SerializedName("data")
|
||||
private List<Station> stations;
|
||||
|
||||
public List<Station> getStations() {
|
||||
return stations;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Facade for easy access to the SEMS portal data response. Data is distributed over different parts of the response
|
||||
* object
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class StationStatus {
|
||||
@SerializedName("kpi")
|
||||
private KeyPerformanceIndicators keyPerformanceIndicators;
|
||||
@SerializedName("inverter")
|
||||
private List<Station> stations;
|
||||
|
||||
public Double getCurrentOutput() {
|
||||
return keyPerformanceIndicators.getCurrentOutput();
|
||||
}
|
||||
|
||||
public Double getDayTotal() {
|
||||
return stations.isEmpty() ? null : stations.get(0).getDayTotal();
|
||||
}
|
||||
|
||||
public Double getMonthTotal() {
|
||||
return stations.isEmpty() ? null : stations.get(0).getMonthTotal();
|
||||
}
|
||||
|
||||
public Double getOverallTotal() {
|
||||
return stations.isEmpty() ? null : stations.get(0).getOverallTotal();
|
||||
}
|
||||
|
||||
public Double getDayIncome() {
|
||||
return keyPerformanceIndicators.getDayIncome();
|
||||
}
|
||||
|
||||
public Double getTotalIncome() {
|
||||
return keyPerformanceIndicators.getTotalIncome();
|
||||
}
|
||||
|
||||
public boolean isOperational() {
|
||||
return stations.isEmpty() ? false : stations.get(0).getStatus() == 1;
|
||||
}
|
||||
|
||||
public ZonedDateTime getLastUpdate() {
|
||||
if (stations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return ZonedDateTime.ofInstant(stations.get(0).getDetails().getLastUpdate().toInstant(),
|
||||
ZoneId.systemDefault());
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Request for the status of a Power Station. Answer can be deserialized in a {@link StatusResponse}
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class StatusRequest {
|
||||
private String powerStationId;
|
||||
|
||||
public StatusRequest(String powerStationId) {
|
||||
this.powerStationId = powerStationId;
|
||||
}
|
||||
|
||||
public void setPowerStationId(String powerStationId) {
|
||||
this.powerStationId = powerStationId;
|
||||
}
|
||||
|
||||
public String getPowerStationId() {
|
||||
return powerStationId;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* POJO containing (a small subset of) the data received from the portal when issuing a {@link StationRequest)
|
||||
*
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
public class StatusResponse extends BaseResponse {
|
||||
|
||||
@SerializedName("data")
|
||||
private StationStatus status;
|
||||
|
||||
public StationStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="semsportal" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>SEMSPortal Binding</name>
|
||||
<description>
|
||||
This is the binding for SEMSPortal. The SEMS portal is where a GoodWE solar installation uploads it's
|
||||
data. The SEMS portal has a lot of data, only a few of them are currently mapped to a channel.
|
||||
You will need an account
|
||||
at semsportal.com and have your solar installation registered on that account.
|
||||
</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="semsportal"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="portal">
|
||||
<label>SEMS Portal</label>
|
||||
<description>The SEMS Portal is where the data about powerstations is collected online.
|
||||
Configuration will only work if
|
||||
you have used this account at least once in the portal itsself.</description>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">GoodWe</property>
|
||||
</properties>
|
||||
<representation-property>username</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="username" type="text" required="true">
|
||||
<label>Username</label>
|
||||
<description>Username (email address) of the account at the SEMS portal</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>Password of the SEMS Portal</description>
|
||||
</parameter>
|
||||
<parameter name="interval" type="integer" min="1" max="60" unit="min">
|
||||
<label>Interval</label>
|
||||
<description>Number of minutes between updates. Minimum is 1 minute, maximum is 60. The default is 5 minutes.</description>
|
||||
<default>5</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="station" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="portal"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Power Station</label>
|
||||
<description>A Power Station is the GoodWe converter that is connected through the internet with the SEMSPortal.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="lastUpdate" typeId="lastUpdate"/>
|
||||
<channel id="currentOutput" typeId="currentOutput"/>
|
||||
<channel id="todayTotal" typeId="todayTotal"/>
|
||||
<channel id="monthTotal" typeId="monthTotal"/>
|
||||
<channel id="overallTotal" typeId="overallTotal"/>
|
||||
<channel id="todayIncome" typeId="todayIncome"/>
|
||||
<channel id="totalIncome" typeId="totalIncome"/>
|
||||
</channels>
|
||||
|
||||
</thing-type>
|
||||
|
||||
|
||||
<channel-type id="lastUpdate">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Timestamp that the last information was received from the station. This is not the same as the last time
|
||||
that was checked: the station goes offline at night.</description>
|
||||
</channel-type>
|
||||
<channel-type id="currentOutput">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Current Output</label>
|
||||
<description>Current output in Watts</description>
|
||||
</channel-type>
|
||||
<channel-type id="todayTotal">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Todays Total Output</label>
|
||||
<description>Todays total output in kWh</description>
|
||||
</channel-type>
|
||||
<channel-type id="monthTotal">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Current Month Total Output</label>
|
||||
<description>The total output of this month in kWh</description>
|
||||
</channel-type>
|
||||
<channel-type id="overallTotal">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Overall Total Output</label>
|
||||
<description>The total output from the start of the installation in kWh</description>
|
||||
</channel-type>
|
||||
<channel-type id="todayIncome">
|
||||
<item-type>Number</item-type>
|
||||
<label>Todays Income</label>
|
||||
<description>Todays income. Only reports if you have set the tariffs in the SEMS portal. Unit is the currency that is
|
||||
set in these tariffs.</description>
|
||||
</channel-type>
|
||||
<channel-type id="totalIncome">
|
||||
<item-type>Number</item-type>
|
||||
<label>Total Income</label>
|
||||
<description>Total income since installation. Only reports if you have set the tariffs in the SEMS portal. Unit is the
|
||||
currency that is set in these tariffs.</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.semsportal.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.semsportal.internal.dto.BaseResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.LoginResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.StationListResponse;
|
||||
import org.openhab.binding.semsportal.internal.dto.StatusResponse;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* @author Iwan Bron - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SEMSJsonParserTest {
|
||||
@Test
|
||||
public void testParseSuccessStatusResult() throws Exception {
|
||||
String json = Files.readString(Paths.get("src/test/resources/success_status.json"));
|
||||
StatusResponse response = getGson().fromJson(json, StatusResponse.class);
|
||||
assertNotNull(response, "Expected deserialized StatusResponse");
|
||||
if (response != null) {// response cannot be null, was asserted before, but code check produces a warning
|
||||
assertTrue(response.isOk(), "Successresponse should be OK");
|
||||
assertNotNull(response.getStatus(), "Expected deserialized StatusResponse.status");
|
||||
assertEquals(381.0, response.getStatus().getCurrentOutput(), "Current Output parsed correctly");
|
||||
assertEquals(0.11, response.getStatus().getDayIncome(), "Day income parsed correctly");
|
||||
assertEquals(0.5, response.getStatus().getDayTotal(), "Day total parsed correctly");
|
||||
assertEquals(ZonedDateTime.of(2021, 2, 6, 11, 22, 48, 0, ZoneId.systemDefault()),
|
||||
response.getStatus().getLastUpdate(), "Last update parsed correctly");
|
||||
assertEquals(17.2, response.getStatus().getMonthTotal(), "Month total parsed correctly");
|
||||
assertEquals(7379.0, response.getStatus().getOverallTotal(), "Overall total parsed correctly");
|
||||
assertEquals(823.38, response.getStatus().getTotalIncome(), "Total income parsed correctly");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseErrorStatusResult() throws Exception {
|
||||
String json = Files.readString(Paths.get("src/test/resources/error_status.json"));
|
||||
BaseResponse response = getGson().fromJson(json, BaseResponse.class);
|
||||
assertNotNull(response, "Expected deserialized StatusResponse");
|
||||
if (response != null) {// response cannot be null, was asserted before, but code check produces a warning
|
||||
assertEquals(response.getCode(), BaseResponse.EXCEPTION, "Error response shoud have error code");
|
||||
assertTrue(response.isError(), "Error response should have isError = true");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSuccessLoginResult() throws Exception {
|
||||
String json = Files.readString(Paths.get("src/test/resources/success_login.json"));
|
||||
LoginResponse response = getGson().fromJson(json, LoginResponse.class);
|
||||
assertNotNull(response, "Expected deserialized LoginResponse");
|
||||
if (response != null) {// response cannot be null, was asserted before, but code check produces a warning
|
||||
assertTrue(response.isOk(), "Success response should result in OK");
|
||||
assertNotNull(response.getToken(), "Success response should result in token");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseErrorLoginResult() throws Exception {
|
||||
String json = Files.readString(Paths.get("src/test/resources/error_login.json"));
|
||||
LoginResponse response = getGson().fromJson(json, LoginResponse.class);
|
||||
assertNotNull(response, "Expected deserialized LoginResponse");
|
||||
if (response != null) {// response cannot be null, was asserted before, but code check produces a warning
|
||||
assertFalse(response.isOk(), "Error response should not result in OK");
|
||||
assertNull(response.getToken(), "Error response should have null token");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSuccessListResult() throws Exception {
|
||||
String json = Files.readString(Paths.get("src/test/resources/success_list.json"));
|
||||
StationListResponse response = getGson().fromJson(json, StationListResponse.class);
|
||||
assertNotNull(response, "Expected deserialized StationListResponse");
|
||||
if (response != null) {// response cannot be null, was asserted before, but code check produces a warning
|
||||
assertTrue(response.isOk(), "Success response should result in OK");
|
||||
assertNotNull(response.getStations(), "List response should have station list");
|
||||
assertEquals(1, response.getStations().size(), "List response should have station list");
|
||||
}
|
||||
}
|
||||
|
||||
private Gson getGson() {
|
||||
return new GsonBuilder().setDateFormat(SEMSPortalBindingConstants.DATE_FORMAT).create();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"hasError": false,
|
||||
"code": 100005,
|
||||
"msg": "Email or password error.",
|
||||
"data": null,
|
||||
"components": {
|
||||
"para": null,
|
||||
"langVer": 97,
|
||||
"timeSpan": 0,
|
||||
"api": "http://www.semsportal.com:82/api/v2/Common/CrossLogin",
|
||||
"msgSocketAdr": "https://eu-xxzx.semsportal.com"
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"language": "en",
|
||||
"function": [
|
||||
"ADD"
|
||||
],
|
||||
"hasError": true,
|
||||
"msg": "未将对象引用设置到对象的实例。",
|
||||
"code": "innerexception",
|
||||
"data": "",
|
||||
"components": {
|
||||
"para": "{\"model\":{\"PowerStationId\":\"ERRORCODE\"}}",
|
||||
"langVer": 97,
|
||||
"timeSpan": 8,
|
||||
"api": "http://www.semsportal.com:82/api/v2/PowerStation/GetMonitorDetailByPowerstationId",
|
||||
"msgSocketAdr": null
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
{
|
||||
"hasError": false,
|
||||
"code": 0,
|
||||
"msg": "Success",
|
||||
"data": [
|
||||
{
|
||||
"powerstation_id": "000000-0000000-0000000-00000",
|
||||
"stationname": "place",
|
||||
"first_letter": "",
|
||||
"adcode": "11111111111111",
|
||||
"location": "",
|
||||
"status": -1,
|
||||
"pac": 0.0,
|
||||
"capacity": 4.5,
|
||||
"eday": 13.1,
|
||||
"emonth": 195.0,
|
||||
"eday_income": 604.318,
|
||||
"etotal": 2746.9,
|
||||
"powerstation_type": "residential",
|
||||
"pre_org_id": null,
|
||||
"org_id": null,
|
||||
"longitude": "16",
|
||||
"latitude": "23",
|
||||
"pac_kw": 2746.9,
|
||||
"to_hour": 2.911111111111111,
|
||||
"weather": {
|
||||
"HeWeather6": [
|
||||
{
|
||||
"basic": {
|
||||
"cid": "XXXXXXXX",
|
||||
"location": "Location",
|
||||
"parent_city": "City",
|
||||
"admin_area": "Area",
|
||||
"cnty": "Country",
|
||||
"lat": "16",
|
||||
"lon": "23",
|
||||
"tz": "+1.00"
|
||||
},
|
||||
"update": {
|
||||
"loc": "2019-08-12 23:57",
|
||||
"utc": "2019-08-12 22:57"
|
||||
},
|
||||
"status": "ok",
|
||||
"now": {
|
||||
"cloud": "100",
|
||||
"cond_code": "300",
|
||||
"cond_txt": "Shower Rain",
|
||||
"fl": "14",
|
||||
"hum": "100",
|
||||
"pcpn": "0.3",
|
||||
"pres": "1014",
|
||||
"tmp": "14",
|
||||
"vis": "16",
|
||||
"wind_deg": "90",
|
||||
"wind_dir": "E",
|
||||
"wind_sc": "1",
|
||||
"wind_spd": "4"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"currency": "EUR",
|
||||
"yield_rate": 0.22,
|
||||
"is_stored": false
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"para": null,
|
||||
"langVer": 40,
|
||||
"timeSpan": 0,
|
||||
"api": "http://eu.semsportal.com:82/api/PowerStationMonitor/QueryPowerStationMonitorForApp"
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"hasError": false,
|
||||
"code": 0,
|
||||
"msg": "Success",
|
||||
"data": {
|
||||
"uid": "0000000-0000-0000-00000000",
|
||||
"timestamp": 1612644477008,
|
||||
"token": "12345678",
|
||||
"client": "ios",
|
||||
"version": "v2.1.0",
|
||||
"language": "en"
|
||||
},
|
||||
"components": {
|
||||
"para": null,
|
||||
"langVer": 97,
|
||||
"timeSpan": 0,
|
||||
"api": "http://eu.semsportal.com:82/api/Auth/GetTokenV2",
|
||||
"msgSocketAdr": "https://eu-xxzx.semsportal.com"
|
||||
},
|
||||
"api": "https://eu.semsportal.com/api/"
|
||||
}
|
@ -0,0 +1,941 @@
|
||||
{
|
||||
"language": "en",
|
||||
"function": [
|
||||
"ADD",
|
||||
"VIEW",
|
||||
"EDIT",
|
||||
"DELETE",
|
||||
"INVERTER_A",
|
||||
"INVERTER_E",
|
||||
"INVERTER_D"
|
||||
],
|
||||
"hasError": false,
|
||||
"msg": "success",
|
||||
"code": "0",
|
||||
"data": {
|
||||
"info": {
|
||||
"powerstation_id": "RANDOM_UUID",
|
||||
"time": "02/06/2021 11:24:41",
|
||||
"date_format": "MM.dd.yyyy",
|
||||
"date_format_ym": "MM.yyyy",
|
||||
"stationname": "Home",
|
||||
"address": "Somewhere",
|
||||
"owner_name": null,
|
||||
"owner_phone": null,
|
||||
"owner_email": "some@mailaddress.com",
|
||||
"battery_capacity": 0.0,
|
||||
"turnon_time": "04/06/2020 18:19:40",
|
||||
"create_time": "04/06/2020 18:18:32",
|
||||
"capacity": 7.8,
|
||||
"longitude": 16,
|
||||
"latitude": 23,
|
||||
"powerstation_type": "Residential",
|
||||
"status": 1,
|
||||
"is_stored": false,
|
||||
"is_powerflow": false,
|
||||
"charts_type": 4,
|
||||
"has_pv": true,
|
||||
"has_statistics_charts": false,
|
||||
"only_bps": false,
|
||||
"only_bpu": false,
|
||||
"time_span": -1.0,
|
||||
"pr_value": ""
|
||||
},
|
||||
"kpi": {
|
||||
"month_generation": 17.7,
|
||||
"pac": 381.0,
|
||||
"power": 0.5,
|
||||
"total_power": 7379.0,
|
||||
"day_income": 0.11,
|
||||
"total_income": 823.38,
|
||||
"yield_rate": 0.12,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"images": [],
|
||||
"weather": {
|
||||
"HeWeather6": [
|
||||
{
|
||||
"daily_forecast": [
|
||||
{
|
||||
"cond_code_d": "407",
|
||||
"cond_code_n": "499",
|
||||
"cond_txt_d": "Snow Flurry",
|
||||
"cond_txt_n": "Snow",
|
||||
"date": "2021-02-06",
|
||||
"time": "2021-02-06 00:00:00",
|
||||
"hum": "79",
|
||||
"pcpn": "0.2",
|
||||
"pop": "49",
|
||||
"pres": "1011",
|
||||
"tmp_max": "0",
|
||||
"tmp_min": "-5",
|
||||
"uv_index": "0",
|
||||
"vis": "6",
|
||||
"wind_deg": "84",
|
||||
"wind_dir": "E",
|
||||
"wind_sc": "4-5",
|
||||
"wind_spd": "34"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "499",
|
||||
"cond_code_n": "499",
|
||||
"cond_txt_d": "Snow",
|
||||
"cond_txt_n": "Snow",
|
||||
"date": "2021-02-07",
|
||||
"time": "2021-02-07 00:00:00",
|
||||
"hum": "93",
|
||||
"pcpn": "7.2",
|
||||
"pop": "82",
|
||||
"pres": "1006",
|
||||
"tmp_max": "-3",
|
||||
"tmp_min": "-6",
|
||||
"uv_index": "0",
|
||||
"vis": "1",
|
||||
"wind_deg": "70",
|
||||
"wind_dir": "NE",
|
||||
"wind_sc": "6-7",
|
||||
"wind_spd": "46"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "499",
|
||||
"cond_code_n": "101",
|
||||
"cond_txt_d": "Snow",
|
||||
"cond_txt_n": "Cloudy",
|
||||
"date": "2021-02-08",
|
||||
"time": "2021-02-08 00:00:00",
|
||||
"hum": "94",
|
||||
"pcpn": "1.1",
|
||||
"pop": "55",
|
||||
"pres": "1005",
|
||||
"tmp_max": "-3",
|
||||
"tmp_min": "-7",
|
||||
"uv_index": "0",
|
||||
"vis": "1",
|
||||
"wind_deg": "84",
|
||||
"wind_dir": "E",
|
||||
"wind_sc": "3-4",
|
||||
"wind_spd": "20"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "901",
|
||||
"cond_code_n": "901",
|
||||
"cond_txt_d": "Cold",
|
||||
"cond_txt_n": "Cold",
|
||||
"date": "2021-02-09",
|
||||
"time": "2021-02-09 00:00:00",
|
||||
"hum": "92",
|
||||
"pcpn": "0.0",
|
||||
"pop": "25",
|
||||
"pres": "1009",
|
||||
"tmp_max": "-4",
|
||||
"tmp_min": "-9",
|
||||
"uv_index": "1",
|
||||
"vis": "24",
|
||||
"wind_deg": "85",
|
||||
"wind_dir": "E",
|
||||
"wind_sc": "3-4",
|
||||
"wind_spd": "18"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "901",
|
||||
"cond_code_n": "103",
|
||||
"cond_txt_d": "Cold",
|
||||
"cond_txt_n": "Partly Cloudy",
|
||||
"date": "2021-02-10",
|
||||
"time": "2021-02-10 00:00:00",
|
||||
"hum": "90",
|
||||
"pcpn": "0.0",
|
||||
"pop": "12",
|
||||
"pres": "1019",
|
||||
"tmp_max": "-3",
|
||||
"tmp_min": "-7",
|
||||
"uv_index": "1",
|
||||
"vis": "25",
|
||||
"wind_deg": "67",
|
||||
"wind_dir": "NE",
|
||||
"wind_sc": "3-4",
|
||||
"wind_spd": "14"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "103",
|
||||
"cond_code_n": "101",
|
||||
"cond_txt_d": "Partly Cloudy",
|
||||
"cond_txt_n": "Cloudy",
|
||||
"date": "2021-02-11",
|
||||
"time": "2021-02-11 00:00:00",
|
||||
"hum": "93",
|
||||
"pcpn": "0.0",
|
||||
"pop": "8",
|
||||
"pres": "1018",
|
||||
"tmp_max": "-1",
|
||||
"tmp_min": "-7",
|
||||
"uv_index": "2",
|
||||
"vis": "6",
|
||||
"wind_deg": "187",
|
||||
"wind_dir": "S",
|
||||
"wind_sc": "1-2",
|
||||
"wind_spd": "11"
|
||||
},
|
||||
{
|
||||
"cond_code_d": "999",
|
||||
"cond_code_n": "999",
|
||||
"cond_txt_d": "-",
|
||||
"cond_txt_n": "-",
|
||||
"date": "2021-02-12",
|
||||
"time": "2021-02-12 00:00:00",
|
||||
"hum": "-",
|
||||
"pcpn": "-",
|
||||
"pop": "-",
|
||||
"pres": "-",
|
||||
"tmp_max": "-",
|
||||
"tmp_min": "-",
|
||||
"uv_index": "-",
|
||||
"vis": "-",
|
||||
"wind_deg": "-",
|
||||
"wind_dir": "--",
|
||||
"wind_sc": "--",
|
||||
"wind_spd": "-"
|
||||
}
|
||||
],
|
||||
"basic": {
|
||||
"cid": "NL2755251",
|
||||
"location": "SomeCity",
|
||||
"cnty": "SomeCountry",
|
||||
"lat": "13",
|
||||
"lon": "26",
|
||||
"tz": "+5.00"
|
||||
},
|
||||
"update": {
|
||||
"loc": "2021-02-05 23:56:00",
|
||||
"utc": "2021-02-05 22:56:00"
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inverter": [
|
||||
{
|
||||
"sn": "56000222200W0000",
|
||||
"dict": {
|
||||
"left": [
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "dmDeviceType",
|
||||
"value": "GW6000-DT",
|
||||
"unit": "",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "serialNum",
|
||||
"value": "5600000000000000",
|
||||
"unit": "",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "laCheckcode",
|
||||
"value": "026848",
|
||||
"unit": "",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "capacity",
|
||||
"value": "6",
|
||||
"unit": "kW",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "laConnected",
|
||||
"value": "04.06.2020 18:19:40",
|
||||
"unit": "",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "InverterPowerOfPlantMonitor",
|
||||
"value": "0.381",
|
||||
"unit": "kW",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "outputV",
|
||||
"value": "233.1/233.5/228.7",
|
||||
"unit": "V",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "acCurrent",
|
||||
"value": "0.5/0.4/0.5",
|
||||
"unit": "A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "acFrequency",
|
||||
"value": "50.00/50.00/50.00",
|
||||
"unit": "Hz",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
}
|
||||
],
|
||||
"right": [
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "innerTemp",
|
||||
"value": "32.6",
|
||||
"unit": "℃",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "dcVandC1",
|
||||
"value": "720.4/0.4",
|
||||
"unit": "V/A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "dcVandC2",
|
||||
"value": "0.0/0.0",
|
||||
"unit": "V/A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "dcVandC3",
|
||||
"value": "--",
|
||||
"unit": "V/A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "dcVandC4",
|
||||
"value": "--",
|
||||
"unit": "V/A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "strCurrent1",
|
||||
"value": "--",
|
||||
"unit": "A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "strCurrent2",
|
||||
"value": "--",
|
||||
"unit": "A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "strCurrent3",
|
||||
"value": "--",
|
||||
"unit": "A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
},
|
||||
{
|
||||
"isHT": false,
|
||||
"key": "strCurrent4",
|
||||
"value": "--",
|
||||
"unit": "A",
|
||||
"isFaultMsg": 0,
|
||||
"faultMsgCode": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_stored": false,
|
||||
"name": "home",
|
||||
"in_pac": 6554.3,
|
||||
"out_pac": 381.0,
|
||||
"eday": 0.5,
|
||||
"emonth": 17.2,
|
||||
"etotal": 7379.0,
|
||||
"status": 1,
|
||||
"turnon_time": "04/06/2020 18:19:40",
|
||||
"releation_id": "5600000000000000",
|
||||
"type": "GW6000-DT",
|
||||
"capacity": 6.0,
|
||||
"d": {
|
||||
"pw_id": "5600000000000000",
|
||||
"capacity": "6kW",
|
||||
"model": "GW6000-DT",
|
||||
"output_power": "381W",
|
||||
"output_current": "0.5A",
|
||||
"grid_voltage": "233.1V",
|
||||
"backup_output": "4294967.295V/0W",
|
||||
"soc": "400%",
|
||||
"soh": "0%",
|
||||
"last_refresh_time": "02.06.2021 11:22:48",
|
||||
"work_mode": "Wait Mode",
|
||||
"dc_input1": "720.4V/0.4A",
|
||||
"dc_input2": "0V/0A",
|
||||
"battery": "65535V/6553.5A/429483622W",
|
||||
"bms_status": "DischargingOfBattery",
|
||||
"warning": "Over Temperature/Under Temperature/Cell Voltage Differences/Over Total Voltage/Discharge Over Current/Charge Over Current/Under SOC/Under Total Voltage/Communication Fail/Output Short/SOC Too High/BMS Module Fault/BMS System Fault/BMS Internal Fault/TBD/TBD",
|
||||
"charge_current_limit": "1A",
|
||||
"discharge_current_limit": "0A",
|
||||
"firmware_version": 181809.0,
|
||||
"creationDate": "02/06/2021 18:22:48",
|
||||
"eDay": 0.5,
|
||||
"eTotal": 7379.0,
|
||||
"pac": 381.0,
|
||||
"hTotal": 5132.0,
|
||||
"vpv1": 720.4,
|
||||
"vpv2": 0.0,
|
||||
"vpv3": 6553.5,
|
||||
"vpv4": 6553.5,
|
||||
"ipv1": 0.4,
|
||||
"ipv2": 0.0,
|
||||
"ipv3": 6553.5,
|
||||
"ipv4": 6553.5,
|
||||
"vac1": 233.1,
|
||||
"vac2": 233.5,
|
||||
"vac3": 228.7,
|
||||
"iac1": 0.5,
|
||||
"iac2": 0.4,
|
||||
"iac3": 0.5,
|
||||
"fac1": 50.0,
|
||||
"fac2": 50.0,
|
||||
"fac3": 50.0,
|
||||
"istr1": 0.0,
|
||||
"istr2": 0.0,
|
||||
"istr3": 0.0,
|
||||
"istr4": 0.0,
|
||||
"istr5": 0.0,
|
||||
"istr6": 0.0,
|
||||
"istr7": 0.0,
|
||||
"istr8": 0.0,
|
||||
"istr9": 0.0,
|
||||
"istr10": 0.0,
|
||||
"istr11": 0.0,
|
||||
"istr12": 0.0,
|
||||
"istr13": 0.0,
|
||||
"istr14": 0.0,
|
||||
"istr15": 0.0,
|
||||
"istr16": 0.0
|
||||
},
|
||||
"it_change_flag": false,
|
||||
"tempperature": 32.6,
|
||||
"check_code": "026848",
|
||||
"next": null,
|
||||
"prev": null,
|
||||
"next_device": {
|
||||
"sn": null,
|
||||
"isStored": false
|
||||
},
|
||||
"prev_device": {
|
||||
"sn": null,
|
||||
"isStored": false
|
||||
},
|
||||
"invert_full": {
|
||||
"sn": "5600000000000000",
|
||||
"powerstation_id": "5600000000000000",
|
||||
"name": "home",
|
||||
"model_type": "GW6000-DT",
|
||||
"change_type": 0,
|
||||
"change_time": 0,
|
||||
"capacity": 6.0,
|
||||
"eday": 0.5,
|
||||
"iday": 0.11,
|
||||
"etotal": 7379.0,
|
||||
"itotal": 1623.38,
|
||||
"hour_total": 5132.0,
|
||||
"status": 1,
|
||||
"turnon_time": 1586168380200,
|
||||
"pac": 381.0,
|
||||
"tempperature": 32.6,
|
||||
"vpv1": 720.4,
|
||||
"vpv2": 0.0,
|
||||
"vpv3": 6553.5,
|
||||
"vpv4": 6553.5,
|
||||
"ipv1": 0.4,
|
||||
"ipv2": 0.0,
|
||||
"ipv3": 6553.5,
|
||||
"ipv4": 6553.5,
|
||||
"vac1": 233.1,
|
||||
"vac2": 233.5,
|
||||
"vac3": 228.7,
|
||||
"iac1": 0.5,
|
||||
"iac2": 0.4,
|
||||
"iac3": 0.5,
|
||||
"fac1": 50.0,
|
||||
"fac2": 50.0,
|
||||
"fac3": 50.0,
|
||||
"istr1": 0.0,
|
||||
"istr2": 0.0,
|
||||
"istr3": 0.0,
|
||||
"istr4": 0.0,
|
||||
"istr5": 0.0,
|
||||
"istr6": 0.0,
|
||||
"istr7": 0.0,
|
||||
"istr8": 0.0,
|
||||
"istr9": 0.0,
|
||||
"istr10": 0.0,
|
||||
"istr11": 0.0,
|
||||
"istr12": 0.0,
|
||||
"istr13": 0.0,
|
||||
"istr14": 0.0,
|
||||
"istr15": 0.0,
|
||||
"istr16": 0.0,
|
||||
"last_time": 1612606968847,
|
||||
"vbattery1": 65535.0,
|
||||
"ibattery1": 6553.5,
|
||||
"pmeter": 381.0,
|
||||
"soc": 400.0,
|
||||
"soh": -0.100000000000364,
|
||||
"bms_discharge_i_max": null,
|
||||
"bms_charge_i_max": 1.0,
|
||||
"bms_warning": 0,
|
||||
"bms_alarm": 65535,
|
||||
"battary_work_mode": 2,
|
||||
"workmode": 1,
|
||||
"vload": 4294967.295,
|
||||
"iload": 4294901.76,
|
||||
"firmwareversion": 1818.0,
|
||||
"pbackup": 0.0,
|
||||
"seller": 0.0,
|
||||
"buy": 0.0,
|
||||
"yesterdaybuytotal": null,
|
||||
"yesterdaysellertotal": null,
|
||||
"yesterdayct2sellertotal": null,
|
||||
"yesterdayetotal": null,
|
||||
"yesterdayetotalload": null,
|
||||
"thismonthetotle": 17.2,
|
||||
"lastmonthetotle": 7361.3,
|
||||
"ram": 9.0,
|
||||
"outputpower": 381.0,
|
||||
"fault_messge": 0,
|
||||
"isbuettey": false,
|
||||
"isbuetteybps": false,
|
||||
"isbuetteybpu": false,
|
||||
"isESUOREMU": false,
|
||||
"backUpPload_S": 0.0,
|
||||
"backUpVload_S": 0.0,
|
||||
"backUpIload_S": 0.0,
|
||||
"backUpPload_T": 0.0,
|
||||
"backUpVload_T": 0.0,
|
||||
"backUpIload_T": 0.0,
|
||||
"eTotalBuy": null,
|
||||
"eDayBuy": null,
|
||||
"eBatteryCharge": null,
|
||||
"eChargeDay": null,
|
||||
"eBatteryDischarge": null,
|
||||
"eDischargeDay": null,
|
||||
"battStrings": 6553.5,
|
||||
"meterConnectStatus": null,
|
||||
"mtActivepowerR": 0.0,
|
||||
"mtActivepowerS": 0.0,
|
||||
"mtActivepowerT": 0.0,
|
||||
"ezPro_connect_status": null,
|
||||
"dataloggersn": "",
|
||||
"equipment_name": null,
|
||||
"hasmeter": false,
|
||||
"meter_type": null,
|
||||
"pre_hour_lasttotal": null,
|
||||
"pre_hour_time": null,
|
||||
"current_hour_pv": 0.0,
|
||||
"extend_properties": null,
|
||||
"eP_connect_status_happen": null,
|
||||
"eP_connect_status_recover": null
|
||||
},
|
||||
"time": "02/06/2021 11:24:41",
|
||||
"battery": "65535V/6553.5A/429483622W",
|
||||
"firmware_version": 181809.0,
|
||||
"warning_bms": "Over Temperature/Under Temperature/Cell Voltage Differences/Over Total Voltage/Discharge Over Current/Charge Over Current/Under SOC/Under Total Voltage/Communication Fail/Output Short/SOC Too High/BMS Module Fault/BMS System Fault/BMS Internal Fault/TBD/TBD",
|
||||
"soh": "0%",
|
||||
"discharge_current_limit_bms": "0A",
|
||||
"charge_current_limit_bms": "1A",
|
||||
"soc": "400%",
|
||||
"pv_input_2": "0V/0A",
|
||||
"pv_input_1": "720.4V/0.4A",
|
||||
"back_up_output": "4294967.295V/0W",
|
||||
"output_voltage": "233.1V",
|
||||
"backup_voltage": "4294967.295V",
|
||||
"output_current": "0.5A",
|
||||
"output_power": "381W",
|
||||
"total_generation": "7379kWh",
|
||||
"daily_generation": "0.5kWh",
|
||||
"battery_charging": "65535V/6553.5A/429483622W",
|
||||
"last_refresh_time": "02/06/2021 11:22:48",
|
||||
"bms_status": "DischargingOfBattery",
|
||||
"pw_id": "0000000-0000-0000-00000000",
|
||||
"fault_message": "",
|
||||
"battery_power": 429483622.5,
|
||||
"point_index": "3",
|
||||
"points": [
|
||||
{
|
||||
"target_index": 1,
|
||||
"target_name": "Vpv1",
|
||||
"display": "Vpv1(V)",
|
||||
"unit": "V",
|
||||
"target_key": "Vpv1",
|
||||
"text_cn": "直流电压1",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 2,
|
||||
"target_name": "Vpv2",
|
||||
"display": "Vpv2(V)",
|
||||
"unit": "V",
|
||||
"target_key": "Vpv2",
|
||||
"text_cn": "直流电压2",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 5,
|
||||
"target_name": "Ipv1",
|
||||
"display": "Ipv1(A)",
|
||||
"unit": "A",
|
||||
"target_key": "Ipv1",
|
||||
"text_cn": "直流电流1",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 6,
|
||||
"target_name": "Ipv2",
|
||||
"display": "Ipv2(A)",
|
||||
"unit": "A",
|
||||
"target_key": "Ipv2",
|
||||
"text_cn": "直流电流2",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 9,
|
||||
"target_name": "Vac1",
|
||||
"display": "Vac1(V)",
|
||||
"unit": "V",
|
||||
"target_key": "Vac1",
|
||||
"text_cn": "交流电压1",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 10,
|
||||
"target_name": "Vac2",
|
||||
"display": "Vac2(V)",
|
||||
"unit": "V",
|
||||
"target_key": "Vac2",
|
||||
"text_cn": "交流电压2",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 11,
|
||||
"target_name": "Vac3",
|
||||
"display": "Vac3(V)",
|
||||
"unit": "V",
|
||||
"target_key": "Vac3",
|
||||
"text_cn": "交流电压3",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 12,
|
||||
"target_name": "Iac1",
|
||||
"display": "Iac1(A)",
|
||||
"unit": "A",
|
||||
"target_key": "Iac1",
|
||||
"text_cn": "交流电流1",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 13,
|
||||
"target_name": "Iac2",
|
||||
"display": "Iac2(A)",
|
||||
"unit": "A",
|
||||
"target_key": "Iac2",
|
||||
"text_cn": "交流电流2",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 14,
|
||||
"target_name": "Iac3",
|
||||
"display": "Iac3(A)",
|
||||
"unit": "A",
|
||||
"target_key": "Iac3",
|
||||
"text_cn": "交流电流3",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 15,
|
||||
"target_name": "Fac1",
|
||||
"display": "Fac1(Hz)",
|
||||
"unit": "Hz",
|
||||
"target_key": "Fac1",
|
||||
"text_cn": "频率1",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 16,
|
||||
"target_name": "Fac2",
|
||||
"display": "Fac2(Hz)",
|
||||
"unit": "Hz",
|
||||
"target_key": "Fac2",
|
||||
"text_cn": "频率2",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 17,
|
||||
"target_name": "Fac3",
|
||||
"display": "Fac3(Hz)",
|
||||
"unit": "Hz",
|
||||
"target_key": "Fac3",
|
||||
"text_cn": "频率3",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 18,
|
||||
"target_name": "Pac",
|
||||
"display": "Pac(W)",
|
||||
"unit": "W",
|
||||
"target_key": "Pac",
|
||||
"text_cn": "功率",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 19,
|
||||
"target_name": "WorkMode",
|
||||
"display": "WorkMode()",
|
||||
"unit": "",
|
||||
"target_key": "WorkMode",
|
||||
"text_cn": "工作模式",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 20,
|
||||
"target_name": "Temperature",
|
||||
"display": "Temperature(℃)",
|
||||
"unit": "℃",
|
||||
"target_key": "Tempperature",
|
||||
"text_cn": "温度",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 21,
|
||||
"target_name": "Daily Generation",
|
||||
"display": "Daily Generation(kWh)",
|
||||
"unit": "kWh",
|
||||
"target_key": "EDay",
|
||||
"text_cn": "日发电量",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 22,
|
||||
"target_name": "Total Generation",
|
||||
"display": "Total Generation(kWh)",
|
||||
"unit": "kWh",
|
||||
"target_key": "ETotal",
|
||||
"text_cn": "总发电量",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 23,
|
||||
"target_name": "HTotal",
|
||||
"display": "HTotal(h)",
|
||||
"unit": "h",
|
||||
"target_key": "HTotal",
|
||||
"text_cn": "工作时长",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
},
|
||||
{
|
||||
"target_index": 36,
|
||||
"target_name": "RSSI",
|
||||
"display": "RSSI(%)",
|
||||
"unit": "%",
|
||||
"target_key": "Reserved5",
|
||||
"text_cn": "GPRS信号强度",
|
||||
"target_sn_six": null,
|
||||
"target_sn_seven": null,
|
||||
"target_type": null,
|
||||
"storage_name": null
|
||||
}
|
||||
],
|
||||
"backup_pload_s": 0.0,
|
||||
"backup_vload_s": 0.0,
|
||||
"backup_iload_s": 0.0,
|
||||
"backup_pload_t": 0.0,
|
||||
"backup_vload_t": 0.0,
|
||||
"backup_iload_t": 0.0,
|
||||
"etotal_buy": null,
|
||||
"eday_buy": null,
|
||||
"ebattery_charge": null,
|
||||
"echarge_day": null,
|
||||
"ebattery_discharge": null,
|
||||
"edischarge_day": null,
|
||||
"batt_strings": 6553.5,
|
||||
"meter_connect_status": null,
|
||||
"mtactivepower_r": 0.0,
|
||||
"mtactivepower_s": 0.0,
|
||||
"mtactivepower_t": 0.0,
|
||||
"has_tigo": false,
|
||||
"canStartIV": false
|
||||
}
|
||||
],
|
||||
"hjgx": {
|
||||
"co2": 7.3568630000000006,
|
||||
"tree": 403.26234999999997,
|
||||
"coal": 2.981116
|
||||
},
|
||||
"pre_powerstation_id": null,
|
||||
"nex_powerstation_id": null,
|
||||
"homKit": {
|
||||
"homeKitLimit": false,
|
||||
"sn": null
|
||||
},
|
||||
"isTigo": false,
|
||||
"smuggleInfo": {
|
||||
"isAllSmuggle": false,
|
||||
"isSmuggle": false,
|
||||
"descriptionText": null,
|
||||
"sns": null
|
||||
},
|
||||
"hasPowerflow": false,
|
||||
"powerflow": null,
|
||||
"hasEnergeStatisticsCharts": false,
|
||||
"energeStatisticsCharts": {
|
||||
"contributingRate": 1.0,
|
||||
"selfUseRate": 1.0,
|
||||
"sum": 0.5,
|
||||
"buy": 0.0,
|
||||
"buyPercent": 0.0,
|
||||
"sell": 0.0,
|
||||
"sellPercent": 0.0,
|
||||
"selfUseOfPv": 0.5,
|
||||
"consumptionOfLoad": 0.5,
|
||||
"chartsType": 4,
|
||||
"hasPv": true,
|
||||
"hasCharge": false,
|
||||
"charge": 0.0,
|
||||
"disCharge": 0.0
|
||||
},
|
||||
"energeStatisticsTotals": {
|
||||
"contributingRate": 1.0,
|
||||
"selfUseRate": 1.0,
|
||||
"sum": 157.2,
|
||||
"buy": 0.0,
|
||||
"buyPercent": 0.0,
|
||||
"sell": 0.0,
|
||||
"sellPercent": 0.0,
|
||||
"selfUseOfPv": 157.2,
|
||||
"consumptionOfLoad": 157.2,
|
||||
"chartsType": 4,
|
||||
"hasPv": true,
|
||||
"hasCharge": false,
|
||||
"charge": 0.0,
|
||||
"disCharge": 0.0
|
||||
},
|
||||
"soc": {
|
||||
"power": 0,
|
||||
"status": -1
|
||||
},
|
||||
"environmental": [],
|
||||
"equipment": [
|
||||
{
|
||||
"type": "5",
|
||||
"title": "home",
|
||||
"status": 1,
|
||||
"statusText": null,
|
||||
"capacity": null,
|
||||
"actionThreshold": null,
|
||||
"subordinateEquipment": "",
|
||||
"powerGeneration": "Power:0.381(kW)",
|
||||
"eday": "Today Generation: 0.5(kWh)",
|
||||
"brand": "",
|
||||
"isStored": false,
|
||||
"soc": "SOC:400%",
|
||||
"isChange": false,
|
||||
"relationId": "0000000-0000-0000-00000000",
|
||||
"sn": "5600000000000000",
|
||||
"has_tigo": false,
|
||||
"is_sec": false,
|
||||
"is_secs": false,
|
||||
"targetPF": null,
|
||||
"exportPowerlimit": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"components": {
|
||||
"para": "{\"model\":{\"PowerStationId\":\"0000000-0000-0000-00000000\"}}",
|
||||
"langVer": 97,
|
||||
"timeSpan": 177,
|
||||
"api": "http://www.semsportal.com:82/api/v2/PowerStation/GetMonitorDetailByPowerstationId",
|
||||
"msgSocketAdr": null
|
||||
}
|
||||
}
|
@ -281,6 +281,7 @@
|
||||
<module>org.openhab.binding.sagercaster</module>
|
||||
<module>org.openhab.binding.samsungtv</module>
|
||||
<module>org.openhab.binding.satel</module>
|
||||
<module>org.openhab.binding.semsportal</module>
|
||||
<module>org.openhab.binding.senechome</module>
|
||||
<module>org.openhab.binding.seneye</module>
|
||||
<module>org.openhab.binding.sensebox</module>
|
||||
|
Loading…
Reference in New Issue
Block a user