mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[shelly] Fix addon sensor read errors causing spurious 0 °C spike (#20995)
* [shelly] Fix addon sensor read errors causing spurious 0 °C spike Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
+96
-52
@@ -525,11 +525,12 @@ public class Shelly2ApiClient extends ShellyHttpClient implements ShellyDiscover
|
||||
boolean channelUpdate) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
|
||||
if (result.temperature0 != null && result.temperature0.tC != null && !getProfile().isSensor) {
|
||||
Shelly2DeviceStatusTempId temperature0 = result.temperature0;
|
||||
if (temperature0 != null && temperature0.tC != null && !getProfile().isSensor) {
|
||||
if (status.tmp == null) {
|
||||
status.tmp = new ShellySensorTmp();
|
||||
}
|
||||
status.temperature = status.tmp.tC = result.temperature0.tC;
|
||||
status.temperature = status.tmp.tC = temperature0.tC;
|
||||
}
|
||||
|
||||
updated |= updateInputStatus(status, result, channelUpdate);
|
||||
@@ -597,17 +598,20 @@ public class Shelly2ApiClient extends ShellyHttpClient implements ShellyDiscover
|
||||
if (rs.timerStartetAt != null && rs.timerStartetAt > 0) {
|
||||
sr.timerRemaining = (int) (now() - rs.timerStartetAt);
|
||||
}
|
||||
if (rs.temperature != null && rs.temperature.tC != null) {
|
||||
if (status.tmp == null) {
|
||||
status.tmp = new ShellySensorTmp();
|
||||
}
|
||||
status.tmp.isValid = true;
|
||||
status.tmp.tC = rs.temperature.tC;
|
||||
status.tmp.tF = rs.temperature.tF;
|
||||
status.tmp.units = "C";
|
||||
sr.temperature = rs.temperature.tC;
|
||||
if (status.temperature == null || rs.temperature.tC > status.temperature) {
|
||||
status.temperature = sr.temperature;
|
||||
if (rs.temperature != null) {
|
||||
Double tC = rs.temperature.tC;
|
||||
if (tC != null) {
|
||||
if (status.tmp == null) {
|
||||
status.tmp = new ShellySensorTmp();
|
||||
}
|
||||
status.tmp.isValid = true;
|
||||
status.tmp.tC = tC;
|
||||
status.tmp.tF = rs.temperature.tF;
|
||||
status.tmp.units = "C";
|
||||
sr.temperature = tC;
|
||||
if (status.temperature == null || tC > status.temperature) {
|
||||
status.temperature = sr.temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,17 +690,20 @@ public class Shelly2ApiClient extends ShellyHttpClient implements ShellyDiscover
|
||||
if (bs.output != null) {
|
||||
sr.ison = rstatus.ison = getBool(bs.output);
|
||||
}
|
||||
if (bs.temperature != null && bs.temperature.tC != null) {
|
||||
if (status.tmp == null) {
|
||||
status.tmp = new ShellySensorTmp();
|
||||
}
|
||||
status.tmp.isValid = true;
|
||||
status.tmp.tC = bs.temperature.tC;
|
||||
status.tmp.tF = bs.temperature.tF;
|
||||
status.tmp.units = "C";
|
||||
sr.temperature = getDouble(bs.temperature.tC);
|
||||
if (status.temperature == null || getDouble(bs.temperature.tC) > status.temperature) {
|
||||
status.temperature = sr.temperature;
|
||||
if (bs.temperature != null) {
|
||||
Double tC = bs.temperature.tC;
|
||||
if (tC != null) {
|
||||
if (status.tmp == null) {
|
||||
status.tmp = new ShellySensorTmp();
|
||||
}
|
||||
status.tmp.isValid = true;
|
||||
status.tmp.tC = tC;
|
||||
status.tmp.tF = bs.temperature.tF;
|
||||
status.tmp.units = "C";
|
||||
sr.temperature = getDouble(tC);
|
||||
if (status.temperature == null || getDouble(tC) > status.temperature) {
|
||||
status.temperature = sr.temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1140,31 +1147,58 @@ public class Shelly2ApiClient extends ShellyHttpClient implements ShellyDiscover
|
||||
}
|
||||
|
||||
if (ds.temperature100 != null) {
|
||||
ShellyExtTemperature extTemp = status.extTemperature;
|
||||
if (extTemp == null) {
|
||||
extTemp = new ShellyExtTemperature();
|
||||
status.extTemperature = extTemp;
|
||||
}
|
||||
extTemp.sensor1 = updateExtTempSensor(ds.temperature100);
|
||||
extTemp.sensor2 = updateExtTempSensor(ds.temperature101);
|
||||
extTemp.sensor3 = updateExtTempSensor(ds.temperature102);
|
||||
extTemp.sensor4 = updateExtTempSensor(ds.temperature103);
|
||||
extTemp.sensor5 = updateExtTempSensor(ds.temperature104);
|
||||
}
|
||||
if (ds.humidity100 != null) {
|
||||
Double rh = ds.humidity100.rh;
|
||||
if (rh != null) {
|
||||
status.extHumidity = new ShellyExtHumidity(rh);
|
||||
ShellyShortTemp s1 = updateExtTempSensor(ds.temperature100);
|
||||
ShellyShortTemp s2 = updateExtTempSensor(ds.temperature101);
|
||||
ShellyShortTemp s3 = updateExtTempSensor(ds.temperature102);
|
||||
ShellyShortTemp s4 = updateExtTempSensor(ds.temperature103);
|
||||
ShellyShortTemp s5 = updateExtTempSensor(ds.temperature104);
|
||||
if (s1 != null || s2 != null || s3 != null || s4 != null || s5 != null) {
|
||||
ShellyExtTemperature extTemp = status.extTemperature;
|
||||
if (extTemp == null) {
|
||||
extTemp = new ShellyExtTemperature();
|
||||
status.extTemperature = extTemp;
|
||||
}
|
||||
extTemp.sensor1 = s1;
|
||||
extTemp.sensor2 = s2;
|
||||
extTemp.sensor3 = s3;
|
||||
extTemp.sensor4 = s4;
|
||||
extTemp.sensor5 = s5;
|
||||
} else {
|
||||
// all sensors in this notification reported read errors — clear so
|
||||
// hasAddon() returns false and sensors#lastUpdate is not written
|
||||
status.extTemperature = null;
|
||||
}
|
||||
}
|
||||
if (ds.voltmeter100 != null) {
|
||||
Double voltage = ds.voltmeter100.voltage;
|
||||
if (voltage != null) {
|
||||
status.extVoltage = new ShellyExtVoltage(voltage);
|
||||
Shelly2DeviceStatusHumidity humidity100 = ds.humidity100;
|
||||
if (humidity100 != null) {
|
||||
if (hasReadError(humidity100.errors)) {
|
||||
logger.debug("{}: Addon humidity:100 sensor read error, skipping update", thingName);
|
||||
status.extHumidity = null;
|
||||
} else {
|
||||
Double rh = humidity100.rh;
|
||||
if (rh != null) {
|
||||
status.extHumidity = new ShellyExtHumidity(rh);
|
||||
}
|
||||
}
|
||||
}
|
||||
Shelly2DeviceStatusVoltage voltmeter100 = ds.voltmeter100;
|
||||
if (voltmeter100 != null) {
|
||||
if (hasReadError(voltmeter100.errors)) {
|
||||
logger.debug("{}: Addon voltmeter:100 sensor read error, skipping update", thingName);
|
||||
status.extVoltage = null;
|
||||
} else {
|
||||
Double voltage = voltmeter100.voltage;
|
||||
if (voltage != null) {
|
||||
status.extVoltage = new ShellyExtVoltage(voltage);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ds.input100 != null) {
|
||||
if (ds.input100.state != null) {
|
||||
if (hasReadError(ds.input100.errors)) {
|
||||
logger.debug("{}: Addon input:100 sensor read error, skipping update", thingName);
|
||||
status.extDigitalInput = null;
|
||||
status.extAnalogInput = null;
|
||||
} else if (ds.input100.state != null) {
|
||||
status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
|
||||
} else if (ds.input100.percent != null) {
|
||||
status.extAnalogInput = new ShellyExtAnalogInput(getDouble(ds.input100.percent));
|
||||
@@ -1173,15 +1207,25 @@ public class Shelly2ApiClient extends ShellyHttpClient implements ShellyDiscover
|
||||
}
|
||||
|
||||
private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
|
||||
if (value != null) {
|
||||
ShellyShortTemp temp = new ShellyShortTemp();
|
||||
Integer idBox = value.id;
|
||||
temp.hwID = idBox != null ? idBox.toString() : "999";
|
||||
temp.tC = getDouble(value.tC);
|
||||
temp.tF = getDouble(value.tF);
|
||||
return temp;
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
if (hasReadError(value.errors)) {
|
||||
Integer idBox = value.id;
|
||||
logger.debug("{}: Addon temperature:{} sensor read error, skipping update", thingName,
|
||||
idBox != null ? idBox : "?");
|
||||
return null;
|
||||
}
|
||||
ShellyShortTemp temp = new ShellyShortTemp();
|
||||
Integer idBox = value.id;
|
||||
temp.hwID = idBox != null ? idBox.toString() : "999";
|
||||
temp.tC = getDouble(value.tC);
|
||||
temp.tF = getDouble(value.tF);
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static boolean hasReadError(@Nullable ArrayList<String> errors) {
|
||||
return errors != null && errors.contains("read");
|
||||
}
|
||||
|
||||
protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
|
||||
|
||||
+19
-16
@@ -598,7 +598,7 @@ public class Shelly2ApiJsonDTO {
|
||||
public Integer id;
|
||||
public Boolean state;
|
||||
public Double percent; // analog input only
|
||||
public ArrayList<String> errors;// shown only if at least one error is present.
|
||||
public @Nullable ArrayList<String> errors;
|
||||
public Double xpercent;
|
||||
public Shelly2InputCounts counts;
|
||||
public Double freq;
|
||||
@@ -655,6 +655,7 @@ public class Shelly2ApiJsonDTO {
|
||||
public class Shelly2DeviceStatusHumidity {
|
||||
public @Nullable Integer id;
|
||||
public @Nullable Double rh;
|
||||
public @Nullable ArrayList<String> errors;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusIlluminance {
|
||||
@@ -666,6 +667,7 @@ public class Shelly2ApiJsonDTO {
|
||||
public class Shelly2DeviceStatusVoltage {
|
||||
public @Nullable Integer id;
|
||||
public @Nullable Double voltage;
|
||||
public @Nullable ArrayList<String> errors;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusTempId extends Shelly2DeviceStatusTemp {
|
||||
@@ -834,22 +836,22 @@ public class Shelly2ApiJsonDTO {
|
||||
public Shelly2DeviceStatusLight light1;
|
||||
|
||||
@SerializedName("temperature:0")
|
||||
public Shelly2DeviceStatusTempId temperature0;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature0;
|
||||
@SerializedName("temperature:100")
|
||||
public Shelly2DeviceStatusTempId temperature100;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature100;
|
||||
@SerializedName("temperature:101")
|
||||
public Shelly2DeviceStatusTempId temperature101;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature101;
|
||||
@SerializedName("temperature:102")
|
||||
public Shelly2DeviceStatusTempId temperature102;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature102;
|
||||
@SerializedName("temperature:103")
|
||||
public Shelly2DeviceStatusTempId temperature103;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature103;
|
||||
@SerializedName("temperature:104")
|
||||
public Shelly2DeviceStatusTempId temperature104;
|
||||
public @Nullable Shelly2DeviceStatusTempId temperature104;
|
||||
|
||||
@SerializedName("humidity:0")
|
||||
public Shelly2DeviceStatusHumidity humidity0;
|
||||
public @Nullable Shelly2DeviceStatusHumidity humidity0;
|
||||
@SerializedName("humidity:100")
|
||||
public Shelly2DeviceStatusHumidity humidity100;
|
||||
public @Nullable Shelly2DeviceStatusHumidity humidity100;
|
||||
|
||||
@SerializedName("illuminance:0")
|
||||
Shelly2DeviceStatusIlluminance illuminance0;
|
||||
@@ -858,15 +860,15 @@ public class Shelly2ApiJsonDTO {
|
||||
public Shelly2DeviceStatusSmoke smoke0;
|
||||
|
||||
@SerializedName("voltmeter:0")
|
||||
public Shelly2DeviceStatusVoltage voltmeter0;
|
||||
public @Nullable Shelly2DeviceStatusVoltage voltmeter0;
|
||||
@SerializedName("voltmeter:1")
|
||||
public Shelly2DeviceStatusVoltage voltmeter1;
|
||||
public @Nullable Shelly2DeviceStatusVoltage voltmeter1;
|
||||
@SerializedName("voltmeter:2")
|
||||
public Shelly2DeviceStatusVoltage voltmeter2;
|
||||
public @Nullable Shelly2DeviceStatusVoltage voltmeter2;
|
||||
@SerializedName("voltmeter:3")
|
||||
public Shelly2DeviceStatusVoltage voltmeter3;
|
||||
public @Nullable Shelly2DeviceStatusVoltage voltmeter3;
|
||||
@SerializedName("voltmeter:100")
|
||||
public Shelly2DeviceStatusVoltage voltmeter100;
|
||||
public @Nullable Shelly2DeviceStatusVoltage voltmeter100;
|
||||
|
||||
@SerializedName("devicepower:0")
|
||||
public Shelly2DeviceStatusPower devicepower0;
|
||||
@@ -988,8 +990,9 @@ public class Shelly2ApiJsonDTO {
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusTemp {
|
||||
public Double tC;
|
||||
public Double tF;
|
||||
public @Nullable Double tC;
|
||||
public @Nullable Double tF;
|
||||
public @Nullable ArrayList<String> errors;
|
||||
}
|
||||
|
||||
public static class Shelly2Energy {
|
||||
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.shelly.internal.api2;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.shelly.internal.ShellyDevices.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatusTemp;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyApiConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingRuntimeConfig;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.core.net.NetworkAddressChangeListener;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Tests for the Gen2 addon sensor read-error path in {@link Shelly2ApiClient}.
|
||||
*
|
||||
* <p>
|
||||
* Verifies that when a DS18B20 or AM2301 addon sensor reports {@code "errors":["read"]} (firmware
|
||||
* sends {@code "tC":null} alongside the error), the binding:
|
||||
* <ul>
|
||||
* <li>correctly deserialises the {@code errors} array from the DTO,</li>
|
||||
* <li>leaves {@link ShellySettingsStatus#extTemperature} / {@code extHumidity} / {@code extVoltage}
|
||||
* null so that no channel update is emitted and {@code sensors#lastUpdate} is not advanced.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings({ "null" })
|
||||
public class Shelly2AddonStatusTest {
|
||||
|
||||
private static final String LOCAL_IP = "192.168.1.50";
|
||||
private static final String DEVICE_IP = "192.168.1.100";
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private ShellyApiConfiguration testConfig() {
|
||||
ShellyBindingConfiguration raw = ShellyBindingConfiguration
|
||||
.fromProperties(Map.of(ShellyBindingConfiguration.CONFIG_LOCAL_IP, LOCAL_IP));
|
||||
ShellyBindingRuntimeConfig bindingConfig = new ShellyBindingRuntimeConfig(raw, 8080, nullNas());
|
||||
return new ShellyApiConfiguration(bindingConfig, "test-realm", DEVICE_IP);
|
||||
}
|
||||
|
||||
private static NetworkAddressService nullNas() {
|
||||
return new NetworkAddressService() {
|
||||
@Override
|
||||
public @Nullable String getPrimaryIpv4HostAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getConfiguredBroadcastAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseOnlyOneAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseIPv6() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNetworkAddressChangeListener(NetworkAddressChangeListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNetworkAddressChangeListener(NetworkAddressChangeListener listener) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ShellyThingInterface mockRelayThing() {
|
||||
ShellyDeviceProfile profile = new ShellyDeviceProfile(THING_TYPE_SHELLYPLUS1PM);
|
||||
profile.isSensor = false;
|
||||
profile.hasBattery = false;
|
||||
ShellyThingInterface handler = mock(ShellyThingInterface.class);
|
||||
when(handler.getProfile()).thenReturn(profile);
|
||||
when(handler.areChannelsCreated()).thenReturn(false);
|
||||
when(handler.updateChannel(anyString(), anyString(), any())).thenReturn(false);
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Testable subclass of {@link Shelly2ApiClient} that injects a mock {@link ShellyThingInterface}
|
||||
* and exposes the protected {@link #fillDeviceStatus} for unit testing.
|
||||
*/
|
||||
private static class TestableApiClient extends Shelly2ApiClient {
|
||||
TestableApiClient(ShellyApiConfiguration config, ShellyThingInterface thing) {
|
||||
super("test-thing", config, Mockito.mock(HttpClient.class));
|
||||
this.thing = thing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T apiRequest(String method, @Nullable Object params, Class<T> classOfT) throws ShellyApiException {
|
||||
throw new ShellyApiException("Not expected in addon test: " + method);
|
||||
}
|
||||
|
||||
public boolean testFillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result)
|
||||
throws ShellyApiException {
|
||||
return fillDeviceStatus(status, result, false);
|
||||
}
|
||||
}
|
||||
|
||||
private TestableApiClient newClient() {
|
||||
return new TestableApiClient(testConfig(), mockRelayThing());
|
||||
}
|
||||
|
||||
// ── DTO parsing ───────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void temperatureStatusTemp_readError_parsesErrorsFieldAndNullTc() {
|
||||
Gson gson = new Gson();
|
||||
Shelly2DeviceStatusTemp t = Objects
|
||||
.requireNonNull(gson.fromJson("{\"tC\":null,\"errors\":[\"read\"]}", Shelly2DeviceStatusTemp.class));
|
||||
assertThat(t.tC, is(nullValue()));
|
||||
assertThat(t.errors, is(not(nullValue())));
|
||||
assertThat(t.errors.contains("read"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void temperatureStatusTemp_normalReading_noErrorsField() {
|
||||
Gson gson = new Gson();
|
||||
Shelly2DeviceStatusTemp t = Objects
|
||||
.requireNonNull(gson.fromJson("{\"tC\":22.5,\"tF\":72.5}", Shelly2DeviceStatusTemp.class));
|
||||
assertThat(t.tC, is(22.5));
|
||||
assertThat(t.errors, is(nullValue()));
|
||||
}
|
||||
|
||||
// ── fillDeviceStatus — temperature ────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_tempReadError_extTemperatureNull() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.temperature100 = result.new Shelly2DeviceStatusTempId();
|
||||
result.temperature100.id = 100;
|
||||
result.temperature100.errors = new ArrayList<>(List.of("read"));
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("sensor read error must leave extTemperature null", status.extTemperature, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_tempValid_extTemperaturePopulated() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.temperature100 = result.new Shelly2DeviceStatusTempId();
|
||||
result.temperature100.id = 100;
|
||||
result.temperature100.tC = 22.5;
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("valid temp must set extTemperature", status.extTemperature, is(not(nullValue())));
|
||||
assertThat(status.extTemperature.sensor1, is(not(nullValue())));
|
||||
assertThat(status.extTemperature.sensor1.tC, is(22.5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_mixedSensors_errorSlotNullValidSlotSet() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
// sensor 100 errors — sensor 101 is valid
|
||||
result.temperature100 = result.new Shelly2DeviceStatusTempId();
|
||||
result.temperature100.id = 100;
|
||||
result.temperature100.errors = new ArrayList<>(List.of("read"));
|
||||
result.temperature101 = result.new Shelly2DeviceStatusTempId();
|
||||
result.temperature101.id = 101;
|
||||
result.temperature101.tC = 18.0;
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
// extTemperature must be set because sensor101 is valid
|
||||
assertThat("at least one valid sensor must set extTemperature", status.extTemperature, is(not(nullValue())));
|
||||
assertThat("errored slot must be null", status.extTemperature.sensor1, is(nullValue()));
|
||||
assertThat("valid slot must be set", status.extTemperature.sensor2, is(not(nullValue())));
|
||||
assertThat(status.extTemperature.sensor2.tC, is(18.0));
|
||||
}
|
||||
|
||||
// ── fillDeviceStatus — humidity ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_humidityReadError_extHumidityNull() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.humidity100 = result.new Shelly2DeviceStatusHumidity();
|
||||
result.humidity100.id = 100;
|
||||
result.humidity100.errors = new ArrayList<>(List.of("read"));
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("humidity read error must leave extHumidity null", status.extHumidity, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_humidityValid_extHumidityPopulated() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.humidity100 = result.new Shelly2DeviceStatusHumidity();
|
||||
result.humidity100.id = 100;
|
||||
result.humidity100.rh = 55.0;
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("valid humidity must set extHumidity", status.extHumidity, is(not(nullValue())));
|
||||
}
|
||||
|
||||
// ── fillDeviceStatus — voltage ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_voltageReadError_extVoltageNull() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.voltmeter100 = result.new Shelly2DeviceStatusVoltage();
|
||||
result.voltmeter100.id = 100;
|
||||
result.voltmeter100.errors = new ArrayList<>(List.of("read"));
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("voltage read error must leave extVoltage null", status.extVoltage, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fillDeviceStatus_voltageValid_extVoltagePopulated() throws ShellyApiException {
|
||||
Shelly2DeviceStatusResult result = new Shelly2DeviceStatusResult();
|
||||
result.voltmeter100 = result.new Shelly2DeviceStatusVoltage();
|
||||
result.voltmeter100.id = 100;
|
||||
result.voltmeter100.voltage = 3.3;
|
||||
|
||||
ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
newClient().testFillDeviceStatus(status, result);
|
||||
|
||||
assertThat("valid voltage must set extVoltage", status.extVoltage, is(not(nullValue())));
|
||||
}
|
||||
}
|
||||
+1
@@ -45,6 +45,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings({ "null" })
|
||||
public class ShellyBluApiTest {
|
||||
|
||||
// ── getStatus() connection guard ─────────────────────────────────────────
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ import org.openhab.core.types.UnDefType;
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings({ "null" })
|
||||
public class ShellyComponentsTest {
|
||||
|
||||
// ── hasAddon ─────────────────────────────────────────────────────────────
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test;
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings({ "null" })
|
||||
public class ShellyThingTableTest {
|
||||
|
||||
private static final String GATEWAY_IP = "192.168.1.100";
|
||||
|
||||
Reference in New Issue
Block a user