From 24e730821af1a8d21787fc18782342ccd124a9a2 Mon Sep 17 00:00:00 2001 From: Lukas Pindl <36566235+BigFood2307@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:57:02 +0200 Subject: [PATCH] [SenecHome] Add writeable Charging Modes (#17474) * [senechome] Now accepts commands for active charging Signed-off-by: Lukas Pindl * [senechome] Manual update for charging modes Signed-off-by: Lukas Pindl * [senechome] docu and spotless Signed-off-by: Lukas Pindl * [Senechome] Charge Modes combined into a single channel Signed-off-by: Lukas Pindl * [senechome] Apply suggestions from code review Co-authored-by: lsiepel Signed-off-by: Lukas Pindl <36566235+BigFood2307@users.noreply.github.com> * [senechome] additional review fixes Signed-off-by: Lukas Pindl --------- Signed-off-by: Lukas Pindl Signed-off-by: Lukas Pindl <36566235+BigFood2307@users.noreply.github.com> Co-authored-by: lsiepel Signed-off-by: Ciprian Pascu --- .../org.openhab.binding.senechome/README.md | 7 +++ .../senechome/internal/SenecHomeApi.java | 37 +++++++++++-- .../internal/SenecHomeBindingConstants.java | 7 +++ .../senechome/internal/SenecHomeHandler.java | 54 ++++++++++++++++++- .../internal/dto/SenecHomeEnergy.java | 13 ++++- .../OH-INF/i18n/senechome.properties | 4 ++ .../resources/OH-INF/thing/thing-types.xml | 13 +++++ 7 files changed, 130 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.senechome/README.md b/bundles/org.openhab.binding.senechome/README.md index c188babea89..4d542b04f3b 100644 --- a/bundles/org.openhab.binding.senechome/README.md +++ b/bundles/org.openhab.binding.senechome/README.md @@ -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 | @@ -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 @@ -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 | @@ -135,6 +140,7 @@ Number SenecGridVoltagePh2 "Voltage Level on Phase 2 [%d V]" { channel="senechome:senechome:pvbattery:gridVoltagePhase3" } Number SenecGridFrequency "Grid Frequency [%.2f Hz]" { channel="senechome:senechome:pvbattery:gridFrequency" } Number SenecBatteryVoltage "Battery Voltage [%.1f V]" { channel="senechome:senechome:pvbattery:batteryVoltage" } +String SenecBatteryChargeMode "Battery Charge Mode [%s]" { channel="senechome:senechome:pvbattery:chargeMode" } ``` ## Sitemap @@ -166,6 +172,7 @@ Text label="Power Grid"{ Default item=SenecGridVoltagePh3 Default item=SenecGridFrequency Default item=SenecBatteryVoltage + Default item=SenecBatteryChargeMode } } ``` diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java index ccb0246e7a9..20b8c152890 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeApi.java @@ -40,6 +40,7 @@ import com.google.gson.JsonSyntaxException; * * @author Steven Schwarznau - Initial contribution * @author Robert Delbrück - Update for Senec API changes + * @author Lukas Pindl - Update for writing to safeChargeMode * */ @NonNullByDefault @@ -72,6 +73,38 @@ public class SenecHomeApi { */ 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); @@ -80,13 +113,11 @@ public class SenecHomeApi { 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()); diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java index 4bd009b20ba..d1c520c8e59 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeBindingConstants.java @@ -20,6 +20,7 @@ import org.openhab.core.thing.ThingTypeUID; * used across the whole binding. * * @author Steven Schwarznau - Initial contribution + * @author Lukas Pindl - Update for writing to chargeMode */ @NonNullByDefault public class SenecHomeBindingConstants { @@ -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"; @@ -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"; } diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java index a5eaf7831b5..7f52c51b802 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/SenecHomeHandler.java @@ -58,6 +58,7 @@ import com.google.gson.JsonParseException; * * @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 { @@ -101,7 +102,32 @@ public class SenecHomeHandler extends BaseThingHandler { 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; + } + } } } @@ -186,6 +212,9 @@ public class SenecHomeHandler extends BaseThingHandler { 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); @@ -314,6 +343,21 @@ public class SenecHomeHandler extends BaseThingHandler { } } + 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) { @@ -322,6 +366,14 @@ public class SenecHomeHandler extends BaseThingHandler { } } + 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 > void updateQtyState(String channelName, String senecValue, int scale, Unit unit) { updateQtyState(channelName, senecValue, scale, unit, null); diff --git a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/dto/SenecHomeEnergy.java b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/dto/SenecHomeEnergy.java index f943394127d..385d2f4752a 100644 --- a/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/dto/SenecHomeEnergy.java +++ b/bundles/org.openhab.binding.senechome/src/main/java/org/openhab/binding/senechome/internal/dto/SenecHomeEnergy.java @@ -23,6 +23,7 @@ import com.google.gson.annotations.SerializedName; * Section is "ENERGY" * * @author Steven Schwarznau - Initial Contribution + * @author Lukas Pindl - Update for writing to safeChargeMode */ public class SenecHomeEnergy implements Serializable { @@ -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 + "]"; } } diff --git a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties index bde416149b4..7c5576d996c 100644 --- a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties +++ b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/i18n/senechome.properties @@ -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 diff --git a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml index db42197ddb3..40b06ad2cef 100644 --- a/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.senechome/src/main/resources/OH-INF/thing/thing-types.xml @@ -32,6 +32,7 @@ + @@ -242,6 +243,18 @@ Number + + String + + Text + + + + + + + +