Skip to content

Commit

Permalink
[smartmeter] Fix Undelivered IOException (openhab#17133)
Browse files Browse the repository at this point in the history
Signed-off-by: Leo Siepel <[email protected]>
  • Loading branch information
lsiepel authored Sep 7, 2024
1 parent e8190f6 commit 9f58f6d
Show file tree
Hide file tree
Showing 17 changed files with 163 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,25 @@
*/
package org.openhab.binding.smartmeter;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* The {@link SmartMeterConfiguration} is the class used to match the
* thing configuration.
*
* @author Matthias Steigenberger - Initial contribution
*/
@NonNullByDefault
public class SmartMeterConfiguration {

@Nullable
public String port;
public Integer refresh;
public Integer baudrateChangeDelay;
public Integer refresh = 10;
public Integer baudrateChangeDelay = 0;
@Nullable
public String initMessage;
public String baudrate;
public String mode;
public String conformity;
public String baudrate = "AUTO";
public String mode = "SML";
public String conformity = "NONE";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.smartmeter.internal;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -37,6 +38,7 @@

import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;

Expand Down Expand Up @@ -83,7 +85,18 @@ public MeterDevice(Supplier<SerialPortManager> serialPortManagerSupplier, String
this.connector = createConnector(serialPortManagerSupplier, serialPort, baudrate, baudrateChangeDelay,
protocolMode);
RxJavaPlugins.setErrorHandler(error -> {
logger.error("Fatal error occured", error);
if (error == null) {
logger.warn("Fatal but unknown error occurred");
return;
}
if (error instanceof UndeliverableException) {
error = error.getCause();
}
if (error instanceof IOException) {
logger.warn("Connection related issue occurred: {}", error.getMessage());
return;
}
logger.warn("Fatal error occurred", error);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
import org.openhab.binding.smartmeter.internal.iec62056.Iec62056_21MeterReader;
import org.openhab.binding.smartmeter.internal.iec62056.MeterReader;
import org.openhab.binding.smartmeter.internal.sml.SmlMeterReader;
import org.openhab.core.io.transport.serial.SerialPortManager;

Expand Down Expand Up @@ -49,8 +49,8 @@ public class MeterDeviceFactory {
switch (protocolMode) {
case D:
case ABC:
return new Iec62056_21MeterReader(serialPortManagerSupplier, deviceId, serialPort, initMessage,
baudrate, baudrateChangeDelay, protocolMode);
return new MeterReader(serialPortManagerSupplier, deviceId, serialPort, initMessage, baudrate,
baudrateChangeDelay, protocolMode);
case SML:
return SmlMeterReader.createInstance(serialPortManagerSupplier, deviceId, serialPort, initMessage,
baudrate, baudrateChangeDelay);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,15 @@ public String getValue() {
@Override
public int hashCode() {
final int prime = 31;
final String status = this.status;
final Unit<? extends Q> unit = this.unit;
final String value = this.value;

int result = 1;
result = prime * result + ((obis == null) ? 0 : obis.hashCode());
result = prime * result + ((status == null) ? 0 : status.hashCode());
result = prime * result + ((unit == null) ? 0 : unit.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
result = prime * result + obis.hashCode();
result = prime * result + (status == null ? 0 : status.hashCode());
result = prime * result + (unit == null ? 0 : unit.hashCode());
result = prime * result + value.hashCode();
return result;
}

Expand All @@ -90,13 +94,15 @@ public boolean equals(@Nullable Object obj) {
if (!obis.equals(other.obis)) {
return false;
}
String status = this.status;
if (status == null) {
if (other.status != null) {
return false;
}
} else if (!status.equals(other.status)) {
return false;
}
Unit<? extends Q> unit = this.unit;
if (unit == null) {
if (other.unit != null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public static ObisCode from(String obis) throws IllegalArgumentException {
* @return the obis as string.
*/
public String asDecimalString() {
Byte a = this.a;
Byte b = this.b;
Byte c = this.c;
Byte f = this.f;
try (Formatter format = new Formatter()) {
format.format(SmartMeterBindingConstants.OBIS_FORMAT, a != null ? a & 0xFF : 0, b != null ? b & 0xFF : 0,
c & 0xFF, d & 0xFF, e & 0xFF, f != null ? f & 0xFF : 0);
Expand Down Expand Up @@ -118,10 +122,15 @@ public String toString() {
return asDecimalString();
}

public boolean matches(@Nullable Byte a, @Nullable Byte b, Byte c, Byte d, Byte e, @Nullable Byte f) {
return (this.a == null || a == null || this.a.equals(a)) && (this.b == null || b == null || this.b.equals(b))
&& this.c.equals(c) && this.d.equals(d) && this.e.equals(e)
&& (this.f == null || f == null || this.f.equals(f));
public boolean matches(@Nullable Byte otherA, @Nullable Byte otherB, Byte otherC, Byte d, Byte e,
@Nullable Byte otherF) {
Byte a = this.a;
Byte b = this.b;
Byte c = this.c;
Byte f = this.f;
return (a == null || otherA == null || a.equals(otherA)) && (b == null || otherB == null || b.equals(otherB))
&& c.equals(otherC) && this.d.equals(d) && this.e.equals(e)
&& (f == null || otherF == null || f.equals(otherF));
}

public boolean matches(Byte c, Byte d, Byte e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import javax.measure.Quantity;
import javax.measure.Unit;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.smartmeter.SmartMeterBindingConstants;
import org.openhab.core.library.CoreItemFactory;
Expand All @@ -44,6 +44,7 @@
* @author Matthias Steigenberger - Initial contribution
*
*/
@NonNullByDefault
@Component(service = { ChannelTypeProvider.class, SmartMeterChannelTypeProvider.class })
public class SmartMeterChannelTypeProvider implements ChannelTypeProvider, MeterValueListener {

Expand All @@ -68,14 +69,14 @@ public void errorOccurred(Throwable e) {
}

@Override
public <Q extends @NonNull Quantity<Q>> void valueChanged(MeterValue<Q> value) {
public <Q extends Quantity<Q>> void valueChanged(MeterValue<Q> value) {
if (!obisChannelMap.containsKey(value.getObisCode())) {
logger.debug("Creating ChannelType for OBIS {}", value.getObisCode());
obisChannelMap.put(value.getObisCode(), getChannelType(value.getUnit(), value.getObisCode()));
}
}

private ChannelType getChannelType(Unit<?> unit, String obis) {
private ChannelType getChannelType(@Nullable Unit<?> unit, String obis) {
String obisChannelId = SmartMeterBindingConstants.getObisChannelId(obis);
StateChannelTypeBuilder stateDescriptionBuilder;
if (unit != null) {
Expand All @@ -96,7 +97,7 @@ private ChannelType getChannelType(Unit<?> unit, String obis) {
}

@Override
public <Q extends @NonNull Quantity<Q>> void valueRemoved(MeterValue<Q> value) {
public <Q extends Quantity<Q>> void valueRemoved(MeterValue<Q> value) {
obisChannelMap.remove(value.getObisCode());
}

Expand All @@ -106,7 +107,7 @@ private ChannelType getChannelType(Unit<?> unit, String obis) {
* @param obis The obis code.
* @return The {@link ChannelTypeUID} or null.
*/
public ChannelTypeUID getChannelTypeIdForObis(String obis) {
public @Nullable ChannelTypeUID getChannelTypeIdForObis(String obis) {
ChannelType channeltype = obisChannelMap.get(obis);
return channeltype != null ? channeltype.getUID() : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,13 @@
public class SmartMeterHandler extends BaseThingHandler {

private static final long DEFAULT_TIMEOUT = 30000;
private static final int DEFAULT_REFRESH_PERIOD = 30;
private Logger logger = LoggerFactory.getLogger(SmartMeterHandler.class);
private MeterDevice<?> smlDevice;
private Disposable valueReader;
private Conformity conformity;
private MeterValueListener valueChangeListener;
private SmartMeterChannelTypeProvider channelTypeProvider;
private @NonNull Supplier<SerialPortManager> serialPortManagerSupplier;
private Supplier<SerialPortManager> serialPortManagerSupplier;

public SmartMeterHandler(Thing thing, SmartMeterChannelTypeProvider channelProvider,
Supplier<SerialPortManager> serialPortManagerSupplier) {
Expand All @@ -99,11 +98,10 @@ public void initialize() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'port' is mandatory and must be configured");
} else {
byte[] pullSequence = config.initMessage == null ? null
: HexUtils.hexToBytes(config.initMessage.replaceAll("\\s+", ""));
int baudrate = config.baudrate == null ? Baudrate.AUTO.getBaudrate()
: Baudrate.fromString(config.baudrate).getBaudrate();
this.conformity = config.conformity == null ? Conformity.NONE : Conformity.valueOf(config.conformity);
String initMessage = config.initMessage;
byte[] pullSequence = initMessage == null ? null : HexUtils.hexToBytes(initMessage.replaceAll("\\s+", ""));
int baudrate = Baudrate.fromString(config.baudrate).getBaudrate();
this.conformity = Conformity.valueOf(config.conformity);
this.smlDevice = MeterDeviceFactory.getDevice(serialPortManagerSupplier, config.mode,
this.thing.getUID().getAsString(), port, pullSequence, baudrate, config.baudrateChangeDelay);
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.HANDLER_CONFIGURATION_PENDING,
Expand Down Expand Up @@ -158,53 +156,58 @@ private void updateOBISValue() {

String obisChannelString = SmartMeterBindingConstants.getObisChannelId(obis);
Channel channel = thing.getChannel(obisChannelString);

ChannelTypeUID channelTypeId = channelTypeProvider.getChannelTypeIdForObis(obis);
if (channelTypeId == null) {
logger.warn("No ChannelTypeId found for OBIS {}", obis);
return;
}

ChannelType channelType = channelTypeProvider.getChannelType(channelTypeId, null);
if (channelType != null) {
String itemType = channelType.getItemType();

State state = getStateForObisValue(value, channel);
if (channel == null) {
logger.debug("Adding channel: {} with item type: {}", obisChannelString, itemType);

// channel has not been created yet
ChannelBuilder channelBuilder = ChannelBuilder
.create(new ChannelUID(thing.getUID(), obisChannelString), itemType)
.withType(channelTypeId);

Configuration configuration = new Configuration();
configuration.put(SmartMeterBindingConstants.CONFIGURATION_CONVERSION, 1);
channelBuilder.withConfiguration(configuration);
channelBuilder.withLabel(obis);
Map<String, String> channelProps = new HashMap<>();
channelProps.put(SmartMeterBindingConstants.CHANNEL_PROPERTY_OBIS, obis);
channelBuilder.withProperties(channelProps);
channelBuilder.withDescription(
MessageFormat.format("Value for OBIS code: {0} with Unit: {1}", obis, value.getUnit()));
channel = channelBuilder.build();
ChannelUID channelId = channel.getUID();

// add all valid channels to the thing builder
List<Channel> channels = new ArrayList<>(getThing().getChannels());
if (channels.stream().filter((element) -> element.getUID().equals(channelId)).count() == 0) {
channels.add(channel);
thingBuilder.withChannels(channels);
updateThing(thingBuilder.build());
}
}

if (!channel.getProperties().containsKey(SmartMeterBindingConstants.CHANNEL_PROPERTY_OBIS)) {
addObisPropertyToChannel(obis, channel);
}
if (state != null) {
updateState(channel.getUID(), state);
if (channelType == null) {
logger.warn("No ChannelType found for OBIS {}", obis);
return;
}
String itemType = channelType.getItemType();

State state = getStateForObisValue(value, channel);
if (channel == null) {
logger.debug("Adding channel: {} with item type: {}", obisChannelString, itemType);

// channel has not been created yet
ChannelBuilder channelBuilder = ChannelBuilder
.create(new ChannelUID(thing.getUID(), obisChannelString), itemType)
.withType(channelTypeId);

Configuration configuration = new Configuration();
configuration.put(SmartMeterBindingConstants.CONFIGURATION_CONVERSION, 1);
channelBuilder.withConfiguration(configuration);
channelBuilder.withLabel(obis);
Map<String, String> channelProps = new HashMap<>();
channelProps.put(SmartMeterBindingConstants.CHANNEL_PROPERTY_OBIS, obis);
channelBuilder.withProperties(channelProps);
channelBuilder.withDescription(
MessageFormat.format("Value for OBIS code: {0} with Unit: {1}", obis, value.getUnit()));
channel = channelBuilder.build();
ChannelUID channelId = channel.getUID();

// add all valid channels to the thing builder
List<Channel> channels = new ArrayList<>(getThing().getChannels());
if (channels.stream().filter((element) -> element.getUID().equals(channelId)).count() == 0) {
channels.add(channel);
thingBuilder.withChannels(channels);
updateThing(thingBuilder.build());
}
}

updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
logger.warn("No ChannelType found for OBIS {}", obis);
if (!channel.getProperties().containsKey(SmartMeterBindingConstants.CHANNEL_PROPERTY_OBIS)) {
addObisPropertyToChannel(obis, channel);
}
if (state != null) {
updateState(channel.getUID(), state);
}

updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
}

private void addObisPropertyToChannel(String obis, Channel channel) {
Expand Down Expand Up @@ -238,8 +241,7 @@ public void errorOccurred(Throwable e) {
this.smlDevice.addValueChangeListener(valueChangeListener);

SmartMeterConfiguration config = getConfigAs(SmartMeterConfiguration.class);
int delay = config.refresh != null ? config.refresh : DEFAULT_REFRESH_PERIOD;
valueReader = this.smlDevice.readValues(DEFAULT_TIMEOUT, this.scheduler, Duration.ofSeconds(delay));
valueReader = this.smlDevice.readValues(DEFAULT_TIMEOUT, this.scheduler, Duration.ofSeconds(config.refresh));
}

private void updateOBISChannel(ChannelUID channelId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
Expand Down Expand Up @@ -89,7 +88,8 @@ public <Q extends Quantity<Q>> QuantityType<?> apply(Channel channel, QuantityTy
}
}
} catch (Exception e) {
logger.warn("Failed to check negate status for obis {}", obis, e);
LoggerFactory.getLogger(Conformity.class)
.warn("Failed to check negate status for obis {}", obis, e);
}
}
}
Expand All @@ -99,8 +99,6 @@ public <Q extends Quantity<Q>> QuantityType<?> apply(Channel channel, QuantityTy
}
};

private static final Logger logger = LoggerFactory.getLogger(Conformity.class);

/**
* Applies the overwritten negation setting for the channel.
*
Expand Down
Loading

0 comments on commit 9f58f6d

Please sign in to comment.