## 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
+++ /dev/null
-/**
- * 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);
- }
-}
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";
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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())));
- }
-}
+++ /dev/null
-/**
- * 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<ThingTypeUID> 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;
- }
-}
+++ /dev/null
-/**
- * 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());
- }
- }
- }
-}
+++ /dev/null
-/**
- * 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();
- }
-}
+++ /dev/null
-/**
- * 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));
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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())));
+ }
+}
--- /dev/null
+/**
+ * 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<ThingTypeUID> 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;
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
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.
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
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
<thing-type id="p1_wifi_meter">
<label>HomeWizard Wi-Fi P1 Meter</label>
<description>This thing provides the measurement data that is available through the http interface of the HomeWizard
Wi-Fi P1 Meter.</description>
<channels>
- <channel id="total_energy_import_t1" typeId="total_energy_import_t1"/>
- <channel id="total_energy_import_t2" typeId="total_energy_import_t2"/>
- <channel id="total_energy_export_t1" typeId="total_energy_export_t1"/>
- <channel id="total_energy_export_t2" typeId="total_energy_export_t2"/>
+ <channel id="active_current" typeId="system.electric-current">
+ <label>Current</label>
+ <description>The sum of the current for all phases</description>
+ </channel>
+ <channel id="active_current_l1" typeId="system.electric-current">
+ <label>Current L1</label>
+ </channel>
+ <channel id="active_current_l2" typeId="system.electric-current">
+ <label>Current L2</label>
+ </channel>
+ <channel id="active_current_l3" typeId="system.electric-current">
+ <label>Current L3</label>
+ </channel>
<channel id="active_power" typeId="active_power"/>
<channel id="active_power_l1" typeId="active_power_l1"/>
<channel id="active_power_l2" typeId="active_power_l2"/>
<channel id="active_power_l3" typeId="active_power_l3"/>
+ <channel id="active_voltage" typeId="system.electric-voltage">
+ <label>Active Voltage</label>
+ </channel>
+ <channel id="active_voltage_l1" typeId="system.electric-voltage">
+ <label>Active Voltage L1</label>
+ </channel>
+ <channel id="active_voltage_l2" typeId="system.electric-voltage">
+ <label>Active Voltage L2</label>
+ </channel>
+ <channel id="active_voltage_l3" typeId="system.electric-voltage">
+ <label>Active Voltage L3</label>
+ </channel>
+
+ <channel id="power_failures" typeId="power_failures"/>
+ <channel id="long_power_failures" typeId="power_failures">
+ <label>Long Power Failures</label>
+ <description>This channel provides the count of long power failures.</description>
+ </channel>
+
+ <channel id="total_energy_import_t1" typeId="total_energy_import_t1"/>
+ <channel id="total_energy_import_t2" typeId="total_energy_import_t2"/>
+ <channel id="total_energy_export_t1" typeId="total_energy_export_t1"/>
+ <channel id="total_energy_export_t2" typeId="total_energy_export_t2"/>
+
<channel id="total_gas" typeId="total_gas"/>
<channel id="gas_timestamp" typeId="gas_timestamp"/>
</channels>
<properties>
<property name="meterModel">Unknown</property>
+ <property name="thingTypeVersion">1</property>
</properties>
<config-description>
</thing-type>
+
+ <channel-type id="power_failures">
+ <item-type>Number</item-type>
+ <label>Power Failures</label>
+ <description>This channel provides the count of any type of power failure.</description>
+ </channel-type>
+
<channel-type id="total_energy_import_t1">
<item-type>Number:Energy</item-type>
<label>Total Imported Energy Counter 1</label>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="homewizard:p1_wifi_meter">
+
+ <instruction-set targetVersion="1">
+ <add-channel id="active_voltage">
+ <type>system:electric-voltage</type>
+ </add-channel>
+ <add-channel id="active_voltage_l1">
+ <type>system:electric-voltage</type>
+ </add-channel>
+ <add-channel id="active_voltage_l2">
+ <type>system:electric-voltage</type>
+ </add-channel>
+ <add-channel id="active_voltage_l3">
+ <type>system:electric-voltage</type>
+ </add-channel>
+ <add-channel id="active_current">
+ <type>system:electric-current</type>
+ </add-channel>
+ <add-channel id="active_current_l1">
+ <type>system:electric-current</type>
+ </add-channel>
+ <add-channel id="active_current_l2">
+ <type>system:electric-current</type>
+ </add-channel>
+ <add-channel id="active_current_l3">
+ <type>system:electric-current</type>
+ </add-channel>
+ <add-channel id="power_failures">
+ <type>homewizard:power_failures</type>
+ </add-channel>
+ <add-channel id="long_power_failures">
+ <type>homewizard:power_failures</type>
+ <label>Long Power Failures</label>
+ <description>This channel provides the count of long power failures.</description>
+ </add-channel>
+ <add-channel id="total_energy_import_t1">
+ <type>homewizard:total_energy_import_t1</type>
+ </add-channel>
+ <add-channel id="total_energy_import_t2">
+ <type>homewizard:total_energy_import_t2</type>
+ </add-channel>
+ <add-channel id="total_energy_export_t1">
+ <type>homewizard:total_energy_export_t1</type>
+ </add-channel>
+ <add-channel id="total_energy_export_t2">
+ <type>homewizard:total_energy_export_t2</type>
+ </add-channel>
+ </instruction-set>
+
+ </thing-type>
+
+</update:update-descriptions>
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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<Channel> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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> 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"));
+ }
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+{
+}
\ No newline at end of file
--- /dev/null
+{
+ "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