Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SenecHome] Add writeable Charging Modes #17474

Merged
merged 6 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bundles/org.openhab.binding.senechome/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ In addition you can switch off devices if the power consumption is getting highe

Examples: Lights, pool filters, wash machines, ...

Also allows for turning the battery into safe charging mode or storage mode.

## Supported Things

| Thing type id | Name |
Expand All @@ -19,6 +21,7 @@ Examples: Lights, pool filters, wash machines, ...
- not equipped battery packs will return 0 for all ...Pack channels
- currently channels for the first wallbox are implemented (senec could handle 4 wallboxes)
- Senec disables http access at ~30.08.2023
- The chargeMode `STORAGE` also known as Lithium Storage Mode is intended (according to the manual) for disassembly and transport. It is untested if it has any side effects.

## Thing Configuration

Expand Down Expand Up @@ -57,6 +60,8 @@ The property `limitationTresholdValue` is used as threshold for channel `powerLi
| batteryFuelCharge | percent | Fuel charge of your battery (0 - 100%) |
| systemState | | Text describing current action of the senec home system (e.g. CHARGE) |
| systemStateValue | | Value describing current action of the senec home system (e.g. 14) |
| chargeMode | OFF/CHARGE/ | In `CHARGE` mode, the battery will try to fill as quickly as possible |
| | STORAGE | in `STORAGE` mode, the battery will try to reach 25% SOC |
| gridPower | watt | Grid power level, negative for supply, positive values for drawing power |
| gridPowerDraw | watt | Absolute power level of power draw, zero while supplying |
| gridPowerSupply | watt | Absolute power level of power supply, zero while drawing |
Expand Down Expand Up @@ -135,6 +140,7 @@ Number SenecGridVoltagePh2 "Voltage Level on Phase 2 [%d V]" <e
Number SenecGridVoltagePh3 "Voltage Level on Phase 3 [%d V]" <energy> { channel="senechome:senechome:pvbattery:gridVoltagePhase3" }
Number SenecGridFrequency "Grid Frequency [%.2f Hz]" <energy> { channel="senechome:senechome:pvbattery:gridFrequency" }
Number SenecBatteryVoltage "Battery Voltage [%.1f V]" <energy> { channel="senechome:senechome:pvbattery:batteryVoltage" }
String SenecBatteryChargeMode "Battery Charge Mode [%s]" { channel="senechome:senechome:pvbattery:chargeMode" }
```

