From: lsiepel Date: Mon, 7 Oct 2024 06:11:04 +0000 (+0200) Subject: [homewizard] Add current, voltage and failure channels (#16995) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=6b411899cc5cbe2e0822b1b3939ed2a2867489e6;p=openhab-addons.git [homewizard] Add current, voltage and failure channels (#16995) Signed-off-by: Leo Siepel --- diff --git a/bundles/org.openhab.binding.homewizard/README.md b/bundles/org.openhab.binding.homewizard/README.md index 93e2d84813..f76ef561f4 100644 --- a/bundles/org.openhab.binding.homewizard/README.md +++ b/bundles/org.openhab.binding.homewizard/README.md @@ -48,23 +48,33 @@ For DSMR5 meters this is generally once per second, for older versions the frequ ## Channels -| Channel ID | Item Type | Description |Available| -|------------------------|---------------------------|--------------------------------------------------------------------------------------------|---------| -| total_energy_import_t1 | Number:Energy | The most recently reported total imported energy in kWh by counter 1. | P,E | -| total_energy_import_t2 | Number:Energy | The most recently reported total imported energy in kWh by counter 2. | P | -| total_energy_export_t1 | Number:Energy | The most recently reported total exported energy in kWh by counter 1. | P,E | -| total_energy_export_t2 | Number:Energy | The most recently reported total exported energy in kWh by counter 2. | P | -| active_power | Number:Power | The current net total power in W. It will be below 0 if power is currently being exported. | P,E | -| active_power_l1 | Number:Power | The current net total power in W for phase 1. | P | -| active_power_l2 | Number:Power | The current net total power in W for phase 2. | P | -| active_power_l3 | Number:Power | The current net total power in W for phase 3. | P | -| total_gas | Number:Volume | The most recently reported total imported gas in m^3. | P | -| gas_timestamp | DateTime | The time stamp of the total_gas measurement. | P | -| total_water | Number:Volume | Total water used. | W | -| current_water | Number:VolumetricFlowRate | Current water usage. | W | -| power_switch | Switch | Controls the power switch of the socket. | E | -| power_lock | Switch | Controls the lock of the power switch (un/locking both the API and the physical button) | E | -| ring_brightness | Number:Dimensionless | Controls the brightness of the ring on the socket | E | +| Channel ID | Item Type | Description | Available | +|------------------------|---------------------------|--------------------------------------------------------------------------------------------|-----------| +| active_current | Number:ElectricCurrent | The combined current in A vor all phases | P,E | +| active_current_l1 | Number:ElectricCurrent | The active current in A for phase 1. | P | +| active_current_l2 | Number:ElectricCurrent | The active current in A for phase 2. | P | +| active_current_l3 | Number:ElectricCurrent | The active current in A for phase 3. | P | +| active_power | Number:Power | The current net total power in W. It will be below 0 if power is currently being exported. | P,E | +| active_power_l1 | Number:Power | The current net total power in W for phase 1. | P | +| active_power_l2 | Number:Power | The current net total power in W for phase 2. | P | +| active_power_l3 | Number:Power | The current net total power in W for phase 3. | P | +| active_voltage | Number:ElectricPotential | The active voltage in V | P | +| active_voltage_l1 | Number:ElectricPotential | The active voltage in V for phase 1. | P | +| active_voltage_l2 | Number:ElectricPotential | The active voltage in V for phase 2. | P | +| active_voltage_l3 | Number:ElectricPotential | The active voltage in V for phase 3. | P | +| total_energy_import_t1 | Number:Energy | The most recently reported total imported energy in kWh by counter 1. | P,E | +| total_energy_import_t2 | Number:Energy | The most recently reported total imported energy in kWh by counter 2. | P | +| total_energy_export_t1 | Number:Energy | The most recently reported total exported energy in kWh by counter 1. | P,E | +| total_energy_export_t2 | Number:Energy | The most recently reported total exported energy in kWh by counter 2. | P | +| total_gas | Number:Volume | The most recently reported total imported gas in m^3. | P | +| gas_timestamp | DateTime | The time stamp of the total_gas measurement. | P | +| total_water | Number:Volume | Total water used. | W | +| current_water | Number:VolumetricFlowRate | Current water usage. | W | +| power_failures | Number | The count of long power failures. | P | +| long_power_failures | Number | the count of any power failures. | P | +| power_switch | Switch | Controls the power switch of the socket. | E | +| power_lock | Switch | Controls the lock of the power switch (un/locking both the API and the physical button) | E | +| ring_brightness | Number:Dimensionless | Controls the brightness of the ring on the socket | E | ## Full Example diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/DataPayload.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/DataPayload.java deleted file mode 100644 index c1bc9e6255..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/DataPayload.java +++ /dev/null @@ -1,354 +0,0 @@ -/** - * 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.homewizard.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.google.gson.annotations.SerializedName; - -/** - * Class that provides storage for the json objects obtained from HomeWizard devices. - * - * @author Daniël van Os - Initial contribution - * - */ -@NonNullByDefault -public class DataPayload { - private int smrVersion = 0; - private String meterModel = ""; - private String wifiSsid = ""; - private int wifiStrength = 0; - - @SerializedName("total_power_import_t1_kwh") - private double totalEnergyImportT1Kwh; - @SerializedName("total_power_import_t2_kwh") - private double totalEnergyImportT2Kwh; - @SerializedName("total_power_export_t1_kwh") - private double totalEnergyExportT1Kwh; - @SerializedName("total_power_export_t2_kwh") - private double totalEnergyExportT2Kwh; - - private double activePowerW; - private double activePowerL1W; - private double activePowerL2W; - private double activePowerL3W; - private double totalGasM3; - private long gasTimestamp = 0; - - @SerializedName("total_liter_m3") - private double totalWaterM3; - @SerializedName("active_liter_lpm") - private double currentWaterLPM; - - /** - * Getter for the smart meter version - * - * @return The most recent smart meter version obtained from the API - */ - public int getSmrVersion() { - return smrVersion; - } - - /** - * Setter for the smart meter version - * - * @param smrVersion The smart meter version to set - */ - public void setSmrVersion(int smrVersion) { - this.smrVersion = smrVersion; - } - - /** - * Getter for the meter model - * - * @return meter model - */ - public String getMeterModel() { - return meterModel; - } - - /** - * Setter for the meter model - * - * @param meterModel meter model - */ - public void setMeterModel(String meterModel) { - this.meterModel = meterModel; - } - - /** - * Getter for the meter's wifi ssid - * - * @return the meter's wifi sid - */ - public String getWifiSsid() { - return wifiSsid; - } - - /** - * Setter for the wifi ssid - * - * @param wifiSsid wifi ssid - */ - public void setWifiSsid(String wifiSsid) { - this.wifiSsid = wifiSsid; - } - - /** - * Getter for the wifi rssi - * - * @return wifi rssi - */ - public int getWifiStrength() { - return wifiStrength; - } - - /** - * Setter for the wifi rssi - * - * @param wifiStrength wifi rssi - */ - public void setWifiStrength(int wifiStrength) { - this.wifiStrength = wifiStrength; - } - - /** - * Getter for the total imported energy on counter 1 - * - * @return total imported energy on counter 1 - */ - public double getTotalEnergyImportT1Kwh() { - return totalEnergyImportT1Kwh; - } - - /** - * Setter for the total imported energy on counter 1 - * - * @param totalEnergyImportT1Kwh total imported energy on counter 1 - */ - public void setTotalEnergyImportT1Kwh(double totalEnergyImportT1Kwh) { - this.totalEnergyImportT1Kwh = totalEnergyImportT1Kwh; - } - - /** - * Getter for the total imported energy on counter 2 - * - * @return total imported energy on counter 2 - */ - public double getTotalEnergyImportT2Kwh() { - return totalEnergyImportT2Kwh; - } - - /** - * Setter for the total imported energy on counter 2 - * - * @param totalEnergyImportT2Kwh - */ - public void setTotalEnergyImportT2Kwh(double totalEnergyImportT2Kwh) { - this.totalEnergyImportT2Kwh = totalEnergyImportT2Kwh; - } - - /** - * Getter for the total exported energy on counter 1 - * - * @return total exported energy on counter 1 - */ - public double getTotalEnergyExportT1Kwh() { - return totalEnergyExportT1Kwh; - } - - /** - * Setter for the total exported energy on counter 1 - * - * @param totalEnergyExportT1Kwh - */ - public void setTotalEnergyExportT1Kwh(double totalEnergyExportT1Kwh) { - this.totalEnergyExportT1Kwh = totalEnergyExportT1Kwh; - } - - /** - * Getter for the total exported energy on counter 2 - * - * @return total exported energy on counter 2 - */ - public double getTotalEnergyExportT2Kwh() { - return totalEnergyExportT2Kwh; - } - - /** - * Setter for the total exported energy on counter 2 - * - * @param totalEnergyExportT2Kwh - */ - public void setTotalEnergyExportT2Kwh(double totalEnergyExportT2Kwh) { - this.totalEnergyExportT2Kwh = totalEnergyExportT2Kwh; - } - - /** - * Getter for the current active total power - * - * @return current active total power - */ - public double getActivePowerW() { - return activePowerW; - } - - /** - * Setter for the current active total power - * - * @param activePowerW - */ - public void setActivePowerW(double activePowerW) { - this.activePowerW = activePowerW; - } - - /** - * Getter for the current active total power on phase 1 - * - * @return current active total power on phase 1 - */ - public double getActivePowerL1W() { - return activePowerL1W; - } - - /** - * Setter for the current active power on phase 1 - * - * @param activePowerL1W current active total power on phase 1 - */ - public void setActivePowerL1W(double activePowerL1W) { - this.activePowerL1W = activePowerL1W; - } - - /** - * Getter for the current active total power on phase 2 - * - * @return current active total power on phase 2 - */ - public double getActivePowerL2W() { - return activePowerL2W; - } - - /** - * Setter for the current active power on phase 2 - * - * @param activePowerL2W current active total power on phase 2 - */ - public void setActivePowerL2W(double activePowerL2W) { - this.activePowerL2W = activePowerL2W; - } - - /** - * Getter for the current active total power on phase 3 - * - * @return current active total power on phase 3 - */ - public double getActivePowerL3W() { - return activePowerL3W; - } - - /** - * Setter for the current active power on phase 3 - * - * @param activePowerL3W current active total power on phase 3 - */ - public void setActivePowerL3W(double activePowerL3W) { - this.activePowerL3W = activePowerL3W; - } - - /** - * Getter for the total imported gas volume - * - * @return total imported gas volume - */ - public double getTotalGasM3() { - return totalGasM3; - } - - /** - * Setter for the total imported gas volume - * - * @param totalGasM3 total imported gas volume - */ - public void setTotalGasM3(double totalGasM3) { - this.totalGasM3 = totalGasM3; - } - - /** - * Getter for the time stamp of the last gas update - * - * @return time stamp of the last gas update - */ - public long getGasTimestamp() { - return gasTimestamp; - } - - /** - * Setter for the time stamp of the last gas update - * - * @param gasTimestamp time stamp of the last gas update - */ - public void setGasTimestamp(long gasTimestamp) { - this.gasTimestamp = gasTimestamp; - } - - /** - * Getter for the total imported water volume - * - * @return total imported water volume - */ - public double getTotalWaterM3() { - return totalWaterM3; - } - - /** - * Setter for the total imported water volume - * - * @param totalWaterM3 total imported water volume - */ - public void setTotalWaterM3(double totalWaterM3) { - this.totalWaterM3 = totalWaterM3; - } - - /** - * Getter for the current water flow - * - * @return current water flow - */ - public double getCurrentWaterLPM() { - return currentWaterLPM; - } - - /** - * Setter for the current water flow - * - * @param currentWaterLPM current water flow - */ - public void setCurrentWaterLPM(double currentWaterLPM) { - this.currentWaterLPM = currentWaterLPM; - } - - @Override - public String toString() { - return String.format( - """ - Data [smrVersion: %d meterModel: %s wifiSsid: %s wifiStrength: %d" - totalEnergyImportT1Kwh: %f totalEnergyImportT2Kwh: %f totalEnergyExportT1Kwh: %f totalEnergyExportT2Kwh: %f" - activePowerW: %f activePowerL1W: %f activePowerL2W: %f activePowerL3W: %f totalGasM3: %f gasTimestamp: %.0f" - totalWaterM3: %f currentWaterLPM: %f] - """, - smrVersion, meterModel, wifiSsid, wifiStrength, totalEnergyImportT1Kwh, totalEnergyImportT2Kwh, - totalEnergyExportT1Kwh, totalEnergyExportT2Kwh, activePowerW, activePowerL1W, activePowerL2W, - activePowerL3W, totalGasM3, gasTimestamp, totalWaterM3, currentWaterLPM); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardBindingConstants.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardBindingConstants.java index 3943f2bed3..8b28b204c8 100644 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardBindingConstants.java +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardBindingConstants.java @@ -32,16 +32,27 @@ public class HomeWizardBindingConstants { public static final ThingTypeUID THING_TYPE_WATERMETER = new ThingTypeUID(BINDING_ID, "watermeter"); // List of all Channel ids - public static final String CHANNEL_ENERGY_IMPORT_T1 = "total_energy_import_t1"; - public static final String CHANNEL_ENERGY_IMPORT_T2 = "total_energy_import_t2"; - public static final String CHANNEL_ENERGY_EXPORT_T1 = "total_energy_export_t1"; - public static final String CHANNEL_ENERGY_EXPORT_T2 = "total_energy_export_t2"; + public static final String CHANNEL_ACTIVE_CURRENT = "active_current"; + public static final String CHANNEL_ACTIVE_CURRENT_L1 = "active_current_l1"; + public static final String CHANNEL_ACTIVE_CURRENT_L2 = "active_current_l2"; + public static final String CHANNEL_ACTIVE_CURRENT_L3 = "active_current_l3"; public static final String CHANNEL_ACTIVE_POWER = "active_power"; public static final String CHANNEL_ACTIVE_POWER_L1 = "active_power_l1"; public static final String CHANNEL_ACTIVE_POWER_L2 = "active_power_l2"; public static final String CHANNEL_ACTIVE_POWER_L3 = "active_power_l3"; - public static final String CHANNEL_TOTAL_GAS = "total_gas"; + public static final String CHANNEL_ACTIVE_VOLTAGE = "active_voltage"; + public static final String CHANNEL_ACTIVE_VOLTAGE_L1 = "active_voltage_l1"; + public static final String CHANNEL_ACTIVE_VOLTAGE_L2 = "active_voltage_l2"; + public static final String CHANNEL_ACTIVE_VOLTAGE_L3 = "active_voltage_l3"; + public static final String CHANNEL_POWER_FAILURES = "power_failures"; + public static final String CHANNEL_LONG_POWER_FAILURES = "long_power_failures"; + public static final String CHANNEL_ENERGY_IMPORT_T1 = "total_energy_import_t1"; + public static final String CHANNEL_ENERGY_IMPORT_T2 = "total_energy_import_t2"; + public static final String CHANNEL_ENERGY_EXPORT_T1 = "total_energy_export_t1"; + public static final String CHANNEL_ENERGY_EXPORT_T2 = "total_energy_export_t2"; + public static final String CHANNEL_GAS_TIMESTAMP = "gas_timestamp"; + public static final String CHANNEL_GAS_TOTAL = "total_gas"; public static final String CHANNEL_TOTAL_WATER = "total_water"; public static final String CHANNEL_CURRENT_WATER = "current_water"; diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardDeviceHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardDeviceHandler.java deleted file mode 100644 index 9e9f74ff60..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardDeviceHandler.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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.homewizard.internal; - -import java.io.IOException; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -/** - * The {@link HomeWizardDeviceHandler} is a base class for all - * HomeWizard devices. It provides configuration and polling of - * data from a device. It also processes common data. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -public abstract class HomeWizardDeviceHandler extends BaseThingHandler { - - protected final Logger logger = LoggerFactory.getLogger(HomeWizardDeviceHandler.class); - protected final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - - private HomeWizardConfiguration config = new HomeWizardConfiguration(); - private @Nullable ScheduledFuture pollingJob; - - protected String apiURL = ""; - - /** - * Constructor - * - * @param thing The thing to handle - */ - public HomeWizardDeviceHandler(Thing thing) { - super(thing); - } - - /** - * If a host has been specified start polling it - */ - @Override - public void initialize() { - config = getConfigAs(HomeWizardConfiguration.class); - if (configure()) { - pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, config.refreshDelay, TimeUnit.SECONDS); - } - } - - /** - * Check the current configuration - * - * @return true if the configuration is ok to start polling, false otherwise - */ - private boolean configure() { - if (config.ipAddress.trim().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Missing ipAddress/host configuration"); - return false; - } else { - updateStatus(ThingStatus.UNKNOWN); - apiURL = String.format("http://%s/api/v1/", config.ipAddress.trim()); - return true; - } - } - - /** - * dispose: stop the poller - */ - @Override - public void dispose() { - var job = pollingJob; - if (job != null) { - job.cancel(true); - } - pollingJob = null; - } - - /** - * Device specific handling of the returned data payload. - * - * @param payload The data parsed from the data Json file - */ - protected abstract void handleDataPayload(DataPayload payload); - - /** - * - */ - protected void pollData() { - final String dataResult; - - try { - dataResult = HttpUtil.executeUrl("GET", apiURL + "data", 30000); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - String.format("Unable to query device data: %s", e.getMessage())); - return; - } - - if (dataResult.trim().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device returned empty data"); - return; - } - - DataPayload dataPayload = gson.fromJson(dataResult, DataPayload.class); - if (dataPayload == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Unable to parse data response from device"); - return; - } - - if ("".equals(dataPayload.getWifiSsid())) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Results from API are empty"); - return; - } - - updateStatus(ThingStatus.ONLINE); - handleDataPayload(dataPayload); - } - - /** - * The actual polling loop - */ - protected void pollingCode() { - pollData(); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardEnergySocketHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardEnergySocketHandler.java deleted file mode 100644 index 72c844fada..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardEnergySocketHandler.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * 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.homewizard.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -/** - * The {@link HomeWizardEnergySocketHandler} implements functionality to handle a HomeWizard EnergySocket. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -public class HomeWizardEnergySocketHandler extends HomeWizardStatefulDeviceHandler { - - /** - * Constructor - * - * @param thing The thing to handle - */ - public HomeWizardEnergySocketHandler(Thing thing) { - super(thing); - } - - /** - * Converts a brightness value (0..255) to a percentage. - * - * @param brightness The brightness to convert. - * @return brightness percentage - */ - private int brightnessToPercentage(int brightness) { - return (int) (100.0 * brightness / 255.0 + 0.5); - } - - /** - * Converts a percentage to a brightness value (0..255) - * - * @param percentage The percentage to convert. - * @return brightness value - */ - private int percentageToBrightness(String percentage) { - return (int) (Double.valueOf(percentage) * 255.0 / 100.0 + 0.5); - } - - /** - * Handle incoming commands. - * - * Power on/off, Power lock/unlock and Ring brightness are supported. - */ - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - // For now I prefer not updating immediately above firing a full update request for each channel - return; - } - - StatePayload result = null; - - /* - * The returned payloads below only contain the modified value, so each has it's own - * call to updateState instead of just calling handleStatePayload() with the returned - * payload. - */ - - switch (channelUID.getId()) { - case HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS: { - result = sendStateCommand( - String.format("{\"brightness\": %d}", percentageToBrightness(command.toFullString()))); - if (result != null) { - updateState(HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS, - new PercentType(brightnessToPercentage(result.getBrightness()))); - } - break; - } - case HomeWizardBindingConstants.CHANNEL_POWER_SWITCH: { - boolean onOff = command.equals(OnOffType.ON); - result = sendStateCommand(String.format("{\"power_on\": %b}", onOff)); - if (result != null) { - updateState(HomeWizardBindingConstants.CHANNEL_POWER_SWITCH, OnOffType.from(result.getPowerOn())); - } - break; - } - case HomeWizardBindingConstants.CHANNEL_POWER_LOCK: { - boolean onOff = command.equals(OnOffType.ON); - result = sendStateCommand(String.format("{\"switch_lock\": %b}", onOff)); - if (result != null) { - updateState(HomeWizardBindingConstants.CHANNEL_POWER_LOCK, OnOffType.from(result.getSwitchLock())); - } - break; - } - default: - logger.warn("Should handle {} {}", channelUID.getIdWithoutGroup(), command); - break; - } - } - - /** - * Device specific handling of the returned payload. - * - * @param payload The data parsed from the Json file - */ - @Override - protected void handleDataPayload(DataPayload payload) { - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1, - new QuantityType<>(payload.getTotalEnergyImportT1Kwh(), Units.KILOWATT_HOUR)); - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1, - new QuantityType<>(payload.getTotalEnergyExportT1Kwh(), Units.KILOWATT_HOUR)); - updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER, - new QuantityType<>(payload.getActivePowerW(), Units.WATT)); - } - - @Override - protected void handleStatePayload(StatePayload payload) { - updateState(HomeWizardBindingConstants.CHANNEL_POWER_SWITCH, OnOffType.from(payload.getPowerOn())); - updateState(HomeWizardBindingConstants.CHANNEL_POWER_LOCK, OnOffType.from(payload.getSwitchLock())); - updateState(HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS, - new PercentType(brightnessToPercentage(payload.getBrightness()))); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardHandlerFactory.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardHandlerFactory.java deleted file mode 100644 index 57e6b805fa..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardHandlerFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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.homewizard.internal; - -import static org.openhab.binding.homewizard.internal.HomeWizardBindingConstants.*; - -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.service.component.annotations.Component; - -/** - * The {@link HomeWizardHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -@Component(configurationPid = "binding.homewizard", service = ThingHandlerFactory.class) -public class HomeWizardHandlerFactory extends BaseThingHandlerFactory { - - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_P1_METER, - THING_TYPE_ENERGY_SOCKET, THING_TYPE_WATERMETER); - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_P1_METER.equals(thingTypeUID)) { - return new HomeWizardP1MeterHandler(thing); - } - - if (THING_TYPE_ENERGY_SOCKET.equals(thingTypeUID)) { - return new HomeWizardEnergySocketHandler(thing); - } - - if (THING_TYPE_WATERMETER.equals(thingTypeUID)) { - return new HomeWizardWaterMeterHandler(thing); - } - - return null; - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandler.java deleted file mode 100644 index 3f8785ce61..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * 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.homewizard.internal; - -import java.time.DateTimeException; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; - -/** - * The {@link HomeWizardP1MeterHandler} implements functionality to handle a HomeWizard P1 Meter. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -public class HomeWizardP1MeterHandler extends HomeWizardDeviceHandler { - - private String meterModel = ""; - private int meterVersion = 0; - - /** - * Constructor - * - * @param thing The thing to handle - */ - public HomeWizardP1MeterHandler(Thing thing) { - super(thing); - } - - /** - * Not listening to any commands. - */ - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } - - /** - * Device specific handling of the returned payload. - * - * @param payload The data parsed from the Json file - */ - @Override - protected void handleDataPayload(DataPayload payload) { - if (!meterModel.equals(payload.getMeterModel())) { - meterModel = payload.getMeterModel(); - updateProperty(HomeWizardBindingConstants.PROPERTY_METER_MODEL, meterModel); - } - - if (meterVersion != payload.getSmrVersion()) { - meterVersion = payload.getSmrVersion(); - updateProperty(HomeWizardBindingConstants.PROPERTY_METER_VERSION, String.format("%d", meterVersion)); - } - - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1, - new QuantityType<>(payload.getTotalEnergyImportT1Kwh(), Units.KILOWATT_HOUR)); - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T2, - new QuantityType<>(payload.getTotalEnergyImportT2Kwh(), Units.KILOWATT_HOUR)); - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1, - new QuantityType<>(payload.getTotalEnergyExportT1Kwh(), Units.KILOWATT_HOUR)); - updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T2, - new QuantityType<>(payload.getTotalEnergyExportT2Kwh(), Units.KILOWATT_HOUR)); - - updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER, - new QuantityType<>(payload.getActivePowerW(), Units.WATT)); - updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L1, - new QuantityType<>(payload.getActivePowerL1W(), Units.WATT)); - updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L2, - new QuantityType<>(payload.getActivePowerL2W(), Units.WATT)); - updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3, - new QuantityType<>(payload.getActivePowerL3W(), Units.WATT)); - - // If no data from the gas meter is present, the json value will be null, which means gson ignores it, - // leaving the value in the payload object at 0. - long dtv = payload.getGasTimestamp(); - if (dtv > 0) { - updateState(HomeWizardBindingConstants.CHANNEL_TOTAL_GAS, - new QuantityType<>(payload.getTotalGasM3(), SIUnits.CUBIC_METRE)); - - // 210119164000 - int seconds = (int) (dtv % 100); - - dtv /= 100; - int minutes = (int) (dtv % 100); - - dtv /= 100; - int hours = (int) (dtv % 100); - - dtv /= 100; - int day = (int) (dtv % 100); - - dtv /= 100; - int month = (int) (dtv % 100); - - dtv /= 100; - int year = (int) (dtv + 2000); - - try { - DateTimeType dtt = new DateTimeType( - ZonedDateTime.of(year, month, day, hours, minutes, seconds, 0, ZoneId.systemDefault())); - updateState(HomeWizardBindingConstants.CHANNEL_GAS_TIMESTAMP, dtt); - updateState(HomeWizardBindingConstants.CHANNEL_TOTAL_GAS, - new QuantityType<>(payload.getTotalGasM3(), SIUnits.CUBIC_METRE)); - } catch (DateTimeException e) { - logger.warn("Unable to parse Gas timestamp: {}", payload.getGasTimestamp()); - } - } - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardStatefulDeviceHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardStatefulDeviceHandler.java deleted file mode 100644 index 11197b7f9f..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardStatefulDeviceHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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.homewizard.internal; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.io.net.http.HttpUtil; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; - -/** - * The {@link HomeWizardStatefulDeviceHandler} extends the base class - * to provide support for devices that also have a 'state' interface. - * This interface can be used to query and control the state of a device. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -public abstract class HomeWizardStatefulDeviceHandler extends HomeWizardDeviceHandler { - - /** - * Constructor - * - * @param thing The thing to handle - */ - public HomeWizardStatefulDeviceHandler(Thing thing) { - super(thing); - } - - /** - * Device specific handling of the returned state payload. - * - * @param payload The data parsed from the state Json file - */ - protected abstract void handleStatePayload(StatePayload payload); - - protected void pollState() { - final String stateResult; - - try { - stateResult = HttpUtil.executeUrl("GET", apiURL + "state", 30000); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - String.format("Unable to query device state: %s", e.getMessage())); - return; - } - - if (stateResult.trim().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device returned empty state"); - return; - } - - StatePayload statePayload = gson.fromJson(stateResult, StatePayload.class); - if (statePayload == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Unable to parse state response from device"); - return; - } - - handleStatePayload(statePayload); - } - - /** - * Sends a command to the state interface of the device. - * - * @param command The command to send. - */ - protected @Nullable StatePayload sendStateCommand(String command) { - try (InputStream is = new ByteArrayInputStream(command.getBytes())) { - String updatedState = HttpUtil.executeUrl("PUT", apiURL + "state", is, "application/json", 30000); - return gson.fromJson(updatedState, StatePayload.class); - } catch (IOException e) { - logger.warn("Failed to send command {} to {}", command, apiURL + "state"); - return null; - } - } - - /* - * This overrides the original polling loop by including a request for the current state.. - */ - @Override - protected void pollingCode() { - pollData(); - pollState(); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardWaterMeterHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardWaterMeterHandler.java deleted file mode 100644 index 5fcc2ac7a8..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/HomeWizardWaterMeterHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * 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.homewizard.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; - -/** - * The {@link HomeWizardWaterMeterHandler} implements functionality to handle a HomeWizard Watermeter. - * - * @author Daniël van Os - Initial contribution - */ -@NonNullByDefault -public class HomeWizardWaterMeterHandler extends HomeWizardDeviceHandler { - - /** - * Constructor - * - * @param thing The thing to handle - */ - public HomeWizardWaterMeterHandler(Thing thing) { - super(thing); - } - - /** - * Not listening to any commands. - */ - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } - - /** - * Device specific handling of the returned payload. - * - * @param payload The data parsed from the Json file - */ - @Override - protected void handleDataPayload(DataPayload payload) { - updateState(HomeWizardBindingConstants.CHANNEL_CURRENT_WATER, - new QuantityType<>(payload.getCurrentWaterLPM(), Units.LITRE_PER_MINUTE)); - updateState(HomeWizardBindingConstants.CHANNEL_TOTAL_WATER, - new QuantityType<>(payload.getTotalWaterM3(), SIUnits.CUBIC_METRE)); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/StatePayload.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/StatePayload.java deleted file mode 100644 index 8235dd34ba..0000000000 --- a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/StatePayload.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * 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.homewizard.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.google.gson.annotations.SerializedName; - -/** - * Class that provides storage for the json object obtained from the HomeWizard device State API - * - * @author Daniël van Os - Initial contribution - * - */ -@NonNullByDefault -public class StatePayload { - @SerializedName("power_on") - private boolean powerOn; - @SerializedName("switch_lock") - private boolean switchLock; - private int brightness = 0; - - /** - * Getter for the power_on field - * - * @return true if the device is currently on, false if it is off - */ - public boolean getPowerOn() { - return powerOn; - } - - /** - * Setter for the power_on field - * - * @param powerOn true to turn the device on, false to turn it off - */ - public void setPowerOn(boolean powerOn) { - this.powerOn = powerOn; - } - - /** - * Getter for the switch_lock field - * - * @return true if the device currently locked, false if it is not - */ - public boolean getSwitchLock() { - return switchLock; - } - - /** - * Setter for the power_on field - * - * @param switchLock true to lock the device, false to unlock it - */ - public void setSwitchLock(boolean switchLock) { - this.switchLock = switchLock; - } - - /** - * Getter for the ring brightness - * - * @return ring brightness percentage - */ - public int getBrightness() { - return brightness; - } - - /** - * Setter for the ring brightness - * - * @param brightness ring brightness - */ - public void setBrightness(int brightness) { - this.brightness = brightness; - } - - @Override - public String toString() { - return String.format("State [power_on: %b switch_lock: %b brightness: %d]", powerOn, switchLock, brightness); - } -} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/DataPayload.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/DataPayload.java new file mode 100644 index 0000000000..e6154bebfb --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/DataPayload.java @@ -0,0 +1,356 @@ +/** + * 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.homewizard.internal.dto; + +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * Class that provides storage for the json objects obtained from HomeWizard devices. + * + * @author Daniël van Os - Initial contribution + * @author Leo Siepel - Clean-up and additional fields + * + */ +@NonNullByDefault +public class DataPayload { + private int smrVersion = 0; + private String meterModel = ""; + private String wifiSsid = ""; + private int wifiStrength = 0; + + @SerializedName("total_power_import_t1_kwh") + private double totalEnergyImportT1Kwh; + @SerializedName("total_power_import_t2_kwh") + private double totalEnergyImportT2Kwh; + @SerializedName("total_power_export_t1_kwh") + private double totalEnergyExportT1Kwh; + @SerializedName("total_power_export_t2_kwh") + private double totalEnergyExportT2Kwh; + + private int activePowerW; + private int activePowerL1W; + private int activePowerL2W; + private int activePowerL3W; + private double totalGasM3; + private long gasTimestamp = 0; + + @SerializedName("any_power_fail_count") + private int anyPowerFailCount; + + @SerializedName("long_power_fail_count") + private int longPowerFailCount; + + @SerializedName("active_voltage_v") + private int activeVoltage; + @SerializedName("active_voltage_l1_v") + private int activeVoltageL1; + @SerializedName("active_voltage_l2_v") + private int activeVoltageL2; + @SerializedName("active_voltage_l3_v") + private int activeVoltageL3; + + @SerializedName("active_current_a") + private double activeCurrent; + @SerializedName("active_current_l1_a") + private double activeCurrentL1; + @SerializedName("active_current_l2_a") + private double activeCurrentL2; + @SerializedName("active_current_l3_a") + private double activeCurrentL3; + + @SerializedName("total_liter_m3") + private double totalWaterM3; + @SerializedName("active_liter_lpm") + private double currentWaterLPM; + + /** + * Getter for the smart meter version + * + * @return The most recent smart meter version obtained from the API + */ + public int getSmrVersion() { + return smrVersion; + } + + /** + * Getter for the meter model + * + * @return meter model + */ + public String getMeterModel() { + return meterModel; + } + + /** + * Getter for the meter's wifi ssid + * + * @return the meter's wifi sid + */ + public String getWifiSsid() { + return wifiSsid; + } + + /** + * Getter for the wifi rssi + * + * @return wifi rssi + */ + public int getWifiStrength() { + return wifiStrength; + } + + /** + * Getter for the total imported energy on counter 1 + * + * @return total imported energy on counter 1 + */ + public double getTotalEnergyImportT1Kwh() { + return totalEnergyImportT1Kwh; + } + + /** + * Getter for the total imported energy on counter 2 + * + * @return total imported energy on counter 2 + */ + public double getTotalEnergyImportT2Kwh() { + return totalEnergyImportT2Kwh; + } + + /** + * Getter for the total exported energy on counter 1 + * + * @return total exported energy on counter 1 + */ + public double getTotalEnergyExportT1Kwh() { + return totalEnergyExportT1Kwh; + } + + /** + * Getter for the total exported energy on counter 2 + * + * @return total exported energy on counter 2 + */ + public double getTotalEnergyExportT2Kwh() { + return totalEnergyExportT2Kwh; + } + + /** + * Getter for the count of any power failures + * + * @return count of any power failures + */ + public int getAnyPowerFailCount() { + return anyPowerFailCount; + } + + /** + * Getter for the count of long power failures + * + * @return count of long power failures + */ + public int getLongPowerFailCount() { + return longPowerFailCount; + } + + /** + * Getter for the active voltage + * + * @return current active voltage + */ + public int getActiveVoltage() { + return activeVoltage; + } + + /** + * Getter for the active voltage on phase 1 + * + * @return active voltage on phase 1 + */ + public int getActiveVoltageL1() { + return activeVoltageL1; + } + + /** + * Getter for the active voltage on phase 2 + * + * @return active voltage on phase 2 + */ + public int getActiveVoltageL2() { + return activeVoltageL2; + } + + /** + * Getter for the active voltage on phase 3 + * + * @return active voltage on phase 3 + */ + public int getActiveVoltageL3() { + return activeVoltageL3; + } + + /** + * Getter for the active current (sum of all phases) + * + * @return active current (all phases) + */ + public double getActiveCurrent() { + return activeCurrent; + } + + /** + * Getter for the active current on phase 1 + * + * @return active current on phase 1 + */ + public double getActiveCurrentL1() { + return activeCurrentL1; + } + + /** + * Getter for the active current on phase 2 + * + * @return active current on phase 2 + */ + public double getActiveCurrentL2() { + return activeCurrentL2; + } + + /** + * Getter for the active current on phase 3 + * + * @return active current on phase 3 + */ + public double getActiveCurrentL3() { + return activeCurrentL3; + } + + /** + * Getter for the current active total power + * + * @return current active total power + */ + public int getActivePowerW() { + return activePowerW; + } + + /** + * Getter for the current active total power on phase 2 + * + * @return current active total power on phase 2 + */ + public int getActivePowerL1W() { + return activePowerL1W; + } + + /** + * Getter for the current active total power on phase 2 + * + * @return current active total power on phase 2 + */ + public int getActivePowerL2W() { + return activePowerL2W; + } + + /** + * Getter for the current active total power on phase 3 + * + * @return current active total power on phase 3 + */ + public int getActivePowerL3W() { + return activePowerL3W; + } + + /** + * Getter for the total imported gas volume + * + * @return total imported gas volume + */ + public double getTotalGasM3() { + return totalGasM3; + } + + /** + * Getter for the time stamp of the last gas update + * + * @param zoneId The time zone id for the return value, falls back to systemDefault() when null + * @return time stamp of the last gas update as ZonedDateTime + * @throws DateTimeException When the method fails to create a ZonedDateTime + */ + public @Nullable ZonedDateTime getGasTimestamp(@Nullable ZoneId zoneId) throws DateTimeException { + ZoneId timeZoneId = zoneId == null ? ZoneId.systemDefault() : zoneId; + long dtv = gasTimestamp; + if (dtv < 1) { + return null; + } + + // 210119164000 + int seconds = (int) (dtv % 100); + + dtv /= 100; + int minutes = (int) (dtv % 100); + + dtv /= 100; + int hours = (int) (dtv % 100); + + dtv /= 100; + int day = (int) (dtv % 100); + + dtv /= 100; + int month = (int) (dtv % 100); + + dtv /= 100; + int year = (int) (dtv + 2000); + + return ZonedDateTime.of(year, month, day, hours, minutes, seconds, 0, timeZoneId); + } + + /** + * Getter for the total imported water volume + * + * @return total imported water volume + */ + public double getTotalWaterM3() { + return totalWaterM3; + } + + /** + * Getter for the current water flow + * + * @return current water flow + */ + public double getCurrentWaterLPM() { + return currentWaterLPM; + } + + @Override + public String toString() { + return String.format( + """ + Data [smrVersion: %d meterModel: %s wifiSsid: %s wifiStrength: %d" + totalEnergyImportT1Kwh: %f totalEnergyImportT2Kwh: %f totalEnergyExportT1Kwh: %f totalEnergyExportT2Kwh: %f" + activePowerW: %f activePowerL1W: %f activePowerL2W: %f activePowerL3W: %f totalGasM3: %f gasTimestamp: %.0f" + totalWaterM3: %f currentWaterLPM: %f] + """, + smrVersion, meterModel, wifiSsid, wifiStrength, totalEnergyImportT1Kwh, totalEnergyImportT2Kwh, + totalEnergyExportT1Kwh, totalEnergyExportT2Kwh, activePowerW, activePowerL1W, activePowerL2W, + activePowerL3W, totalGasM3, gasTimestamp, totalWaterM3, currentWaterLPM); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/StatePayload.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/StatePayload.java new file mode 100644 index 0000000000..76d3102649 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/dto/StatePayload.java @@ -0,0 +1,91 @@ +/** + * 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.homewizard.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * Class that provides storage for the json object obtained from the HomeWizard device State API + * + * @author Daniël van Os - Initial contribution + * + */ +@NonNullByDefault +public class StatePayload { + @SerializedName("power_on") + private boolean powerOn; + @SerializedName("switch_lock") + private boolean switchLock; + private int brightness = 0; + + /** + * Getter for the power_on field + * + * @return true if the device is currently on, false if it is off + */ + public boolean getPowerOn() { + return powerOn; + } + + /** + * Setter for the power_on field + * + * @param powerOn true to turn the device on, false to turn it off + */ + public void setPowerOn(boolean powerOn) { + this.powerOn = powerOn; + } + + /** + * Getter for the switch_lock field + * + * @return true if the device currently locked, false if it is not + */ + public boolean getSwitchLock() { + return switchLock; + } + + /** + * Setter for the power_on field + * + * @param switchLock true to lock the device, false to unlock it + */ + public void setSwitchLock(boolean switchLock) { + this.switchLock = switchLock; + } + + /** + * Getter for the ring brightness + * + * @return ring brightness percentage + */ + public int getBrightness() { + return brightness; + } + + /** + * Setter for the ring brightness + * + * @param brightness ring brightness + */ + public void setBrightness(int brightness) { + this.brightness = brightness; + } + + @Override + public String toString() { + return String.format("State [power_on: %b switch_lock: %b brightness: %d]", powerOn, switchLock, brightness); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardDeviceHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardDeviceHandler.java new file mode 100644 index 0000000000..984ca85374 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardDeviceHandler.java @@ -0,0 +1,162 @@ +/** + * 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.homewizard.internal.handler; + +import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.homewizard.internal.HomeWizardConfiguration; +import org.openhab.binding.homewizard.internal.dto.DataPayload; +import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * The {@link HomeWizardDeviceHandler} is a base class for all + * HomeWizard devices. It provides configuration and polling of + * data from a device. It also processes common data. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +public abstract class HomeWizardDeviceHandler extends BaseThingHandler { + + protected final Logger logger = LoggerFactory.getLogger(HomeWizardDeviceHandler.class); + protected final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + protected ScheduledExecutorService executorService = this.scheduler; + private HomeWizardConfiguration config = new HomeWizardConfiguration(); + private @Nullable ScheduledFuture pollingJob; + + protected String apiURL = ""; + + /** + * Constructor + * + * @param thing The thing to handle + */ + public HomeWizardDeviceHandler(Thing thing) { + super(thing); + } + + /** + * If a host has been specified start polling it + */ + @Override + public void initialize() { + config = getConfigAs(HomeWizardConfiguration.class); + if (configure()) { + pollingJob = executorService.scheduleWithFixedDelay(this::pollingCode, 0, config.refreshDelay, + TimeUnit.SECONDS); + } + } + + /** + * Check the current configuration + * + * @return true if the configuration is ok to start polling, false otherwise + */ + private boolean configure() { + if (config.ipAddress.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Missing ipAddress/host configuration"); + return false; + } else { + updateStatus(ThingStatus.UNKNOWN); + apiURL = String.format("http://%s/api/v1/", config.ipAddress.trim()); + return true; + } + } + + /** + * dispose: stop the poller + */ + @Override + public void dispose() { + var job = pollingJob; + if (job != null) { + job.cancel(true); + } + pollingJob = null; + } + + /** + * Device specific handling of the returned data payload. + * + * @param payload The data parsed from the data Json file + */ + protected abstract void handleDataPayload(DataPayload payload); + + /** + * @return json response from the remote server + * @throws IOException + */ + public String getData() throws IOException { + return HttpUtil.executeUrl("GET", apiURL + "data", 30000); + } + + /** + * + */ + protected void pollData() { + final String dataResult; + + try { + dataResult = getData(); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + String.format("Unable to query device data: %s", e.getMessage())); + return; + } + + if (dataResult.trim().isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device returned empty data"); + return; + } + + DataPayload dataPayload = gson.fromJson(dataResult, DataPayload.class); + if (dataPayload == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Unable to parse data response from device"); + return; + } + + if ("".equals(dataPayload.getWifiSsid())) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Results from API are empty"); + return; + } + + updateStatus(ThingStatus.ONLINE); + handleDataPayload(dataPayload); + } + + /** + * The actual polling loop + */ + protected void pollingCode() { + pollData(); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardEnergySocketHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardEnergySocketHandler.java new file mode 100644 index 0000000000..aaac22c050 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardEnergySocketHandler.java @@ -0,0 +1,141 @@ +/** + * 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.homewizard.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.homewizard.internal.HomeWizardBindingConstants; +import org.openhab.binding.homewizard.internal.dto.DataPayload; +import org.openhab.binding.homewizard.internal.dto.StatePayload; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; + +/** + * The {@link HomeWizardEnergySocketHandler} implements functionality to handle a HomeWizard EnergySocket. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +public class HomeWizardEnergySocketHandler extends HomeWizardStatefulDeviceHandler { + + /** + * Constructor + * + * @param thing The thing to handle + * @param timeZoneProvider The TimeZoneProvider + */ + public HomeWizardEnergySocketHandler(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider); + } + + /** + * Converts a brightness value (0..255) to a percentage. + * + * @param brightness The brightness to convert. + * @return brightness percentage + */ + private int brightnessToPercentage(int brightness) { + return (int) (100.0 * brightness / 255.0 + 0.5); + } + + /** + * Converts a percentage to a brightness value (0..255) + * + * @param percentage The percentage to convert. + * @return brightness value + */ + private int percentageToBrightness(String percentage) { + return (int) (Double.valueOf(percentage) * 255.0 / 100.0 + 0.5); + } + + /** + * Handle incoming commands. + * + * Power on/off, Power lock/unlock and Ring brightness are supported. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + // For now I prefer not updating immediately above firing a full update request for each channel + return; + } + + StatePayload result = null; + + /* + * The returned payloads below only contain the modified value, so each has it's own + * call to updateState instead of just calling handleStatePayload() with the returned + * payload. + */ + + switch (channelUID.getId()) { + case HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS: { + result = sendStateCommand( + String.format("{\"brightness\": %d}", percentageToBrightness(command.toFullString()))); + if (result != null) { + updateState(HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS, + new PercentType(brightnessToPercentage(result.getBrightness()))); + } + break; + } + case HomeWizardBindingConstants.CHANNEL_POWER_SWITCH: { + boolean onOff = command.equals(OnOffType.ON); + result = sendStateCommand(String.format("{\"power_on\": %b}", onOff)); + if (result != null) { + updateState(HomeWizardBindingConstants.CHANNEL_POWER_SWITCH, OnOffType.from(result.getPowerOn())); + } + break; + } + case HomeWizardBindingConstants.CHANNEL_POWER_LOCK: { + boolean onOff = command.equals(OnOffType.ON); + result = sendStateCommand(String.format("{\"switch_lock\": %b}", onOff)); + if (result != null) { + updateState(HomeWizardBindingConstants.CHANNEL_POWER_LOCK, OnOffType.from(result.getSwitchLock())); + } + break; + } + default: + logger.warn("Should handle {} {}", channelUID.getIdWithoutGroup(), command); + break; + } + } + + /** + * Device specific handling of the returned payload. + * + * @param payload The data parsed from the Json file + */ + @Override + protected void handleDataPayload(DataPayload payload) { + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1, + new QuantityType<>(payload.getTotalEnergyImportT1Kwh(), Units.KILOWATT_HOUR)); + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1, + new QuantityType<>(payload.getTotalEnergyExportT1Kwh(), Units.KILOWATT_HOUR)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER, + new QuantityType<>(payload.getActivePowerW(), Units.WATT)); + } + + @Override + protected void handleStatePayload(StatePayload payload) { + updateState(HomeWizardBindingConstants.CHANNEL_POWER_SWITCH, OnOffType.from(payload.getPowerOn())); + updateState(HomeWizardBindingConstants.CHANNEL_POWER_LOCK, OnOffType.from(payload.getSwitchLock())); + updateState(HomeWizardBindingConstants.CHANNEL_RING_BRIGHTNESS, + new PercentType(brightnessToPercentage(payload.getBrightness()))); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardHandlerFactory.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardHandlerFactory.java new file mode 100644 index 0000000000..2eb4a8863c --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardHandlerFactory.java @@ -0,0 +1,74 @@ +/** + * 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.homewizard.internal.handler; + +import static org.openhab.binding.homewizard.internal.HomeWizardBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link HomeWizardHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.homewizard", service = ThingHandlerFactory.class) +public class HomeWizardHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_P1_METER, + THING_TYPE_ENERGY_SOCKET, THING_TYPE_WATERMETER); + + private final TimeZoneProvider timeZoneProvider; + + @Activate + public HomeWizardHandlerFactory(final @Reference TimeZoneProvider timeZoneProvider) { + this.timeZoneProvider = timeZoneProvider; + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_P1_METER.equals(thingTypeUID)) { + return new HomeWizardP1MeterHandler(thing, timeZoneProvider); + } + + if (THING_TYPE_ENERGY_SOCKET.equals(thingTypeUID)) { + return new HomeWizardEnergySocketHandler(thing, timeZoneProvider); + } + + if (THING_TYPE_WATERMETER.equals(thingTypeUID)) { + return new HomeWizardWaterMeterHandler(thing, timeZoneProvider); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardP1MeterHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardP1MeterHandler.java new file mode 100644 index 0000000000..7419ba4603 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardP1MeterHandler.java @@ -0,0 +1,131 @@ +/** + * 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.homewizard.internal.handler; + +import java.time.DateTimeException; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.homewizard.internal.HomeWizardBindingConstants; +import org.openhab.binding.homewizard.internal.dto.DataPayload; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link HomeWizardP1MeterHandler} implements functionality to handle a HomeWizard P1 Meter. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +public class HomeWizardP1MeterHandler extends HomeWizardDeviceHandler { + + private String meterModel = ""; + private int meterVersion = 0; + private TimeZoneProvider timeZoneProvider; + + /** + * Constructor + * + * @param thing The thing to handle + * @param timeZoneProvider The TimeZoneProvider + */ + public HomeWizardP1MeterHandler(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing); + this.timeZoneProvider = timeZoneProvider; + } + + /** + * Not listening to any commands. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + /** + * Device specific handling of the returned payload. + * + * @param payload The data parsed from the Json file + */ + @Override + protected void handleDataPayload(DataPayload payload) { + if (!meterModel.equals(payload.getMeterModel())) { + meterModel = payload.getMeterModel(); + updateProperty(HomeWizardBindingConstants.PROPERTY_METER_MODEL, meterModel); + } + + if (meterVersion != payload.getSmrVersion()) { + meterVersion = payload.getSmrVersion(); + updateProperty(HomeWizardBindingConstants.PROPERTY_METER_VERSION, String.format("%d", meterVersion)); + } + + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1, + new QuantityType<>(payload.getTotalEnergyImportT1Kwh(), Units.KILOWATT_HOUR)); + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T2, + new QuantityType<>(payload.getTotalEnergyImportT2Kwh(), Units.KILOWATT_HOUR)); + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1, + new QuantityType<>(payload.getTotalEnergyExportT1Kwh(), Units.KILOWATT_HOUR)); + updateState(HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T2, + new QuantityType<>(payload.getTotalEnergyExportT2Kwh(), Units.KILOWATT_HOUR)); + + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER, + new QuantityType<>(payload.getActivePowerW(), Units.WATT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L1, + new QuantityType<>(payload.getActivePowerL1W(), Units.WATT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L2, + new QuantityType<>(payload.getActivePowerL2W(), Units.WATT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3, + new QuantityType<>(payload.getActivePowerL3W(), Units.WATT)); + + updateState(HomeWizardBindingConstants.CHANNEL_POWER_FAILURES, new DecimalType(payload.getAnyPowerFailCount())); + updateState(HomeWizardBindingConstants.CHANNEL_LONG_POWER_FAILURES, + new DecimalType(payload.getLongPowerFailCount())); + + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT, + new QuantityType<>(payload.getActiveCurrent(), Units.AMPERE)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L1, + new QuantityType<>(payload.getActiveCurrentL1(), Units.AMPERE)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L2, + new QuantityType<>(payload.getActiveCurrentL2(), Units.AMPERE)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L3, + new QuantityType<>(payload.getActiveCurrentL3(), Units.AMPERE)); + + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE, + new QuantityType<>(payload.getActiveVoltage(), Units.VOLT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L1, + new QuantityType<>(payload.getActiveVoltageL1(), Units.VOLT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L2, + new QuantityType<>(payload.getActiveVoltageL2(), Units.VOLT)); + updateState(HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L3, + new QuantityType<>(payload.getActiveVoltageL3(), Units.VOLT)); + + ZonedDateTime gasTimestamp; + try { + gasTimestamp = payload.getGasTimestamp(timeZoneProvider.getTimeZone()); + } catch (DateTimeException e) { + logger.warn("Unable to parse Gas timestamp: {}", e.getMessage()); + gasTimestamp = null; + } + if (gasTimestamp != null) { + updateState(HomeWizardBindingConstants.CHANNEL_GAS_TOTAL, + new QuantityType<>(payload.getTotalGasM3(), SIUnits.CUBIC_METRE)); + updateState(HomeWizardBindingConstants.CHANNEL_GAS_TIMESTAMP, new DateTimeType(gasTimestamp)); + } + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardStatefulDeviceHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardStatefulDeviceHandler.java new file mode 100644 index 0000000000..9afd528afb --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardStatefulDeviceHandler.java @@ -0,0 +1,104 @@ +/** + * 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.homewizard.internal.handler; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.homewizard.internal.dto.StatePayload; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; + +/** + * The {@link HomeWizardStatefulDeviceHandler} extends the base class + * to provide support for devices that also have a 'state' interface. + * This interface can be used to query and control the state of a device. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +public abstract class HomeWizardStatefulDeviceHandler extends HomeWizardP1MeterHandler { + + /** + * Constructor + * + * @param thing The thing to handle + * @param timeZoneProvider The TimeZoneProvider + */ + public HomeWizardStatefulDeviceHandler(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider); + } + + /** + * Device specific handling of the returned state payload. + * + * @param payload The data parsed from the state Json file + */ + protected abstract void handleStatePayload(StatePayload payload); + + protected void pollState() { + final String stateResult; + + try { + stateResult = HttpUtil.executeUrl("GET", apiURL + "state", 30000); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + String.format("Unable to query device state: %s", e.getMessage())); + return; + } + + if (stateResult.trim().isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device returned empty state"); + return; + } + + StatePayload statePayload = gson.fromJson(stateResult, StatePayload.class); + if (statePayload == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Unable to parse state response from device"); + return; + } + + handleStatePayload(statePayload); + } + + /** + * Sends a command to the state interface of the device. + * + * @param command The command to send. + */ + protected @Nullable StatePayload sendStateCommand(String command) { + try (InputStream is = new ByteArrayInputStream(command.getBytes())) { + String updatedState = HttpUtil.executeUrl("PUT", apiURL + "state", is, "application/json", 30000); + return gson.fromJson(updatedState, StatePayload.class); + } catch (IOException e) { + logger.warn("Failed to send command {} to {}", command, apiURL + "state"); + return null; + } + } + + /* + * This overrides the original polling loop by including a request for the current state.. + */ + @Override + protected void pollingCode() { + pollData(); + pollState(); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardWaterMeterHandler.java b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardWaterMeterHandler.java new file mode 100644 index 0000000000..fd46bdd1aa --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/java/org/openhab/binding/homewizard/internal/handler/HomeWizardWaterMeterHandler.java @@ -0,0 +1,63 @@ +/** + * 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.homewizard.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.homewizard.internal.HomeWizardBindingConstants; +import org.openhab.binding.homewizard.internal.dto.DataPayload; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link HomeWizardWaterMeterHandler} implements functionality to handle a HomeWizard Watermeter. + * + * @author Daniël van Os - Initial contribution + */ +@NonNullByDefault +public class HomeWizardWaterMeterHandler extends HomeWizardP1MeterHandler { + + /** + * Constructor + * + * @param thing The thing to handle + * @param timeZoneProvider The TimeZoneProvider + */ + public HomeWizardWaterMeterHandler(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider); + } + + /** + * Not listening to any commands. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + /** + * Device specific handling of the returned payload. + * + * @param payload The data parsed from the Json file + */ + @Override + protected void handleDataPayload(DataPayload payload) { + updateState(HomeWizardBindingConstants.CHANNEL_CURRENT_WATER, + new QuantityType<>(payload.getCurrentWaterLPM(), Units.LITRE_PER_MINUTE)); + updateState(HomeWizardBindingConstants.CHANNEL_TOTAL_WATER, + new QuantityType<>(payload.getTotalWaterM3(), SIUnits.CUBIC_METRE)); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/i18n/homewizard.properties b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/i18n/homewizard.properties index 867755a84c..2d38c10234 100644 --- a/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/i18n/homewizard.properties +++ b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/i18n/homewizard.properties @@ -9,6 +9,17 @@ thing-type.homewizard.energy_socket.label = HomeWizard Energysocket thing-type.homewizard.energy_socket.description = This thing provides the measurement data that is available through the http interface of a HomeWizard Energysocket. thing-type.homewizard.p1_wifi_meter.label = HomeWizard Wi-Fi P1 Meter thing-type.homewizard.p1_wifi_meter.description = This thing provides the measurement data that is available through the http interface of the HomeWizard Wi-Fi P1 Meter. +thing-type.homewizard.p1_wifi_meter.channel.active_current.label = Current +thing-type.homewizard.p1_wifi_meter.channel.active_current.description = The sum of the current for all phases +thing-type.homewizard.p1_wifi_meter.channel.active_current_l1.label = Current L1 +thing-type.homewizard.p1_wifi_meter.channel.active_current_l2.label = Current L2 +thing-type.homewizard.p1_wifi_meter.channel.active_current_l3.label = Current L3 +thing-type.homewizard.p1_wifi_meter.channel.active_voltage.label = Active Voltage +thing-type.homewizard.p1_wifi_meter.channel.active_voltage_l1.label = Active Voltage L1 +thing-type.homewizard.p1_wifi_meter.channel.active_voltage_l2.label = Active Voltage L2 +thing-type.homewizard.p1_wifi_meter.channel.active_voltage_l3.label = Active Voltage L3 +thing-type.homewizard.p1_wifi_meter.channel.long_power_failures.label = Long Power Failures +thing-type.homewizard.p1_wifi_meter.channel.long_power_failures.description = This channel provides the count of long power failures. thing-type.homewizard.watermeter.label = HomeWizard Wi-Fi Watermeter thing-type.homewizard.watermeter.description = This thing provides the measurement data that is available through the http interface of a HomeWizard Watermeter. @@ -41,6 +52,8 @@ channel-type.homewizard.current_water.label = Current Water Rate channel-type.homewizard.current_water.description = This channel provides the most recently reported current water usage in liters per minute. channel-type.homewizard.gas_timestamp.label = Gas Update Time Stamp channel-type.homewizard.gas_timestamp.description = This channel provides the time stamp of the total_gas measurement. +channel-type.homewizard.power_failures.label = Power Failures +channel-type.homewizard.power_failures.description = This channel provides the count of any type of power failure. channel-type.homewizard.power_lock.label = Power Lock channel-type.homewizard.power_lock.description = This channel provides access to the power lock of the Energysocket channel-type.homewizard.power_switch.label = Power Switch diff --git a/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/thing/thing-types.xml index 14982a4185..2b26551e2d 100644 --- a/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,27 +4,60 @@ 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"> - This thing provides the measurement data that is available through the http interface of the HomeWizard Wi-Fi P1 Meter. - - - - + + + The sum of the current for all phases + + + + + + + + + + + + + + + + + + + + + + + + + + + This channel provides the count of long power failures. + + + + + + + Unknown + 1 @@ -98,6 +131,13 @@ + + + Number + + This channel provides the count of any type of power failure. + + Number:Energy diff --git a/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 0000000000..168fc512a0 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,57 @@ + + + + + + + + system:electric-voltage + + + system:electric-voltage + + + system:electric-voltage + + + system:electric-voltage + + + system:electric-current + + + system:electric-current + + + system:electric-current + + + system:electric-current + + + homewizard:power_failures + + + homewizard:power_failures + + This channel provides the count of long power failures. + + + homewizard:total_energy_import_t1 + + + homewizard:total_energy_import_t2 + + + homewizard:total_energy_export_t1 + + + homewizard:total_energy_export_t2 + + + + + + diff --git a/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerMock.java b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerMock.java new file mode 100644 index 0000000000..224a367f42 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerMock.java @@ -0,0 +1,46 @@ +/** + * 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.homewizard.internal; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.openhab.binding.homewizard.internal.handler.HomeWizardP1MeterHandler; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.thing.Thing; + +/** + * The {@link HomeWizardP1MeterHandlerMock} is responsible for mocking {@link HomeWizardP1MeterHandler} + * + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class HomeWizardP1MeterHandlerMock extends HomeWizardP1MeterHandler { + + public HomeWizardP1MeterHandlerMock(Thing thing, TimeZoneProvider timeZoneProvider) { + super(thing, timeZoneProvider); + + executorService = Mockito.mock(ScheduledExecutorService.class); + doAnswer((InvocationOnMock invocation) -> { + ((Runnable) invocation.getArguments()[0]).run(); + return null; + }).when(executorService).scheduleWithFixedDelay(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class)); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerTest.java b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerTest.java new file mode 100644 index 0000000000..30fb17e306 --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/HomeWizardP1MeterHandlerTest.java @@ -0,0 +1,200 @@ +/** + * 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.homewizard.internal; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.openhab.binding.homewizard.internal.dto.DataUtil; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +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.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; + +/** + * Tests for the HomeWizard Handler + * + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class HomeWizardP1MeterHandlerTest { + + private static final Configuration CONFIG = createConfig(); + + private static Configuration createConfig() { + final Configuration config = new Configuration(); + config.put("ipAddress", "1.2.3.4"); + return config; + } + + private static Thing mockThing() { + final Thing thing = mock(Thing.class); + when(thing.getUID()) + .thenReturn(new ThingUID(HomeWizardBindingConstants.THING_TYPE_P1_METER, "homewizard-test-thing")); + when(thing.getConfiguration()).thenReturn(CONFIG); + + final List channelList = Arrays.asList( + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L1), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L3), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L1), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L2), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L1), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L2), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_POWER_FAILURES), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_LONG_POWER_FAILURES), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T2), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T2), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_GAS_TIMESTAMP), // + mockChannel(thing.getUID(), HomeWizardBindingConstants.CHANNEL_GAS_TOTAL)); + + when(thing.getChannels()).thenReturn(channelList); + return thing; + } + + private static Channel mockChannel(final ThingUID thingId, final String channelId) { + final Channel channel = Mockito.mock(Channel.class); + when(channel.getUID()).thenReturn(new ChannelUID(thingId, channelId)); + return channel; + } + + private static HomeWizardP1MeterHandlerMock createAndInitHandler(final ThingHandlerCallback callback, + final Thing thing) { + final TimeZoneProvider timeZoneProvider = mock(TimeZoneProvider.class); + doReturn(ZoneId.systemDefault()).when(timeZoneProvider).getTimeZone(); + final HomeWizardP1MeterHandlerMock handler = spy(new HomeWizardP1MeterHandlerMock(thing, timeZoneProvider)); + + try { + doReturn(DataUtil.fromFile("response.json")).when(handler).getData(); + } catch (IOException e) { + assertFalse(true); + } + + handler.setCallback(callback); + handler.initialize(); + return handler; + } + + private static State getState(final int input) { + return new DecimalType(input); + } + + private static State getState(final int input, Unit unit) { + return new QuantityType<>(input, unit); + } + + private static State getState(final double input, Unit unit) { + return new QuantityType<>(input, unit); + } + + @Test + public void testUpdateChannels() { + final Thing thing = mockThing(); + final ThingHandlerCallback callback = mock(ThingHandlerCallback.class); + final HomeWizardP1MeterHandlerMock handler = createAndInitHandler(callback, thing); + + try { + verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN))); + verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE))); + + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT), + getState(567.0, Units.AMPERE)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L1), + getState(-4.0, Units.AMPERE)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L2), + getState(2.0, Units.AMPERE)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_CURRENT_L3), + getState(333.0, Units.AMPERE)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER), + getState(-543, Units.WATT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L1), + getState(-676, Units.WATT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L2), + getState(133, Units.WATT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_POWER_L3), + getState(18, Units.WATT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE), + getState(220, Units.VOLT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L1), + getState(221, Units.VOLT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L2), + getState(222, Units.VOLT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ACTIVE_VOLTAGE_L3), + getState(223, Units.VOLT)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T1), + getState(8874.0, Units.KILOWATT_HOUR)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_EXPORT_T2), + getState(7788.0, Units.KILOWATT_HOUR)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T1), + getState(10830.511, Units.KILOWATT_HOUR)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_ENERGY_IMPORT_T2), + getState(2948.827, Units.KILOWATT_HOUR)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_POWER_FAILURES), getState(7)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_LONG_POWER_FAILURES), + getState(2)); + verify(callback).stateUpdated( + new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_GAS_TIMESTAMP), + new DateTimeType(ZonedDateTime.of(2021, 6, 06, 14, 0, 10, 0, ZoneId.systemDefault()))); + verify(callback).stateUpdated(new ChannelUID(thing.getUID(), HomeWizardBindingConstants.CHANNEL_GAS_TOTAL), + getState(2569.646, SIUnits.CUBIC_METRE)); + } finally { + handler.dispose(); + } + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/DataUtil.java b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/DataUtil.java new file mode 100644 index 0000000000..d0396e8e9d --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/DataUtil.java @@ -0,0 +1,64 @@ +/** + * 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.homewizard.internal.dto; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Utility class for working with test data in unit tests + * + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class DataUtil { + + private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + @SuppressWarnings("null") + public static Reader openDataReader(String fileName) throws FileNotFoundException { + String packagePath = (DataUtil.class.getPackage().getName()).replace(".", "/"); + String filePath = "src/test/resources/" + packagePath + "/" + fileName; + + InputStream inputStream = new FileInputStream(filePath); + return new InputStreamReader(inputStream, StandardCharsets.UTF_8); + } + + public T fromJson(String fileName, Type typeOfT) throws IOException { + try (Reader reader = openDataReader(fileName)) { + return gson.fromJson(reader, typeOfT); + } + } + + @SuppressWarnings("null") + public static String fromFile(String fileName) throws IOException { + try (Reader reader = openDataReader(fileName)) { + return new BufferedReader(reader).lines().parallel().collect(Collectors.joining("\n")); + } + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/P1PayloadTest.java b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/P1PayloadTest.java new file mode 100644 index 0000000000..cf7bdbb59b --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/java/org/openhab/binding/homewizard/internal/dto/P1PayloadTest.java @@ -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.homewizard.internal.dto; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * Tests deserialization of HomeWizard API responses from JSON. + * + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class P1PayloadTest { + + private static final DataUtil DATA_UTIL = new DataUtil(); + + @Test + public void deserializeResponse() throws IOException { + DataPayload key = DATA_UTIL.fromJson("response.json", DataPayload.class); + assertThat(key, is(notNullValue())); + + assertThat(key.getActiveCurrent(), is(567.0)); + assertThat(key.getActiveCurrentL1(), is(-4.0)); + assertThat(key.getActiveCurrentL2(), is(2.0)); + assertThat(key.getActiveCurrentL3(), is(333.0)); + assertThat(key.getActivePowerW(), is(-543)); + assertThat(key.getActivePowerL1W(), is(-676)); + assertThat(key.getActivePowerL2W(), is(133)); + assertThat(key.getActivePowerL3W(), is(18)); + assertThat(key.getActiveVoltage(), is(220)); + assertThat(key.getActiveVoltageL1(), is(221)); + assertThat(key.getActiveVoltageL2(), is(222)); + assertThat(key.getActiveVoltageL3(), is(223)); + assertThat(key.getTotalEnergyExportT1Kwh(), is(8874.0)); + assertThat(key.getTotalEnergyExportT2Kwh(), is(7788.0)); + assertThat(key.getTotalEnergyImportT1Kwh(), is(10830.511)); + assertThat(key.getTotalEnergyImportT2Kwh(), is(2948.827)); + assertThat(key.getAnyPowerFailCount(), is(7)); + assertThat(key.getLongPowerFailCount(), is(2)); + assertThat(key.getGasTimestamp(ZoneId.systemDefault()), + is(ZonedDateTime.of(2021, 6, 06, 14, 0, 10, 0, ZoneId.systemDefault()))); + assertThat(key.getTotalGasM3(), is(2569.646)); + + assertThat(key.getMeterModel(), is("ISKRA 2M550T-101")); + assertThat(key.getSmrVersion(), is(50)); + assertThat(key.getWifiSsid(), is("My Wi-Fi")); + assertThat(key.getWifiStrength(), is(100)); + } + + @Test + public void deserializeResponseEmpty() throws IOException { + DataPayload key = DATA_UTIL.fromJson("response-empty.json", DataPayload.class); + assertThat(key, is(notNullValue())); + + assertThat(key.getActiveCurrent(), is(0.0)); + assertThat(key.getActiveCurrentL1(), is(0.0)); + assertThat(key.getActiveCurrentL2(), is(0.0)); + assertThat(key.getActiveCurrentL3(), is(0.0)); + assertThat(key.getActivePowerW(), is(0)); + assertThat(key.getActivePowerL1W(), is(0)); + assertThat(key.getActivePowerL2W(), is(0)); + assertThat(key.getActivePowerL3W(), is(0)); + assertThat(key.getActiveVoltage(), is(0)); + assertThat(key.getActiveVoltageL1(), is(0)); + assertThat(key.getActiveVoltageL2(), is(0)); + assertThat(key.getActiveVoltageL3(), is(0)); + assertThat(key.getAnyPowerFailCount(), is(0)); + assertThat(key.getLongPowerFailCount(), is(0)); + assertThat(key.getTotalEnergyExportT1Kwh(), is(0.0)); + assertThat(key.getTotalEnergyExportT2Kwh(), is(0.0)); + assertThat(key.getTotalEnergyImportT1Kwh(), is(0.0)); + assertThat(key.getTotalEnergyImportT2Kwh(), is(0.0)); + assertThat(key.getGasTimestamp(ZoneId.systemDefault()), is(nullValue())); + assertThat(key.getTotalGasM3(), is(0.0)); + + assertThat(key.getMeterModel(), is("")); + assertThat(key.getSmrVersion(), is(0)); + assertThat(key.getWifiSsid(), is("")); + assertThat(key.getWifiStrength(), is(0)); + } +} diff --git a/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response-empty.json b/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response-empty.json new file mode 100644 index 0000000000..7a73a41bfd --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response-empty.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response.json b/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response.json new file mode 100644 index 0000000000..5844ea388b --- /dev/null +++ b/bundles/org.openhab.binding.homewizard/src/test/resources/org/openhab/binding/homewizard/internal/dto/response.json @@ -0,0 +1,56 @@ +{ + "wifi_ssid": "My Wi-Fi", + "wifi_strength": 100, + "smr_version": 50, + "meter_model": "ISKRA 2M550T-101", + "unique_id": "00112233445566778899AABBCCDDEEFF", + "active_tariff": 2, + "total_power_import_kwh": 13779.338, + "total_power_import_t1_kwh": 10830.511, + "total_power_import_t2_kwh": 2948.827, + "total_power_export_kwh": 8877, + "total_power_export_t1_kwh": 8874, + "total_power_export_t2_kwh": 7788, + "active_power_w": -543, + "active_power_l1_w": -676, + "active_power_l2_w": 133, + "active_power_l3_w": 18, + "active_current_a": 567, + "active_current_l1_a": -4, + "active_current_l2_a": 2, + "active_current_l3_a": 333, + "active_voltage_v": 220, + "active_voltage_l1_v": 221, + "active_voltage_l2_v": 222, + "active_voltage_l3_v": 223, + "voltage_sag_l1_count": 1, + "voltage_sag_l2_count": 2, + "voltage_sag_l3_count": 3, + "voltage_swell_l1_count": 4, + "voltage_swell_l2_count": 5, + "voltage_swell_l3_count": 6, + "any_power_fail_count": 7, + "long_power_fail_count": 2, + "total_gas_m3": 2569.646, + "gas_timestamp": 210606140010, + "gas_unique_id": "FFEEDDCCBBAA99887766554433221100", + "active_power_average_w": 123.080, + "montly_power_peak_w": 1111.000, + "montly_power_peak_timestamp": 230101080010, + "external": [ + { + "unique_id": "FFEEDDCCBBAA99887766554433221100", + "type": "gas_meter", + "timestamp": 210606140010, + "value": 2569.646, + "unit": "m3" + }, + { + "unique_id": "ABCDEF0123456789ABCDEF0123456789", + "type": "water_meter", + "timestamp": 210606140015, + "value": 123.456, + "unit": "m3" + } + ] + } \ No newline at end of file