[solax] Support for Solax EV charger via direct(local) data retrieval (#17055)

* Implementation of Solax EV Charger support

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
This commit is contained in:
Konstantin Polihronov 2024-07-23 10:23:03 +03:00 committed by GitHub
parent cbbc36697c
commit 104a1e5379
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1206 additions and 249 deletions

View File

@ -13,8 +13,9 @@ In case the parsed information that comes with the binding out of the box differ
## Supported Things
| Thing | Thing Type | Description |
|------------------------|------------|-------------------------------------------------------------------------------------|
| local-connect-inverter | Thing | An inverter representation with all the data available as a channels (directly retrieved from the wi-fi module |
|------------------------|------------|---------------------------------------------------------------------------------------------------------------------------------|
| local-connect-inverter | Thing | An inverter representation with all the data available as a channels (directly retrieved from the wi-fi module) |
| local-connect-charger | Thing | An electric vehicle charger representation with all the data available as a channels (directly retrieved from the wi-fi module) |
| cloud-connect-inverter | Thing | An inverter representation with all the data available as a channels (retrieved from the Solax cloud API) |
Note: Channels may vary depending on the inverter type and the availability of information for parsing the raw data.
@ -113,6 +114,46 @@ If you're missing a channel this means that it's not supported for your inverter
| inverterType | Inverter Type (for example X1_HYBRID_G4) |
### Local Connect EV Charger Configuration
### Parameters
| Parameter | Description |
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| refreshInterval | Defines the refresh interval when the binding polls from the inverter's Wi-Fi module (in seconds). Optional parameter. Default 10 seconds. |
| password | Password for accessing the Wi-Fi module (the serial number of the wifi). Mandatory parameter. |
| hostname | IP address or hostname of your Wi-Fi module. If hostname is used must be resolvable by OpenHAB. Mandatory parameter. |
### Channels
| Channel ID | Type | Description |
|------------------------------------------|-----------------------------|---------------------------------------------------------------|
| eq-single-session | Number:Energy | Energy charged for the current session. [kWh] |
| eq-total | Number:Energy | Total energy charged for all sessions. [kWh] |
| charger-output-power-phase1 | Number:Power | Power to/from the charger phase 1. [W] |
| charger-output-power-phase2 | Number:Power | Power to/from the charger phase 2. [W] |
| charger-output-power-phase3 | Number:Power | Power to/from the charger phase 3. [W] |
| charger-total-output-power | Number:Power | Power from the charger on all phases. [W] |
| charger-current-phase1 | Number:ElectricCurrent | Current from the charger phase 1. [A] |
| charger-current-phase2 | Number:ElectricCurrent | Current from the charger phase 2. [A] |
| charger-current-phase3 | Number:ElectricCurrent | Current from the charger phase 3. [A] |
| charger-voltage-phase1 | Number:ElectricPotential | Voltage of the charger's phase 1. [V] |
| charger-voltage-phase2 | Number:ElectricPotential | Voltage of the charger's phase 2. [V] |
| charger-voltage-phase3 | Number:ElectricPotential | Voltage of the charger's phase 3. [V] |
| charger-external-current-phase1 | Number:ElectricCurrent | Current from the provider phase 1. [A] |
| charger-external-current-phase2 | Number:ElectricCurrent | Current from the provider phase 2. [A] |
| charger-external-current-phase3 | Number:ElectricCurrent | Current from the provider phase 3. [A] |
| charger-external-power-phase1 | Number:Power | Power from the provider phase 1. [W] |
| charger-external-power-phase2 | Number:Power | Power from the provider phase 2. [W] |
| charger-external-power-phase3 | Number:Power | Power from the provider phase 3. [W] |
| charger-external-total-power | Number:Power | Total power from the provider. [W] |
| charger-plug-temperature | Number:Temperature | Temperature of the charger's plug. [°C] |
| charger-internal-temperature | Number:Temperature | Internal temperature on the board of the charger. [°C] |
| charger-mode | String | Charger Workmode. |
| charger-state | String | Charger State. |
| last-update-time | DateTime | Last time with a successful retrieval of data. |
### Cloud Connect Inverter Configuration
| Parameter | Description |

View File

@ -28,16 +28,19 @@ public class SolaxBindingConstants {
public static final String BINDING_ID = "solax";
private static final String THING_LOCAL_CONNECT_INVERTER_ID = "local-connect-inverter";
private static final String THING_LOCAL_CONNECT_CHARGER_ID = "local-connect-charger";
private static final String THING_CLOUD_CONNECT_INVERTER_ID = "cloud-connect-inverter";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_LOCAL_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID,
THING_LOCAL_CONNECT_INVERTER_ID);
public static final ThingTypeUID THING_TYPE_LOCAL_CONNECT_CHARGER = new ThingTypeUID(BINDING_ID,
THING_LOCAL_CONNECT_CHARGER_ID);
public static final ThingTypeUID THING_TYPE_CLOUD_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID,
THING_CLOUD_CONNECT_INVERTER_ID);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER,
THING_TYPE_CLOUD_CONNECT_INVERTER);
THING_TYPE_LOCAL_CONNECT_CHARGER, THING_TYPE_CLOUD_CONNECT_INVERTER);
// List of properties
public static final String PROPERTY_INVERTER_TYPE = "inverterType";
@ -116,6 +119,32 @@ public class SolaxBindingConstants {
public static final String CHANNEL_INVERTER_EPS_POWER_S = "inverter-eps-power-s";
public static final String CHANNEL_INVERTER_EPS_POWER_T = "inverter-eps-power-t";
// EV Charger channels
public static final String CHANNEL_CHARGER_MODE = "charger-mode";
public static final String CHANNEL_CHARGER_STATE = "charger-state";
public static final String CHANNEL_CHARGER_EQ_SINGLE_SESSION = "eq-single-session";
public static final String CHANNEL_CHARGER_EQ_TOTAL = "eq-total";
public static final String CHANNEL_CHARGER_OUTPUT_POWER_PHASE1 = "charger-output-power-phase1";
public static final String CHANNEL_CHARGER_OUTPUT_POWER_PHASE2 = "charger-output-power-phase2";
public static final String CHANNEL_CHARGER_OUTPUT_POWER_PHASE3 = "charger-output-power-phase3";
public static final String CHANNEL_CHARGER_TOTAL_OUTPUT_POWER = "charger-total-output-power";
public static final String CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE1 = "charger-current-phase1";
public static final String CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE2 = "charger-current-phase2";
public static final String CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE3 = "charger-current-phase3";
public static final String CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE1 = "charger-voltage-phase1";
public static final String CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE2 = "charger-voltage-phase2";
public static final String CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE3 = "charger-voltage-phase3";
public static final String CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE1 = "charger-external-current-phase1";
public static final String CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE2 = "charger-external-current-phase2";
public static final String CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE3 = "charger-external-current-phase3";
public static final String CHANNEL_CHARGER_EXTERNAL_POWER_PHASE1 = "charger-external-power-phase1";
public static final String CHANNEL_CHARGER_EXTERNAL_POWER_PHASE2 = "charger-external-power-phase2";
public static final String CHANNEL_CHARGER_EXTERNAL_POWER_PHASE3 = "charger-external-power-phase3";
public static final String CHANNEL_CHARGER_TOTAL_EXTERNAL_POWER = "charger-external-total-power";
public static final String CHANNEL_CHARGER_PLUG_TEMPERATURE = "charger-plug-temperature";
public static final String CHANNEL_CHARGER_INTERNAL_TEMPERATURE = "charger-internal-temperature";
// I18N Keys
public static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved";
public static final String I18N_KEY_OFFLINE_CONFIGURATION_ERROR_JSON_CANNOT_BE_PARSED = "@text/offline.configuration-error.json-cannot-be-parsed";
}

View File

@ -17,7 +17,8 @@ import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.handlers.SolaxCloudHandler;
import org.openhab.binding.solax.internal.handlers.SolaxLocalAccessHandler;
import org.openhab.binding.solax.internal.handlers.SolaxLocalAccessChargerHandler;
import org.openhab.binding.solax.internal.handlers.SolaxLocalAccessInverterHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
@ -61,9 +62,11 @@ public class SolaxHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_LOCAL_CONNECT_INVERTER.equals(thingTypeUID)) {
return new SolaxLocalAccessHandler(thing, i18nProvider, timeZoneProvider);
return new SolaxLocalAccessInverterHandler(thing, i18nProvider, timeZoneProvider);
} else if (THING_TYPE_CLOUD_CONNECT_INVERTER.equals(thingTypeUID)) {
return new SolaxCloudHandler(thing, i18nProvider, timeZoneProvider, localeProvider);
} else if (THING_TYPE_LOCAL_CONNECT_CHARGER.equals(thingTypeUID)) {
return new SolaxLocalAccessChargerHandler(thing, i18nProvider, timeZoneProvider);
}
return null;
}