## Sitemap
Expand Down Expand Up @@ -166,6 +172,7 @@ Text label="Power Grid"{
Default item=SenecGridVoltagePh3
Default item=SenecGridFrequency
Default item=SenecBatteryVoltage
Default item=SenecBatteryChargeMode
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
*
* @author Steven Schwarznau - Initial contribution
* @author Robert Delbrück - Update for Senec API changes
* @author Lukas Pindl - Update for writing to safeChargeMode
*
*/
@NonNullByDefault
Expand Down Expand Up @@ -72,6 +73,38 @@ public void setHostname(String hostname) {
*/
public SenecHomeResponse getStatistics()
throws TimeoutException, ExecutionException, IOException, InterruptedException, JsonSyntaxException {

String dataToSend = gson.toJson(new SenecHomeResponse());
ContentResponse response = postRequest(dataToSend);
return Objects.requireNonNull(gson.fromJson(response.getContentAsString(), SenecHomeResponse.class));
}

/**
* POST json, to lala.cgi of Senec webinterface to set a given parameter
*
* @return boolean, wether or not the request was successful
*/
public boolean setValue(String section, String id, String value) {
String dataToSend = "{\"" + section + "\":{\"" + id + "\":\"" + value + "\"}}";
try {
postRequest(dataToSend);
return true;
} catch (TimeoutException | ExecutionException | IOException | InterruptedException e) {
return false;
}
}

/**
* helper function to handle the actual POST request to the webinterface
*
* @return object of type ContentResponse, the response received to the POST request
* @throws TimeoutException Communication failed (Timeout)
* @throws ExecutionException Communication failed
* @throws IOException Communication failed
* @throws InterruptedException Communication failed (Interrupted)
*/
private ContentResponse postRequest(String dataToSend)
throws TimeoutException, ExecutionException, IOException, InterruptedException {
String location = hostname + "/lala.cgi";
logger.trace("sending request to: {}", location);

Expand All @@ -80,13 +113,11 @@ public SenecHomeResponse getStatistics()
request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString());
ContentResponse response = null;
try {
String dataToSend = gson.toJson(new SenecHomeResponse());
logger.trace("data to send: {}", dataToSend);
response = request.method(HttpMethod.POST).content(new StringContentProvider(dataToSend))
.timeout(15, TimeUnit.SECONDS).send();
if (response.getStatus() == HttpStatus.OK_200) {
String responseString = response.getContentAsString();
return Objects.requireNonNull(gson.fromJson(responseString, SenecHomeResponse.class));
return response;
} else {
logger.trace("Got unexpected response code {}", response.getStatus());
throw new IOException("Got unexpected response code " + response.getStatus());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* used across the whole binding.
*
* @author Steven Schwarznau - Initial contribution
* @author Lukas Pindl - Update for writing to chargeMode
*/
@NonNullByDefault
public class SenecHomeBindingConstants {
Expand Down Expand Up @@ -49,6 +50,7 @@ public class SenecHomeBindingConstants {
public static final String CHANNEL_SENEC_BATTERY_FUEL_CHARGE = "batteryFuelCharge";
public static final String CHANNEL_SENEC_BATTERY_VOLTAGE = "batteryVoltage";
public static final String CHANNEL_SENEC_BATTERY_CURRENT = "batteryCurrent";
public static final String CHANNEL_SENEC_CHARGE_MODE = "chargeMode";

// SenecHomeGrid
public static final String CHANNEL_SENEC_GRID_POWER = "gridPower";
Expand Down Expand Up @@ -107,4 +109,9 @@ public class SenecHomeBindingConstants {
public static final String CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH2 = "wallbox1ChargingCurrentPhase2";
public static final String CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH3 = "wallbox1ChargingCurrentPhase3";
public static final String CHANNEL_SENEC_WALLBOX1_CHARGING_POWER = "wallbox1ChargingPower";

// Charge Mode Definitions
public static final String STATE_SENEC_CHARGE_MODE_OFF = "OFF";
public static final String STATE_SENEC_CHARGE_MODE_CHARGE = "CHARGE";
public static final String STATE_SENEC_CHARGE_MODE_STORAGE = "STORAGE";
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
*
* @author Steven Schwarznau - Initial contribution
* @author Erwin Guib - added more channels, added some convenience methods to reduce code duplication
* @author Lukas Pindl - Update for writing to chargeMode
*/
@NonNullByDefault
public class SenecHomeHandler extends BaseThingHandler {
Expand Down Expand Up @@ -101,7 +102,32 @@ public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Refreshing {}", channelUID);
refresh();
} else {
logger.trace("The SenecHome-Binding is a read-only binding and can not handle commands");
String channelID = channelUID.getId();

logger.trace("Channel: {}", channelID);
switch (channelID) {
case CHANNEL_SENEC_CHARGE_MODE: {
if (command instanceof StringType stringCommand) {
logger.trace("Command: {} ", stringCommand.toString());
if (stringCommand.toString().equals(STATE_SENEC_CHARGE_MODE_OFF)) {
senecHomeApi.setValue("ENERGY", "SAFE_CHARGE_PROHIBIT", "u8_01");
senecHomeApi.setValue("ENERGY", "LI_STORAGE_MODE_STOP", "u8_01");
} else if (stringCommand.toString().equals(STATE_SENEC_CHARGE_MODE_CHARGE)) {
senecHomeApi.setValue("ENERGY", "SAFE_CHARGE_FORCE", "u8_01");
senecHomeApi.setValue("ENERGY", "LI_STORAGE_MODE_STOP", "u8_01");
} else if (stringCommand.toString().equals(STATE_SENEC_CHARGE_MODE_STORAGE)) {
senecHomeApi.setValue("ENERGY", "SAFE_CHARGE_PROHIBIT", "u8_01");
senecHomeApi.setValue("ENERGY", "LI_STORAGE_MODE_START", "u8_01");
}
updateState(channelUID, stringCommand);
}
break;
}
default: {
logger.warn("Received command on unexpected channel: {}", channelID);
break;
}
}
}
}

Expand Down Expand Up @@ -186,6 +212,9 @@ private <Q extends Quantity<Q>> void updateQtyStateIfAvailable(String channel, S
updateQtyState(CHANNEL_SENEC_BATTERY_POWER, response.energy.batteryPower, 2, Units.WATT);
updateQtyState(CHANNEL_SENEC_BATTERY_CURRENT, response.energy.batteryCurrent, 2, Units.AMPERE);
updateQtyState(CHANNEL_SENEC_BATTERY_VOLTAGE, response.energy.batteryVoltage, 2, Units.VOLT);

updateChargeState(CHANNEL_SENEC_CHARGE_MODE, response.energy.safeChargeMode, response.energy.liStorageMode);

updateStringStateFromInt(CHANNEL_SENEC_SYSTEM_STATE, response.energy.systemState,
SenecSystemStatus::descriptionFromCode);
updateDecimalState(CHANNEL_SENEC_SYSTEM_STATE_VALUE, response.energy.systemState);
Expand Down Expand Up @@ -314,6 +343,21 @@ protected void updateStringStateFromInt(String channelName, String senecValue,
}
}

protected void updateChargeState(String channelName, String senecValueCharge, String senecValueStorage) {
Channel channel = getThing().getChannel(channelName);
if (channel != null) {
BigDecimal valueCharge = getSenecValue(senecValueCharge);
BigDecimal valueStorage = getSenecValue(senecValueStorage);
if (valueStorage.intValue() == 1) {
updateState(channel.getUID(), new StringType(STATE_SENEC_CHARGE_MODE_STORAGE));
} else if (valueCharge.intValue() == 1) {
updateState(channel.getUID(), new StringType(STATE_SENEC_CHARGE_MODE_CHARGE));
} else {
updateState(channel.getUID(), new StringType(STATE_SENEC_CHARGE_MODE_OFF));
}
}
}

protected void updateDecimalState(String channelName, String senecValue) {
Channel channel = getThing().getChannel(channelName);
if (channel != null) {
Expand All @@ -322,6 +366,14 @@ protected void updateDecimalState(String channelName, String senecValue) {
}
}

protected void updateSwitchState(String channelName, String senecValue) {
Channel channel = getThing().getChannel(channelName);
if (channel != null) {
BigDecimal value = getSenecValue(senecValue);
updateState(channel.getUID(), OnOffType.from(value.intValue() == 1));
}
}

protected <Q extends Quantity<Q>> void updateQtyState(String channelName, String senecValue, int scale,
Unit<Q> unit) {
updateQtyState(channelName, senecValue, scale, unit, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Section is "ENERGY"
*
* @author Steven Schwarznau - Initial Contribution
* @author Lukas Pindl - Update for writing to safeChargeMode
*/
public class SenecHomeEnergy implements Serializable {

Expand Down Expand Up @@ -64,11 +65,21 @@ public class SenecHomeEnergy implements Serializable {
*/
public @SerializedName("STAT_STATE") String systemState;

/**
* Safe Charge Mode Running.
*/
public @SerializedName("SAFE_CHARGE_RUNNING") String safeChargeMode;

/**
* Lithium Storage Mode Running.
*/
public @SerializedName("LI_STORAGE_MODE_RUNNING") String liStorageMode;

@Override
public String toString() {
return "SenecHomeEnergy [housePowerConsumption=" + housePowerConsumption + ", inverterPowerGeneration="
+ inverterPowerGeneration + ", batteryPower=" + batteryPower + ", batteryVoltage=" + batteryVoltage
+ ", batteryCurrent=" + batteryCurrent + ", batteryFuelCharge=" + batteryFuelCharge + ", systemState="
+ systemState + "]";
+ systemState + ", safeChargeMode=" + safeChargeMode + ", liStorageMode=" + liStorageMode + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ channel-type.senechome.batteryPower.label = Battery Power
channel-type.senechome.batteryTemperature.label = Battery Temperature
channel-type.senechome.batteryVoltage.label = Battery Voltage
channel-type.senechome.caseTemperature.label = Case Temperature
channel-type.senechome.chargeMode.label = Safe Charge Mode
channel-type.senechome.chargeMode.state.option.OFF = Off
channel-type.senechome.chargeMode.state.option.CHARGE = Safe Charge
channel-type.senechome.chargeMode.state.option.STORAGE = Lithium Storage
channel-type.senechome.chargedEnergyPack1.label = Total charged energy battery pack 1
channel-type.senechome.chargedEnergyPack2.label = Total charged energy battery pack 2
channel-type.senechome.chargedEnergyPack3.label = Total charged energy battery pack 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<channel id="batteryFuelCharge" typeId="batteryFuelCharge"/>
<channel id="systemState" typeId="systemState"/>
<channel id="systemStateValue" typeId="systemStateValue"/>
<channel id="chargeMode" typeId="chargeMode"/>

<!-- SenecHomeGrid -->
<channel id="gridPower" typeId="gridPower"/>
Expand Down Expand Up @@ -242,6 +243,18 @@
<category>Number</category>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="chargeMode">
<item-type>String</item-type>
<label>Safe Charge Mode</label>
<category>Text</category>
<state readOnly="false" pattern="%s">
<options>
<option value="OFF">Off</option>
<option value="CHARGE">Safe Charge</option>
<option value="STORAGE">Lithium Storage</option>
</options>
</state>
</channel-type>


<channel-type id="gridPower">
Expand Down