[semsportal] initial contribution (#10100)

Signed-off-by: Iwan Bron <bron@olisa.eu>
This commit is contained in:
Iwan Bron 2021-05-23 20:45:04 +02:00 committed by GitHub
parent 4b5d64340f
commit 66a540289d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2817 additions and 0 deletions

View File

@ -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

View File

@ -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>

View 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

View 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

View 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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 = "";
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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"
}
}

View File

@ -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
}
}

View File

@ -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"
}
}

View File

@ -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/"
}

View File

@ -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": "Power0.381(kW)",
"eday": "Today Generation 0.5(kWh)",
"brand": "",
"isStored": false,
"soc": "SOC400%",
"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
}
}

View File

@ -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>