View File

@ -30,6 +30,7 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class LocalConnectRawDataBean implements RawDataBean {
@SerializedName(value = "sn", alternate = { "SN" })
private @Nullable String sn;
private @Nullable String ver;
private int type;
@ -38,6 +39,10 @@ public class LocalConnectRawDataBean implements RawDataBean {
@SerializedName("Information")
private String @Nullable [] information;
private @Nullable String rawData;
@SerializedName("OCPPServer")
private @Nullable String ocppServer;
@SerializedName("OCPPChargerId")
private @Nullable String ocppChargerId;
@Override
public String toString() {
@ -95,6 +100,22 @@ public class LocalConnectRawDataBean implements RawDataBean {
this.rawData = rawData;
}
public @Nullable String getOcppServer() {
return ocppServer;
}
public void setOcppServer(@Nullable String ocppServer) {
this.ocppServer = ocppServer;
}
public @Nullable String getOcppChargerId() {
return ocppChargerId;
}
public void setOcppChargerId(String ocppChargerId) {
this.ocppChargerId = ocppChargerId;
}
public static LocalConnectRawDataBean fromJson(String json) {
if (json.isEmpty()) {
throw new IllegalArgumentException("JSON payload should not be empty");

View File

@ -36,6 +36,8 @@ import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonSyntaxException;
/**
* The {@link SolaxCloudHandler} is responsible for handling commands, which are
* sent to one of the channels.
@ -99,6 +101,10 @@ public abstract class AbstractSolaxHandler extends BaseThingHandler {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (SolaxUpdateException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (JsonSyntaxException jse) {
logger.debug("JsonSyntaxException received.", jse);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
SolaxBindingConstants.I18N_KEY_OFFLINE_CONFIGURATION_ERROR_JSON_CANNOT_BE_PARSED);
} finally {
retrieveDataCallLock.unlock();
}
@ -116,6 +122,14 @@ public abstract class AbstractSolaxHandler extends BaseThingHandler {
}
}
@Override
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
if (status == ThingStatus.OFFLINE && statusDetail == ThingStatusDetail.CONFIGURATION_ERROR) {
cancelSchedule();
}
}
@Override
public void dispose() {
super.dispose();

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.handlers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.SolaxConfiguration;
import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
import org.openhab.binding.solax.internal.connectivity.SolaxConnector;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SolaxLocalAccessAbstractHandler} abstract handler combining the common logic for an inverter and a charger
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public abstract class SolaxLocalAccessAbstractHandler extends AbstractSolaxHandler {
private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessAbstractHandler.class);
protected final Set<String> unsupportedExistingChannels = new HashSet<>();
protected boolean alreadyRemovedUnsupportedChannels;
public SolaxLocalAccessAbstractHandler(Thing thing, TranslationProvider i18nProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, timeZoneProvider);
}
@Override
protected SolaxConnector createConnector(SolaxConfiguration config) {
return new LocalHttpConnector(config.password, config.hostname);
}
protected LocalConnectRawDataBean parseJson(String rawJsonData) {
LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
return fromJson;
}
protected void removeUnsupportedChannels(Set<String> supportedChannels) {
if (supportedChannels.isEmpty()) {
return;
}
List<Channel> channels = getThing().getChannels();
List<Channel> channelsToRemove = channels.stream()
.filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
if (!channelsToRemove.isEmpty()) {
if (logger.isDebugEnabled()) {
logRemovedChannels(channelsToRemove);
}
updateThing(editThing().withoutChannels(channelsToRemove).build());
}
}
private void logRemovedChannels(List<Channel> channelsToRemove) {
List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
.toList();
logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
channelsToRemoveForLog);
}
protected <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
Set<String> supportedChannels) {
if (supportedChannels.contains(channelID)) {
if (value > Short.MIN_VALUE) {
updateState(channelID, new QuantityType<>(value, unit));
} else if (!unsupportedExistingChannels.contains(channelID)) {
updateState(channelID, UnDefType.UNDEF);
unsupportedExistingChannels.add(channelID);
logger.warn(
"Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
channelID, value);
}
}
}
}

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.handlers;
import java.time.ZonedDateTime;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.SolaxBindingConstants;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.exceptions.SolaxUpdateException;
import org.openhab.binding.solax.internal.model.local.EvChargerData;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.evchargers.EvChargerDataParser;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;
/**
* The {@link SolaxLocalAccessChargerHandler} the handler for the charger
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class SolaxLocalAccessChargerHandler extends SolaxLocalAccessAbstractHandler {
private static final EvChargerDataParser parser = new EvChargerDataParser();
public SolaxLocalAccessChargerHandler(Thing thing, TranslationProvider i18nProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, timeZoneProvider);
}
@Override
public void initialize() {
removeUnsupportedChannels(parser.getSupportedChannels());
super.initialize();
}
@Override
protected void updateFromData(String rawJsonData) throws SolaxUpdateException {
LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
EvChargerData data = parser.getData(rawDataBean);
updateChannels(parser, data);
updateProperties(data);
}
private void updateProperties(LocalData data) {
}
private void updateChannels(RawDataParser parser, EvChargerData data) {
Set<String> supportedChannels = parser.getSupportedChannels();
// States/modes
updateState(SolaxBindingConstants.CHANNEL_CHARGER_MODE, new StringType(data.getDeviceMode()));
updateState(SolaxBindingConstants.CHANNEL_CHARGER_STATE, new StringType(data.getDeviceState()));
// Energy
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EQ_SINGLE_SESSION, data.getEqSingle(), Units.KILOWATT_HOUR,
supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EQ_TOTAL, data.getEqTotal(), Units.KILOWATT_HOUR,
supportedChannels);
// Output data
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE1, data.getCurrentPhase1(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE2, data.getCurrentPhase2(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE3, data.getCurrentPhase3(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE1, data.getVoltagePhase1(), Units.VOLT,
supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE2, data.getVoltagePhase2(), Units.VOLT,
supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE3, data.getVoltagePhase3(), Units.VOLT,
supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_POWER_PHASE1, data.getOutputPowerPhase1(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_POWER_PHASE2, data.getOutputPowerPhase2(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_OUTPUT_POWER_PHASE3, data.getOutputPowerPhase3(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_TOTAL_OUTPUT_POWER, data.getTotalChargePower(), Units.WATT,
supportedChannels);
// Provider data
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE1, data.getExternalCurrentPhase1(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE2, data.getExternalCurrentPhase2(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE3, data.getExternalCurrentPhase3(),
Units.AMPERE, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_POWER_PHASE1, data.getExternalPowerPhase1(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_POWER_PHASE2, data.getExternalPowerPhase2(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_EXTERNAL_POWER_PHASE3, data.getExternalPowerPhase3(),
Units.WATT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_TOTAL_EXTERNAL_POWER, data.getExternalTotalPower(),
Units.WATT, supportedChannels);
// Temperatures
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_PLUG_TEMPERATURE, data.getPlugTemperature(),
SIUnits.CELSIUS, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_CHARGER_INTERNAL_TEMPERATURE, data.getInternalTemperature(),
SIUnits.CELSIUS, supportedChannels);
// Binding provided data
updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
}
}

View File

@ -13,63 +13,43 @@
package org.openhab.binding.solax.internal.handlers;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.SolaxBindingConstants;
import org.openhab.binding.solax.internal.SolaxConfiguration;
import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
import org.openhab.binding.solax.internal.connectivity.SolaxConnector;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonParseException;
/**
* The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
* sent to one of the channels.
* The {@link SolaxLocalAccessInverterHandler} the handler for the inverter
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
public class SolaxLocalAccessInverterHandler extends SolaxLocalAccessAbstractHandler {
private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessInverterHandler.class);
private boolean alreadyRemovedUnsupportedChannels;
private final Set<String> unsupportedExistingChannels = new HashSet<>();
public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
public SolaxLocalAccessInverterHandler(Thing thing, TranslationProvider i18nProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, timeZoneProvider);
}
@Override
protected SolaxConnector createConnector(SolaxConfiguration config) {
return new LocalHttpConnector(config.password, config.hostname);
}
@Override
protected void updateFromData(String rawJsonData) {
try {
@ -82,7 +62,7 @@ public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
alreadyRemovedUnsupportedChannels = true;
}
LocalInverterData genericInverterData = parser.getData(rawDataBean);
LocalData genericInverterData = parser.getData(rawDataBean);
updateChannels(parser, genericInverterData);
updateProperties(genericInverterData);
} else {
@ -96,23 +76,17 @@ public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
}
}
private LocalConnectRawDataBean parseJson(String rawJsonData) {
LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
return fromJson;
}
private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
int type = rawDataBean.getType();
return InverterType.fromIndex(type);
}
private void updateProperties(LocalInverterData genericInverterData) {
private void updateProperties(LocalData genericInverterData) {
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
}
private void updateChannels(RawDataParser parser, LocalInverterData inverterData) {
private void updateChannels(RawDataParser parser, LocalData inverterData) {
updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
Set<String> supportedChannels = parser.getSupportedChannels();
@ -222,42 +196,4 @@ public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
// Binding provided data
updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
}
private void removeUnsupportedChannels(Set<String> supportedChannels) {
if (supportedChannels.isEmpty()) {
return;
}
List<Channel> channels = getThing().getChannels();
List<Channel> channelsToRemove = channels.stream()
.filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
if (!channelsToRemove.isEmpty()) {
if (logger.isDebugEnabled()) {
logRemovedChannels(channelsToRemove);
}
updateThing(editThing().withoutChannels(channelsToRemove).build());
}
}
private void logRemovedChannels(List<Channel> channelsToRemove) {
List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
.toList();
logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
channelsToRemoveForLog);
}
private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
Set<String> supportedChannels) {
if (supportedChannels.contains(channelID)) {
if (value > Short.MIN_VALUE) {
updateState(channelID, new QuantityType<>(value, unit));
} else if (!unsupportedExistingChannels.contains(channelID)) {
updateState(channelID, UnDefType.UNDEF);
unsupportedExistingChannels.add(channelID);
logger.warn(
"Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
channelID, value);
}
}
}
}

View File

@ -19,11 +19,11 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X1BoostAirMiniDataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X1HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X3HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X3MicOrProG2DataParser;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.inverters.X1BoostAirMiniDataParser;
import org.openhab.binding.solax.internal.model.local.inverters.X1HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.inverters.X3HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.inverters.X3MicOrProG2DataParser;
/**
* The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from

View File

@ -20,19 +20,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CommonLocalInverterData} is an abstract class that contains the common information, applicable for all
* inverters.
* The {@link CommonLocalDeviceData} is an abstract class that contains the common information, applicable for all
* devices.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public abstract class CommonLocalInverterData implements LocalInverterData {
public abstract class CommonLocalDeviceData implements LocalData {
private final Logger logger = LoggerFactory.getLogger(CommonLocalInverterData.class);
private final Logger logger = LoggerFactory.getLogger(CommonLocalDeviceData.class);
private LocalConnectRawDataBean data;
public CommonLocalInverterData(LocalConnectRawDataBean data) {
public CommonLocalDeviceData(LocalConnectRawDataBean data) {
this.data = data;
}
@ -56,7 +56,7 @@ public abstract class CommonLocalInverterData implements LocalInverterData {
return InverterType.fromIndex(data.getType());
}
protected short getData(int index) {
protected Short getFromRawData(int index) {
try {
short[] dataArray = data.getData();
if (dataArray != null) {
@ -69,12 +69,17 @@ public abstract class CommonLocalInverterData implements LocalInverterData {
}
public long packU16(int indexMajor, int indexMinor) {
short major = getData(indexMajor);
short minor = getData(indexMinor);
short major = getFromRawData(indexMajor);
short minor = getFromRawData(indexMinor);
if (major == 0) {
return minor;
}
return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF);
}
@Override
public LocalConnectRawDataBean getData() {
return data;
}
}

View File

@ -0,0 +1,161 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.util.ByteUtil;
/**
* The {@link EvChargerData} is the data representation of the EV charger, retrieved from the raw data array.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class EvChargerData extends CommonLocalDeviceData {
public EvChargerData(LocalConnectRawDataBean bean) {
super(bean);
}
@Override
public InverterType getInverterType() {
return InverterType.UNKNOWN;
}
public String getDeviceState() {
return String.valueOf(getFromRawData(0));
}
public String getDeviceMode() {
return String.valueOf(getFromRawData(1));
}
public double getEqSingle() {
return getFromRawData(12).doubleValue() / 10;
}
public double getEqTotal() {
return ((double) ByteUtil.read32BitSigned(getFromRawData(14), getFromRawData(15))) / 10;
}
public short getTotalChargePower() {
return getFromRawData(11);
}
@Override
public double getVoltagePhase1() {
return getFromRawData(2).doubleValue() / 100;
}
@Override
public double getVoltagePhase2() {
return getFromRawData(3).doubleValue() / 100;
}
@Override
public double getVoltagePhase3() {
return getFromRawData(4).doubleValue() / 100;
}
@Override
public double getCurrentPhase1() {
return getFromRawData(5).doubleValue() / 100;
}
@Override
public double getCurrentPhase2() {
return getFromRawData(6).doubleValue() / 100;
}
@Override
public double getCurrentPhase3() {
return getFromRawData(7).doubleValue() / 100;
}
@Override
public short getOutputPowerPhase1() {
return getFromRawData(8);
}
@Override
public short getOutputPowerPhase2() {
return getFromRawData(9);
}
@Override
public short getOutputPowerPhase3() {
return getFromRawData(10);
}
public double getExternalCurrentPhase1() {
return getFromRawData(16).doubleValue() / 100;
}
public double getExternalCurrentPhase2() {
return getFromRawData(17).doubleValue() / 100;
}
public double getExternalCurrentPhase3() {
return getFromRawData(18).doubleValue() / 100;
}
public double getExternalPowerPhase1() {
return getFromRawData(19).intValue();
}
public double getExternalPowerPhase2() {
return getFromRawData(20).intValue();
}
public double getExternalPowerPhase3() {
return getFromRawData(21).intValue();
}
public double getExternalTotalPower() {
return getFromRawData(22).intValue();
}
public short getPlugTemperature() {
return getFromRawData(23);
}
public short getInternalTemperature() {
return getFromRawData(24);
}
public short getCPState() {
return getFromRawData(26);
}
public int getChargingDuration() {
return ByteUtil.read32BitSigned(getFromRawData(80), getFromRawData(81));
}
public short getOccpOfflineMode() {
return getFromRawData(85);
}
public short getTypePower() {
return getFromRawData(87);
}
public short getTypePhase() {
return getFromRawData(88);
}
public short getTypeCharger() {
return getFromRawData(89);
}
}

View File

@ -14,26 +14,35 @@ package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
/**
* The {@link LocalInverterData} Interface for the parsed inverter data in meaningful format
* The {@link LocalData} Interface for the parsed inverter data in meaningful format
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public interface LocalInverterData {
public interface LocalData {
@Nullable
String getWifiSerial();
default String getWifiSerial() {
return getData().getSn();
}
@Nullable
String getWifiVersion();
default String getWifiVersion() {
return getData().getVer();
}
InverterType getInverterType();
@Nullable
String getRawData();
default String getRawData() {
return getData().getRawData();
}
LocalConnectRawDataBean getData();
default double getPV1Voltage() {
return Short.MIN_VALUE;

View File

@ -10,13 +10,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.local.parsers;
package org.openhab.binding.solax.internal.model.local;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
/**
* The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which
@ -27,7 +26,7 @@ import org.openhab.binding.solax.internal.model.local.LocalInverterData;
@NonNullByDefault
public interface RawDataParser {
LocalInverterData getData(LocalConnectRawDataBean bean);
LocalData getData(LocalConnectRawDataBean bean);
Set<String> getSupportedChannels();
}

View File

@ -16,80 +16,80 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X1BoostAirMiniInverterData} is an implementation of the single phased inverter data interface for X1 Mini
* The {@link X1BoostAirMiniData} is an implementation of the single phased inverter data interface for X1 Mini
* / X1 Air Mini or X1 Boost Mini inverter.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class X1BoostAirMiniInverterData extends CommonLocalInverterData {
public class X1BoostAirMiniData extends CommonLocalDeviceData {
public X1BoostAirMiniInverterData(LocalConnectRawDataBean data) {
public X1BoostAirMiniData(LocalConnectRawDataBean data) {
super(data);
}
@Override
public double getInverterVoltage() {
return (double) getData(0) / 10;
return (double) getFromRawData(0) / 10;
}
@Override
public double getInverterCurrent() {
return (double) getData(1) / 10;
return (double) getFromRawData(1) / 10;
}
@Override
public short getInverterOutputPower() {
return getData(2);
return getFromRawData(2);
}
@Override
public double getPV1Voltage() {
return (double) getData(3) / 10;
return (double) getFromRawData(3) / 10;
}
@Override
public double getPV2Voltage() {
return (double) getData(4) / 10;
return (double) getFromRawData(4) / 10;
}
@Override
public double getPV1Current() {
return (double) getData(5) / 10;
return (double) getFromRawData(5) / 10;
}
@Override
public double getPV2Current() {
return (double) getData(6) / 10;
return (double) getFromRawData(6) / 10;
}
@Override
public short getPV1Power() {
return getData(7);
return getFromRawData(7);
}
@Override
public short getPV2Power() {
return getData(8);
return getFromRawData(8);
}
@Override
public double getInverterFrequency() {
return (double) getData(9) / 100;
return (double) getFromRawData(9) / 100;
}
@Override
public double getTotalEnergy() {
return (double) getData(11) / 10;
return (double) getFromRawData(11) / 10;
}
@Override
public double getTodayEnergy() {
return (double) getData(13) / 10;
return (double) getFromRawData(13) / 10;
}
@Override
public short getPowerUsage() {
return (short) Math.round((double) getData(43) / 10);
return (short) Math.round((double) getFromRawData(43) / 10);
}
}

View File

@ -16,100 +16,100 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4
* The {@link X1HybridG4Data} is an implementation of the single phased inverter data interface for X1 Hybrid G4
* inverter.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class X1HybridG4InverterData extends CommonLocalInverterData {
public class X1HybridG4Data extends CommonLocalDeviceData {
public X1HybridG4InverterData(LocalConnectRawDataBean data) {
public X1HybridG4Data(LocalConnectRawDataBean data) {
super(data);
}
@Override
public double getInverterVoltage() {
return ((double) getData(0)) / 10;
return ((double) getFromRawData(0)) / 10;
}
@Override
public double getInverterCurrent() {
return ((double) getData(1)) / 10;
return ((double) getFromRawData(1)) / 10;
}
@Override
public short getInverterOutputPower() {
return getData(2);
return getFromRawData(2);
}
@Override
public double getInverterFrequency() {
return ((double) getData(3)) / 100;
return ((double) getFromRawData(3)) / 100;
}
@Override
public short getFeedInPower() {
return getData(32);
return getFromRawData(32);
}
@Override
public double getPV1Voltage() {
return ((double) getData(4)) / 10;
return ((double) getFromRawData(4)) / 10;
}
@Override
public double getPV2Voltage() {
return ((double) getData(5)) / 10;
return ((double) getFromRawData(5)) / 10;
}
@Override
public double getPV1Current() {
return ((double) getData(6)) / 10;
return ((double) getFromRawData(6)) / 10;
}
@Override
public double getPV2Current() {
return ((double) getData(7)) / 10;
return ((double) getFromRawData(7)) / 10;
}
@Override
public short getPV1Power() {
return getData(8);
return getFromRawData(8);
}
@Override
public short getPV2Power() {
return getData(9);
return getFromRawData(9);
}
@Override
public double getBatteryVoltage() {
return ((double) getData(14)) / 100;
return ((double) getFromRawData(14)) / 100;
}
@Override
public double getBatteryCurrent() {
return ((double) getData(15)) / 100;
return ((double) getFromRawData(15)) / 100;
}
@Override
public short getBatteryPower() {
return getData(16);
return getFromRawData(16);
}
@Override
public short getBatteryTemperature() {
return getData(17);
return getFromRawData(17);
}
@Override
public short getBatteryLevel() {
return getData(18);
return getFromRawData(18);
}
@Override
public short getInverterWorkModeCode() {
return getData(10);
return getFromRawData(10);
}
}

View File

@ -16,15 +16,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X3HybridG4InverterData} is responsible for handling commands, which are
* sent to one of the channels.
* The {@link X3HybridG4Data} is an implementation of the single phased inverter data interface for X3 Hybrid G4
* inverter.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class X3HybridG4InverterData extends CommonLocalInverterData {
public class X3HybridG4Data extends CommonLocalDeviceData {
public X3HybridG4InverterData(LocalConnectRawDataBean data) {
public X3HybridG4Data(LocalConnectRawDataBean data) {
super(data);
}
@ -32,197 +32,197 @@ public class X3HybridG4InverterData extends CommonLocalInverterData {
@Override
public double getVoltagePhase1() {
return ((double) getData(0)) / 10;
return ((double) getFromRawData(0)) / 10;
}
@Override
public double getVoltagePhase2() {
return ((double) getData(1)) / 10;
return ((double) getFromRawData(1)) / 10;
}
@Override
public double getVoltagePhase3() {
return ((double) getData(2)) / 10;
return ((double) getFromRawData(2)) / 10;
}
@Override
public double getCurrentPhase1() {
return ((double) getData(3)) / 10;
return ((double) getFromRawData(3)) / 10;
}
@Override
public double getCurrentPhase2() {
return ((double) getData(4)) / 10;
return ((double) getFromRawData(4)) / 10;
}
@Override
public double getCurrentPhase3() {
return ((double) getData(5)) / 10;
return ((double) getFromRawData(5)) / 10;
}
@Override
public short getOutputPowerPhase1() {
return getData(6);
return getFromRawData(6);
}
@Override
public short getOutputPowerPhase2() {
return getData(7);
return getFromRawData(7);
}
@Override
public short getOutputPowerPhase3() {
return getData(8);
return getFromRawData(8);
}
@Override
public short getTotalOutputPower() {
return getData(9);
return getFromRawData(9);
}
@Override
public double getPV1Voltage() {
return ((double) getData(10)) / 10;
return ((double) getFromRawData(10)) / 10;
}
@Override
public double getPV2Voltage() {
return ((double) getData(11)) / 10;
return ((double) getFromRawData(11)) / 10;
}
@Override
public double getPV1Current() {
return ((double) getData(12)) / 10;
return ((double) getFromRawData(12)) / 10;
}
@Override
public double getPV2Current() {
return ((double) getData(13)) / 10;
return ((double) getFromRawData(13)) / 10;
}
@Override
public short getPV1Power() {
return getData(14);
return getFromRawData(14);
}
@Override
public short getPV2Power() {
return getData(15);
return getFromRawData(15);
}
@Override
public double getFrequencyPhase1() {
return ((double) getData(16)) / 100;
return ((double) getFromRawData(16)) / 100;
}
@Override
public double getFrequencyPhase2() {
return ((double) getData(17)) / 100;
return ((double) getFromRawData(17)) / 100;
}
@Override
public double getFrequencyPhase3() {
return ((double) getData(18)) / 100;
return ((double) getFromRawData(18)) / 100;
}
// Battery
@Override
public double getBatteryVoltage() {
return ((double) getData(39)) / 100;
return ((double) getFromRawData(39)) / 100;
}
@Override
public double getBatteryCurrent() {
return ((double) getData(40)) / 100;
return ((double) getFromRawData(40)) / 100;
}
@Override
public short getBatteryPower() {
return getData(41);
return getFromRawData(41);
}
@Override
public short getBatteryTemperature() {
return getData(105);
return getFromRawData(105);
}
@Override
public short getBatteryLevel() {
return getData(103);
return getFromRawData(103);
}
// Feed in power
@Override
public short getFeedInPower() {
return (short) (getData(34) - getData(35));
return (short) (getFromRawData(34) - getFromRawData(35));
}
// Totals
@Override
public short getPowerUsage() {
return getData(47);
return getFromRawData(47);
}
@Override
public double getTotalEnergy() {
return ((double) getData(68)) / 10;
return ((double) getFromRawData(68)) / 10;
}
@Override
public short getTotalBatteryDischargeEnergy() {
return getData(74);
return getFromRawData(74);
}
@Override
public short getTotalBatteryChargeEnergy() {
return getData(76);
return getFromRawData(76);
}
@Override
public double getTotalPVEnergy() {
return ((double) getData(80)) / 10;
return ((double) getFromRawData(80)) / 10;
}
@Override
public short getTotalFeedInEnergy() {
return getData(86);
return getFromRawData(86);
}
@Override
public double getTotalConsumption() {
return ((double) getData(88)) / 10;
return ((double) getFromRawData(88)) / 10;
}
@Override
public double getTodayEnergy() {
return ((double) getData(82)) / 10;
return ((double) getFromRawData(82)) / 10;
}
@Override
public double getTodayFeedInEnergy() {
return ((double) getData(90)) / 100;
return ((double) getFromRawData(90)) / 100;
}
@Override
public double getTodayConsumption() {
return ((double) getData(92)) / 100;
return ((double) getFromRawData(92)) / 100;
}
@Override
public double getTodayBatteryDischargeEnergy() {
return ((double) getData(78)) / 10;
return ((double) getFromRawData(78)) / 10;
}
@Override
public double getTodayBatteryChargeEnergy() {
return ((double) getData(79)) / 10;
return ((double) getFromRawData(79)) / 10;
}
@Override
public short getInverterWorkModeCode() {
return getData(19);
return getFromRawData(19);
}
}

View File

@ -16,16 +16,16 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X3HybridG4InverterData} is responsible for handling commands, which are
* The {@link X3HybridG4Data} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Henrik Tóth - Initial contribution
* (based on X1/X3 G4 parser from Konstantin Polihronov)
*/
@NonNullByDefault
public class X3MicOrProG2InverterData extends CommonLocalInverterData {
public class X3MicOrProG2Data extends CommonLocalDeviceData {
public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
public X3MicOrProG2Data(LocalConnectRawDataBean data) {
super(data);
}
@ -33,121 +33,121 @@ public class X3MicOrProG2InverterData extends CommonLocalInverterData {
@Override
public double getVoltagePhase1() {
return ((double) getData(0)) / 10;
return ((double) getFromRawData(0)) / 10;
}
@Override
public double getVoltagePhase2() {
return ((double) getData(1)) / 10;
return ((double) getFromRawData(1)) / 10;
}
@Override
public double getVoltagePhase3() {
return ((double) getData(2)) / 10;
return ((double) getFromRawData(2)) / 10;
}
@Override
public double getCurrentPhase1() {
return ((double) getData(3)) / 10;
return ((double) getFromRawData(3)) / 10;
}
@Override
public double getCurrentPhase2() {
return ((double) getData(4)) / 10;
return ((double) getFromRawData(4)) / 10;
}
@Override
public double getCurrentPhase3() {
return ((double) getData(5)) / 10;
return ((double) getFromRawData(5)) / 10;
}
@Override
public short getOutputPowerPhase1() {
return getData(6);
return getFromRawData(6);
}
@Override
public short getOutputPowerPhase2() {
return getData(7);
return getFromRawData(7);
}
@Override
public short getOutputPowerPhase3() {
return getData(8);
return getFromRawData(8);
}
@Override
public double getPV1Voltage() {
return ((double) getData(9)) / 10;
return ((double) getFromRawData(9)) / 10;
}
@Override
public double getPV2Voltage() {
return ((double) getData(10)) / 10;
return ((double) getFromRawData(10)) / 10;
}
@Override
public double getPV1Current() {
return ((double) getData(12)) / 10;
return ((double) getFromRawData(12)) / 10;
}
@Override
public double getPV2Current() {
return ((double) getData(13)) / 10;
return ((double) getFromRawData(13)) / 10;
}
@Override
public short getPV1Power() {
return getData(15);
return getFromRawData(15);
}
@Override
public short getPV2Power() {
return getData(16);
return getFromRawData(16);
}
@Override
public double getFrequencyPhase1() {
return ((double) getData(18)) / 100;
return ((double) getFromRawData(18)) / 100;
}
@Override
public double getFrequencyPhase2() {
return ((double) getData(19)) / 100;
return ((double) getFromRawData(19)) / 100;
}
@Override
public double getFrequencyPhase3() {
return ((double) getData(20)) / 100;
return ((double) getFromRawData(20)) / 100;
}
@Override
public short getInverterWorkModeCode() {
return getData(21);
return getFromRawData(21);
}
@Override
public double getTotalEnergy() {
return ((double) getData(22)) / 10;
return ((double) getFromRawData(22)) / 10;
}
@Override
public double getTodayEnergy() {
return ((double) getData(24)) / 10;
return ((double) getFromRawData(24)) / 10;
}
@Override
public short getInverterTemperature1() {
return getData(26);
return getFromRawData(26);
}
@Override
public short getInverterTemperature2() {
return getData(27);
return getFromRawData(27);
}
@Override
public short getTotalOutputPower() {
return getData(78);
return getFromRawData(78);
}
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.model.local.evchargers;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.EvChargerData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
/**
* The {@link EvChargerDataParser} is the implementation that parses raw data into a EvChargerData for the EV charger.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class EvChargerDataParser implements RawDataParser {
private static final Set<String> EV_CHARGER_SUPPORTED_CHANNELS = Set.of(CHANNEL_CHARGER_MODE, CHANNEL_CHARGER_STATE,
CHANNEL_CHARGER_EQ_SINGLE_SESSION, CHANNEL_CHARGER_EQ_TOTAL, CHANNEL_CHARGER_OUTPUT_POWER_PHASE1,
CHANNEL_CHARGER_OUTPUT_POWER_PHASE2, CHANNEL_CHARGER_OUTPUT_POWER_PHASE3,
CHANNEL_CHARGER_TOTAL_OUTPUT_POWER, CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE1,
CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE2, CHANNEL_CHARGER_OUTPUT_CURRENT_PHASE3,
CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE2,
CHANNEL_CHARGER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE1,
CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE2, CHANNEL_CHARGER_EXTERNAL_CURRENT_PHASE3,
CHANNEL_CHARGER_EXTERNAL_POWER_PHASE1, CHANNEL_CHARGER_EXTERNAL_POWER_PHASE2,
CHANNEL_CHARGER_EXTERNAL_POWER_PHASE3, CHANNEL_CHARGER_TOTAL_EXTERNAL_POWER,
CHANNEL_CHARGER_PLUG_TEMPERATURE, CHANNEL_CHARGER_INTERNAL_TEMPERATURE);
@Override
public EvChargerData getData(LocalConnectRawDataBean bean) {
return new EvChargerData(bean);
}
@Override
public Set<String> getSupportedChannels() {
return EV_CHARGER_SUPPORTED_CHANNELS;
}
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.local.parsers;
package org.openhab.binding.solax.internal.model.local.inverters;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
@ -18,11 +18,12 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X1BoostAirMiniInverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.X1BoostAirMiniData;
/**
* The {@link X1BoostAirMiniDataParser} is the implementation that parses raw data into a LocalInverterData for the
* The {@link X1BoostAirMiniDataParser} is the implementation that parses raw data into a {@link LocalData} for the
* X1 Mini / X1 Air Mini or X1 Boost Mini inverter.
*
* @author Konstantin Polihronov - Initial contribution
@ -38,8 +39,8 @@ public class X1BoostAirMiniDataParser implements RawDataParser {
CHANNEL_TOTAL_ENERGY, CHANNEL_TODAY_ENERGY, CHANNEL_POWER_USAGE);
@Override
public LocalInverterData getData(LocalConnectRawDataBean bean) {
return new X1BoostAirMiniInverterData(bean);
public LocalData getData(LocalConnectRawDataBean bean) {
return new X1BoostAirMiniData(bean);
}
@Override

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.local.parsers;
package org.openhab.binding.solax.internal.model.local.inverters;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
@ -18,11 +18,12 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X1HybridG4InverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.X1HybridG4Data;
/**
* The {@link X1HybridG4DataParser} is the implementation that parses raw data into a LocalInverterData for the
* The {@link X1HybridG4DataParser} is the implementation that parses raw data into a {@link LocalData} for the
* X1 Hybrid G4 inverter.
*
* @author Konstantin Polihronov - Initial contribution
@ -39,8 +40,8 @@ public class X1HybridG4DataParser implements RawDataParser {
CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
@Override
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X1HybridG4InverterData(rawData);
public LocalData getData(LocalConnectRawDataBean rawData) {
return new X1HybridG4Data(rawData);
}
@Override

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.local.parsers;
package org.openhab.binding.solax.internal.model.local.inverters;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
@ -18,11 +18,12 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X3HybridG4InverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.X3HybridG4Data;
/**
* The {@link X3HybridG4DataParser} is the implementation that parses raw data into a LocalInverterData for the
* The {@link X3HybridG4DataParser} is the implementation that parses raw data into a {@link LocalData} for the
* X3 Hybrid G4 inverter.
*
* @author Konstantin Polihronov - Initial contribution
@ -47,8 +48,8 @@ public class X3HybridG4DataParser implements RawDataParser {
CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
@Override
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X3HybridG4InverterData(rawData);
public LocalData getData(LocalConnectRawDataBean rawData) {
return new X3HybridG4Data(rawData);
}
@Override

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.local.parsers;
package org.openhab.binding.solax.internal.model.local.inverters;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
@ -18,8 +18,9 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X3MicOrProG2InverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
import org.openhab.binding.solax.internal.model.local.X3MicOrProG2Data;
/**
* The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
@ -44,8 +45,8 @@ public class X3MicOrProG2DataParser implements RawDataParser {
CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
@Override
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X3MicOrProG2InverterData(rawData);
public LocalData getData(LocalConnectRawDataBean rawData) {
return new X3MicOrProG2Data(rawData);
}
@Override

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.util;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ByteUtil} Utility method for manipulating byte-level data
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class ByteUtil {
public static final int MASK = 0xFFFF;
public static final int SHIFT_VALUE = 16;
public static int read32BitSigned(short low, short high) {
return (high << SHIFT_VALUE) | (low & MASK);
}
}

View File

@ -23,6 +23,8 @@ thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.label = Me
thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.description = Inverter power on meter2.
thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power
thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter
thing-type.solax.cloud-connect-inverter.channel.inverter-workmode.label = Inverter Workmode
thing-type.solax.cloud-connect-inverter.channel.inverter-workmode.description = Inverter Workmode
thing-type.solax.cloud-connect-inverter.channel.pv-total-power.label = PV Total Power
thing-type.solax.cloud-connect-inverter.channel.pv-total-power.description = The sum of PV powers from all PV strings
thing-type.solax.cloud-connect-inverter.channel.pv1-power.label = PV 1 Power
@ -41,6 +43,54 @@ thing-type.solax.cloud-connect-inverter.channel.total-energy.label = Yield total
thing-type.solax.cloud-connect-inverter.channel.total-energy.description = Total inverter output energy
thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.label = Total Feed-In Energy
thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.description = Total energy feed-in to the electricity network.
thing-type.solax.local-connect-charger.label = Local Connect Charger
thing-type.solax.local-connect-charger.description = The charger representation that supports local connections via HTTP
thing-type.solax.local-connect-charger.channel.charger-current-phase1.label = Charger Output Current Phase 1
thing-type.solax.local-connect-charger.channel.charger-current-phase1.description = Current from the charger phase 1
thing-type.solax.local-connect-charger.channel.charger-current-phase2.label = Charger Output Current Phase 2
thing-type.solax.local-connect-charger.channel.charger-current-phase2.description = Current from the charger phase 2
thing-type.solax.local-connect-charger.channel.charger-current-phase3.label = Charger Output Current Phase 3
thing-type.solax.local-connect-charger.channel.charger-current-phase3.description = Current from the charger phase 3
thing-type.solax.local-connect-charger.channel.charger-external-current-phase1.label = Charger External Current Phase 1
thing-type.solax.local-connect-charger.channel.charger-external-current-phase1.description = Current from the provider phase 1
thing-type.solax.local-connect-charger.channel.charger-external-current-phase2.label = Charger External Current Phase 2
thing-type.solax.local-connect-charger.channel.charger-external-current-phase2.description = Current from the provider phase 2
thing-type.solax.local-connect-charger.channel.charger-external-current-phase3.label = Charger External Current Phase 3
thing-type.solax.local-connect-charger.channel.charger-external-current-phase3.description = Current from the provider phase 3
thing-type.solax.local-connect-charger.channel.charger-external-power-phase1.label = Charger External Power Phase 1
thing-type.solax.local-connect-charger.channel.charger-external-power-phase1.description = Power from the provider phase 1
thing-type.solax.local-connect-charger.channel.charger-external-power-phase2.label = Charger External Power Phase 2
thing-type.solax.local-connect-charger.channel.charger-external-power-phase2.description = Power from the provider phase 2
thing-type.solax.local-connect-charger.channel.charger-external-power-phase3.label = Charger External Power Phase 3
thing-type.solax.local-connect-charger.channel.charger-external-power-phase3.description = Power from the provider phase 3
thing-type.solax.local-connect-charger.channel.charger-external-total-power.label = Charger External Total Power
thing-type.solax.local-connect-charger.channel.charger-external-total-power.description = Total power from the provider
thing-type.solax.local-connect-charger.channel.charger-internal-temperature.label = Charger Internal Temperature
thing-type.solax.local-connect-charger.channel.charger-internal-temperature.description = Internal temperature on the board of the charger
thing-type.solax.local-connect-charger.channel.charger-mode.label = Charger Workmode
thing-type.solax.local-connect-charger.channel.charger-mode.description = Charger Workmode
thing-type.solax.local-connect-charger.channel.charger-output-power-phase1.label = Charger Output Power Phase 1
thing-type.solax.local-connect-charger.channel.charger-output-power-phase1.description = Power to/from the charger phase 1
thing-type.solax.local-connect-charger.channel.charger-output-power-phase2.label = Charger Output Power Phase 2
thing-type.solax.local-connect-charger.channel.charger-output-power-phase2.description = Power to/from the charger phase 2
thing-type.solax.local-connect-charger.channel.charger-output-power-phase3.label = Charger Output Power Phase 3
thing-type.solax.local-connect-charger.channel.charger-output-power-phase3.description = Power to/from the charger phase 3
thing-type.solax.local-connect-charger.channel.charger-plug-temperature.label = Charger Plug Temperature
thing-type.solax.local-connect-charger.channel.charger-plug-temperature.description = Temperature of the charger's plug
thing-type.solax.local-connect-charger.channel.charger-state.label = Charger State
thing-type.solax.local-connect-charger.channel.charger-state.description = Charger State
thing-type.solax.local-connect-charger.channel.charger-total-output-power.label = Charger Output Total Power
thing-type.solax.local-connect-charger.channel.charger-total-output-power.description = Power from the charger on all phases
thing-type.solax.local-connect-charger.channel.charger-voltage-phase1.label = Charger Voltage Phase 1
thing-type.solax.local-connect-charger.channel.charger-voltage-phase1.description = Voltage of the charger's phase 1
thing-type.solax.local-connect-charger.channel.charger-voltage-phase2.label = Charger Voltage Phase 2
thing-type.solax.local-connect-charger.channel.charger-voltage-phase2.description = Voltage of the charger's phase 2
thing-type.solax.local-connect-charger.channel.charger-voltage-phase3.label = Charger Voltage Phase 3
thing-type.solax.local-connect-charger.channel.charger-voltage-phase3.description = Voltage of the charger's phase 3
thing-type.solax.local-connect-charger.channel.eq-single-session.label = Energy charged this session
thing-type.solax.local-connect-charger.channel.eq-single-session.description = The energy charged for the current session
thing-type.solax.local-connect-charger.channel.eq-total.label = Total energy charged
thing-type.solax.local-connect-charger.channel.eq-total.description = The total energy charged for all sessions
thing-type.solax.local-connect-inverter.label = Local Connect Inverter
thing-type.solax.local-connect-inverter.description = The inverter representation that supports local connections via HTTP
thing-type.solax.local-connect-inverter.channel.battery-current.label = Battery Current
@ -142,6 +192,12 @@ thing-type.config.solax.cloud-connect-inverter.refreshInterval.label = Refresh I
thing-type.config.solax.cloud-connect-inverter.refreshInterval.description = Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day)
thing-type.config.solax.cloud-connect-inverter.token.label = Token
thing-type.config.solax.cloud-connect-inverter.token.description = Token to access the Solax cloud API
thing-type.config.solax.local-connect-charger.hostname.label = Network Address
thing-type.config.solax.local-connect-charger.hostname.description = IP address or the host name of the Wi-Fi module
thing-type.config.solax.local-connect-charger.password.label = Password
thing-type.config.solax.local-connect-charger.password.description = Password for accessing the Wi-Fi module (the serial number of the Wi-Fi module)
thing-type.config.solax.local-connect-charger.refreshInterval.label = Refresh Interval
thing-type.config.solax.local-connect-charger.refreshInterval.description = Specifies the refresh interval in seconds.
thing-type.config.solax.local-connect-inverter.hostname.label = Network Address
thing-type.config.solax.local-connect-inverter.hostname.description = IP address or the host name of the Wi-Fi module
thing-type.config.solax.local-connect-inverter.password.label = Password
@ -153,12 +209,45 @@ thing-type.config.solax.local-connect-inverter.refreshInterval.description = Spe
channel-type.solax.battery-temperature.label = Battery Temperature
channel-type.solax.battery-temperature.description = Battery Temperature
channel-type.solax.charger-mode.label = Charger Mode
channel-type.solax.charger-mode.description = Charger Mode
channel-type.solax.charger-mode.state.option.0 = Stop
channel-type.solax.charger-mode.state.option.1 = Fast
channel-type.solax.charger-mode.state.option.2 = Eco
channel-type.solax.charger-mode.state.option.3 = Green
channel-type.solax.charger-state.label = Charger State
channel-type.solax.charger-state.description = Charger State
channel-type.solax.charger-state.state.option.0 = Preparing
channel-type.solax.charger-state.state.option.1 = Preparing
channel-type.solax.charger-state.state.option.2 = Charging
channel-type.solax.charger-state.state.option.3 = Finishing
channel-type.solax.charger-state.state.option.4 = Fault
channel-type.solax.charger-state.state.option.5 = Unavailable
channel-type.solax.charger-state.state.option.6 = Reserved
channel-type.solax.charger-state.state.option.7 = Suspended EV
channel-type.solax.charger-state.state.option.8 = Suspended EV SE
channel-type.solax.charger-temperature.label = Charger Temperature
channel-type.solax.charger-temperature.description = Charger Temperature
channel-type.solax.frequency.label = Electric Frequency
channel-type.solax.frequency.description = Frequency of the electricity to/from the inverter
channel-type.solax.inverter-status-type.label = Inverter Status
channel-type.solax.inverter-status-type.description = The status of the inverter.
channel-type.solax.inverter-temperature.label = Inverter Temperature
channel-type.solax.inverter-temperature.description = Inverter Temperature
channel-type.solax.inverter-workmode-cloud.label = Inverter Workmode
channel-type.solax.inverter-workmode-cloud.description = Inverter Workmode
channel-type.solax.inverter-workmode-cloud.state.option.100 = Waiting
channel-type.solax.inverter-workmode-cloud.state.option.101 = Checking
channel-type.solax.inverter-workmode-cloud.state.option.102 = Normal
channel-type.solax.inverter-workmode-cloud.state.option.103 = Fault
channel-type.solax.inverter-workmode-cloud.state.option.104 = Permanent Fault
channel-type.solax.inverter-workmode-cloud.state.option.105 = Updating
channel-type.solax.inverter-workmode-cloud.state.option.106 = EPS Check
channel-type.solax.inverter-workmode-cloud.state.option.107 = EPS Normal
channel-type.solax.inverter-workmode-cloud.state.option.108 = Self Test
channel-type.solax.inverter-workmode-cloud.state.option.109 = Idle
channel-type.solax.inverter-workmode-cloud.state.option.110 = Standby
channel-type.solax.inverter-workmode-cloud.state.option.111 = PV Wake-up Battery
channel-type.solax.inverter-workmode-cloud.state.option.112 = GEN Check
channel-type.solax.inverter-workmode-cloud.state.option.113 = GEN Run
channel-type.solax.inverter-workmode.label = Inverter Workmode
channel-type.solax.inverter-workmode.description = Inverter Workmode
channel-type.solax.inverter-workmode.state.option.0 = Waiting
@ -180,6 +269,11 @@ channel-type.solax.last-retrieve-time-stamp.description = Last time with a succe
channel-type.solax.raw-data-type.label = Raw Data
channel-type.solax.raw-data-type.description = The raw JSON data retrieved from the inverter's Wi-Fi module.
# channel types
channel-type.solax.inverter-status-type.label = Inverter Status
channel-type.solax.inverter-status-type.description = The status of the inverter.
# thing status descriptions
cloud.inverter.status.wait = Wait
@ -198,3 +292,4 @@ cloud.inverter.status.gen-run = Gen Run
cloud.inverter.status.unknown = Unknown
offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved.
offline.configuration-error.parser-not-implemented = Parser for inverter of type {0} is not implemented.
offline.configuration-error.json-cannot-be-parsed = Json cannot be parsed. Check your connection configuration for a wrong password.

View File

@ -93,5 +93,46 @@
<description>The raw JSON data retrieved from the inverter's Wi-Fi module.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="charger-state">
<item-type>String</item-type>
<label>Charger State</label>
<description>Charger State</description>
<state pattern="%s" readOnly="true">
<options>
<option value="0">Preparing</option>
<option value="1">Preparing</option>
<option value="2">Charging</option>
<option value="3">Finishing</option>
<option value="4">Fault</option>
<option value="5">Unavailable</option>
<option value="6">Reserved</option>
<option value="7">Suspended EV</option>
<option value="8">Suspended EV SE</option>
</options>
</state>
</channel-type>
<channel-type id="charger-mode">
<item-type>String</item-type>
<label>Charger Mode</label>
<description>Charger Mode</description>
<state pattern="%s" readOnly="true">
<options>
<option value="0">Stop</option>
<option value="1">Fast</option>
<option value="2">Eco</option>
<option value="3">Green</option>
</options>
</state>
</channel-type>
<channel-type id="charger-temperature">
<item-type>Number:Temperature</item-type>
<label>Charger Temperature</label>
<description>Charger Temperature</description>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="solax"
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">
<thing-type id="local-connect-charger">
<label>Local Connect Charger</label>
<description>The charger representation that supports local connections via HTTP</description>
<channels>
<channel id="eq-single-session" typeId="system.electric-energy">
<label>Energy This Session</label>
<description>The energy charged for the current session</description>
</channel>
<channel id="eq-total" typeId="system.electric-energy">
<label>Total Energy Charged</label>
<description>The total energy charged for all sessions</description>
</channel>
<channel id="charger-output-power-phase1" typeId="system.electric-power">
<label>Output Power Phase1</label>
<description>Power to/from the charger phase 1</description>
</channel>
<channel id="charger-output-power-phase2" typeId="system.electric-power">
<label>Output Power Phase2</label>
<description>Power to/from the charger phase 2</description>
</channel>
<channel id="charger-output-power-phase3" typeId="system.electric-power">
<label>Output Power Phase3</label>
<description>Power to/from the charger phase 3</description>
</channel>
<channel id="charger-total-output-power" typeId="system.electric-power">
<label>Output Total Power</label>
<description>Power from the charger on all phases</description>
</channel>
<channel id="charger-current-phase1" typeId="system.electric-current">
<label>Output Current Phase1</label>
<description>Current from the charger phase 1</description>
</channel>
<channel id="charger-current-phase2" typeId="system.electric-current">
<label>Output Current Phase2</label>
<description>Current from the charger phase 2</description>
</channel>
<channel id="charger-current-phase3" typeId="system.electric-current">
<label>Output Current Phase3</label>
<description>Current from the charger phase 3</description>
</channel>
<channel id="charger-voltage-phase1" typeId="system.electric-voltage">
<label>Voltage Phase1</label>
<description>Voltage of the charger's phase 1</description>
</channel>
<channel id="charger-voltage-phase2" typeId="system.electric-voltage">
<label>Voltage Phase2</label>
<description>Voltage of the charger's phase 2</description>
</channel>
<channel id="charger-voltage-phase3" typeId="system.electric-voltage">
<label>Voltage Phase3</label>
<description>Voltage of the charger's phase 3</description>
</channel>
<channel id="charger-external-current-phase1" typeId="system.electric-current">
<label>External Current Phase1</label>
<description>Current from the provider phase 1</description>
</channel>
<channel id="charger-external-current-phase2" typeId="system.electric-current">
<label>External Current Phase2</label>
<description>Current from the provider phase 2</description>
</channel>
<channel id="charger-external-current-phase3" typeId="system.electric-current">
<label>External Current Phase3</label>
<description>Current from the provider phase 3</description>
</channel>
<channel id="charger-external-power-phase1" typeId="system.electric-power">
<label>External Power Phase1</label>
<description>Power from the provider phase 1</description>
</channel>
<channel id="charger-external-power-phase2" typeId="system.electric-power">
<label>External Power Phase2</label>
<description>Power from the provider phase 2</description>
</channel>
<channel id="charger-external-power-phase3" typeId="system.electric-power">
<label>External Power Phase3</label>
<description>Power from the provider phase 3</description>
</channel>
<channel id="charger-external-total-power" typeId="system.electric-power">
<label>External Total Power</label>
<description>Total power from the provider</description>
</channel>
<channel id="charger-plug-temperature" typeId="charger-temperature">
<label>Plug Temperature</label>
<description>Temperature of the charger's plug</description>
</channel>
<channel id="charger-internal-temperature" typeId="charger-temperature">
<label>Internal Temperature</label>
<description>Internal temperature on the board of the charger</description>
</channel>
<channel id="charger-mode" typeId="charger-mode">
<label>Workmode</label>
<description>Charger Workmode</description>
</channel>
<channel id="charger-state" typeId="charger-state">
<label>State</label>
<description>Charger State</description>
</channel>
<channel id="last-update-time" typeId="last-retrieve-time-stamp"/>
</channels>
<config-description>
<parameter name="refreshInterval" type="integer" min="1" max="600">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds.</description>
<default>10</default>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Password for accessing the Wi-Fi module (the serial number of the Wi-Fi module)</description>
<context>password</context>
</parameter>
<parameter name="hostname" type="text" required="true">
<label>Network Address</label>
<description>IP address or the host name of the Wi-Fi module</description>
<context>network-address</context>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@ -20,8 +20,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
/**
* The {@link AbstractParserTest} Abstract class defining the common logic for testing local connections to the various
@ -44,7 +44,7 @@ public abstract class AbstractParserTest {
Set<String> supportedChannels = parser.getSupportedChannels();
assertFalse(supportedChannels.isEmpty());
LocalInverterData data = parser.getData(bean);
LocalData data = parser.getData(bean);
assertParserSpecific(data);
}
@ -52,5 +52,5 @@ public abstract class AbstractParserTest {
protected abstract String getRawData();
protected abstract void assertParserSpecific(LocalInverterData data);
protected abstract void assertParserSpecific(LocalData data);
}

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.local.parsers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.EvChargerData;
/**
* The {@link TestEVChargerParser} Simple test that tests for proper parsing against a real data from the inverter
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class TestEVChargerParser {
private static final String RAW_DATA = """
{
"SN":"SQBLABLA",
"ver":"3.004.11",
"type":1,
"Data":[
2,2,23914,23991,23895,1517,1513,1519,3654,3657,
3656,10968,44,0,346,0,65434,35463,65459,65508,
65513,27,402,0,43,0,2,15,0,0,
0,0,0,5004,5000,4996,10518,1547,6150,4,
0,0,0,0,0,0,0,0,1,100,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
1717,0,3114,1547,6150,0,1,1,1,0,
0,121,584,266,0,50,0,0,1,1,0],
"Information":[11.000,1,"CXXXXXXXXXX",1,1.13,1.01,0.00,0.00,0.00,1],
"OCPPServer":"",
"OCPPChargerId":""
}
""";
@Test
public void testParser() {
LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA);
assertNotNull(bean);
EvChargerData data = new EvChargerData(bean);
assertEquals("SQBLABLA", data.getWifiSerial()); // 0
assertEquals("3.004.11", data.getWifiVersion()); // 1
assertEquals(239.14, data.getVoltagePhase1()); // 2
assertEquals(239.91, data.getVoltagePhase2()); // 3
assertEquals(238.95, data.getVoltagePhase3()); // 4
assertEquals(15.17, data.getCurrentPhase1()); // 5
assertEquals(15.13, data.getCurrentPhase2()); // 6
assertEquals(15.19, data.getCurrentPhase3()); // 7
assertEquals(3654, data.getOutputPowerPhase1()); // 8
assertEquals(3657, data.getOutputPowerPhase2()); // 9
assertEquals(3656, data.getOutputPowerPhase3()); // 10
assertEquals(10968, data.getTotalChargePower()); // 11
assertEquals(4.4, data.getEqSingle()); // 12
assertEquals(34.6, data.getEqTotal()); // 14 and 15
assertEquals(-1.02, data.getExternalCurrentPhase1()); // 16
assertEquals(-300.73, data.getExternalCurrentPhase2()); // 17
assertEquals(-0.77, data.getExternalCurrentPhase3()); // 18
assertEquals(-28, data.getExternalPowerPhase1()); // 19
assertEquals(-23, data.getExternalPowerPhase2()); // 20
assertEquals(27, data.getExternalPowerPhase3()); // 21
assertEquals(402, data.getExternalTotalPower()); // 22
assertEquals(0, data.getPlugTemperature()); // 23
assertEquals(43, data.getInternalTemperature()); // 24
assertEquals(2, data.getCPState()); // 26
assertEquals(1717, data.getChargingDuration()); // 80 and 81
assertEquals(0, data.getOccpOfflineMode()); // 85
assertEquals(1, data.getTypePower()); // 87
assertEquals(1, data.getTypePhase()); // 88
assertEquals(0, data.getTypeCharger()); // 89
}
}

View File

@ -10,13 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.local;
package org.openhab.binding.solax.internal.local.parsers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.local.AbstractParserTest;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
/**
* The {@link TestX1BoostAirMiniDataParser} Simple test that tests for proper parsing against a real data from the
@ -49,7 +50,7 @@ public class TestX1BoostAirMiniDataParser extends AbstractParserTest {
}
@Override
protected void assertParserSpecific(LocalInverterData data) {
protected void assertParserSpecific(LocalData data) {
assertEquals("SR***", data.getWifiSerial());
assertEquals("3.006.04", data.getWifiVersion());

View File

@ -10,13 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.local;
package org.openhab.binding.solax.internal.local.parsers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.local.AbstractParserTest;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
/**
* The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter
@ -46,7 +47,7 @@ public class TestX1HybridG4Parser extends AbstractParserTest {
}
@Override
protected void assertParserSpecific(LocalInverterData data) {
protected void assertParserSpecific(LocalData data) {
assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
assertEquals("3.008.10", data.getWifiVersion());

View File

@ -10,13 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.local;
package org.openhab.binding.solax.internal.local.parsers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.local.AbstractParserTest;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.LocalData;
/**
* The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
@ -53,7 +54,7 @@ public class TestX3HybridG4Parser extends AbstractParserTest {
}
@Override
protected void assertParserSpecific(LocalInverterData data) {
protected void assertParserSpecific(LocalData data) {
assertEquals("XYZ", data.getWifiSerial());
assertEquals("3.005.01", data.getWifiVersion());

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.local;
package org.openhab.binding.solax.internal.local.parsers;
import static org.junit.jupiter.api.Assertions.*;
@ -18,8 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalData;
import org.openhab.binding.solax.internal.model.local.RawDataParser;
/**
* The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
@ -56,7 +56,7 @@ public class TestX3MicOrProG2Parser {
RawDataParser parser = inverterType.getParser();
assertNotNull(parser);
LocalInverterData data = parser.getData(bean);
LocalData data = parser.getData(bean);
assertEquals("XYZ", data.getWifiSerial());
assertEquals("3.003.02", data.getWifiVersion());

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.solax.internal.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* The {@link TestByteUtil} Simple test that tests the methods of the ByteUtil
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class TestByteUtil {
@Test
public void testRead32BitSignedWithNegativeInputs() {
assertEquals(-65536, ByteUtil.read32BitSigned((short) 0, (short) -1));
assertEquals(-1, ByteUtil.read32BitSigned((short) -1, (short) -1));
assertEquals(-2, ByteUtil.read32BitSigned((short) -2, (short) -1));
}
@Test
public void testRead32BitSignedWithBoundaryValues() {
assertEquals(Short.MAX_VALUE, ByteUtil.read32BitSigned((short) Short.MAX_VALUE, (short) 0));
assertEquals((Short.MIN_VALUE & 0xFFFF) | (Short.MAX_VALUE << 16),
ByteUtil.read32BitSigned((short) Short.MIN_VALUE, (short) Short.MAX_VALUE));
assertEquals(Integer.MIN_VALUE, ByteUtil.read32BitSigned((short) 0, (short) (Integer.MIN_VALUE >> 16)));
}
@Test
public void testRead32BitSignedResultingInNegative() {
assertEquals(-131072, ByteUtil.read32BitSigned((short) 0, (short) -2));
}
@Test
public void testRead32BitSignedResultingInMaxInteger() {
assertEquals(Integer.MAX_VALUE, ByteUtil.read32BitSigned((short) 0xFFFF, (short) 0x7FFF));
}
}