*/
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";
}
*/
package org.openhab.binding.smartmeter.internal;
+import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
+import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
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);
});
}
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;
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);
@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;
}
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;
* @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);
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) {
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;
* @author Matthias Steigenberger - Initial contribution
*
*/
+@NonNullByDefault
@Component(service = { ChannelTypeProvider.class, SmartMeterChannelTypeProvider.class })
public class SmartMeterChannelTypeProvider implements ChannelTypeProvider, MeterValueListener {
}
@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) {
}
@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());
}
* @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;
}
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) {
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,
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) {
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) {
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;
/**
}
}
} 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);
}
}
}
}
};
- private static final Logger logger = LoggerFactory.getLogger(Conformity.class);
-
/**
* Applies the overwritten negation setting for the channel.
*
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.smartmeter.internal.MeterValue;
-import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*/
@NonNullByDefault
public class NegateHandler {
- private static final Logger LOGGER = LoggerFactory.getLogger(NegateHandler.class);
/**
* Gets whether negation should be applied for the given <code>negateProperty</code> and the {@link MeterValue}
try {
longValue = (long) Double.parseDouble(value);
} catch (NumberFormatException e) {
- LOGGER.warn("Failed to parse value: {} when determining isNegateSet, assuming false", value);
+ LoggerFactory.getLogger(NegateHandler.class)
+ .warn("Failed to parse value: {} when determining isNegateSet, assuming false", value);
return false;
}
return (longValue & (1L << negatePosition)) != 0;
+++ /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.smartmeter.internal.iec62056;
-
-import java.util.function.Supplier;
-
-import javax.measure.Quantity;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.smartmeter.connectors.IMeterReaderConnector;
-import org.openhab.binding.smartmeter.internal.MeterDevice;
-import org.openhab.binding.smartmeter.internal.MeterValue;
-import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
-import org.openhab.core.io.transport.serial.SerialPortManager;
-import org.openmuc.j62056.DataMessage;
-import org.openmuc.j62056.DataSet;
-
-/**
- * Reads meter values from an IEC 62056-21 compatible device with mode A,B,C or D.
- *
- * @author Matthias Steigenberger - Initial contribution
- *
- */
-@NonNullByDefault
-public class Iec62056_21MeterReader extends MeterDevice<DataMessage> {
-
- public Iec62056_21MeterReader(Supplier<SerialPortManager> serialPortManagerSupplier, String deviceId,
- String serialPort, byte @Nullable [] initMessage, int baudrate, int baudrateChangeDelay,
- ProtocolMode protocolMode) {
- super(serialPortManagerSupplier, deviceId, serialPort, initMessage, baudrate, baudrateChangeDelay,
- protocolMode);
- }
-
- @Override
- protected IMeterReaderConnector<DataMessage> createConnector(Supplier<SerialPortManager> serialPortManagerSupplier,
- String serialPort, int baudrate, int baudrateChangeDelay, ProtocolMode protocolMode) {
- return new Iec62056_21SerialConnector(serialPortManagerSupplier, serialPort, baudrate, baudrateChangeDelay,
- protocolMode);
- }
-
- @Override
- protected <Q extends @NonNull Quantity<Q>> void populateValueCache(DataMessage smlFile) {
- for (DataSet dataSet : smlFile.getDataSets()) {
- String address = dataSet.getAddress();
- if (address != null && !address.isEmpty()) {
- addObisCache(new MeterValue<Q>(address, dataSet.getValue(),
- Iec62056_21UnitConversion.getUnit(dataSet.getUnit())));
- }
- }
- }
-}
+++ /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.smartmeter.internal.iec62056;
-
-import java.io.IOException;
-import java.time.Duration;
-import java.util.function.Supplier;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.smartmeter.connectors.ConnectorBase;
-import org.openhab.binding.smartmeter.internal.helper.Baudrate;
-import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
-import org.openhab.core.io.transport.serial.SerialPortManager;
-import org.openmuc.j62056.DataMessage;
-import org.openmuc.j62056.Iec21Port;
-import org.openmuc.j62056.Iec21Port.Builder;
-import org.openmuc.j62056.ModeDListener;
-import org.reactivestreams.Publisher;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-
-/**
- * This connector reads meter values with IEC62056-21 protocol.
- *
- * @author Matthias Steigenberger - Initial contribution
- *
- */
-@NonNullByDefault
-public class Iec62056_21SerialConnector extends ConnectorBase<DataMessage> {
-
- private final Logger logger = LoggerFactory.getLogger(Iec62056_21SerialConnector.class);
- private int baudrate;
- private int baudrateChangeDelay;
- private ProtocolMode protocolMode;
- @Nullable
- private Iec21Port iec21Port;
-
- public Iec62056_21SerialConnector(Supplier<SerialPortManager> serialPortManagerSupplier, String portName,
- int baudrate, int baudrateChangeDelay, ProtocolMode protocolMode) {
- super(portName);
- this.baudrate = baudrate;
- this.baudrateChangeDelay = baudrateChangeDelay;
- this.protocolMode = protocolMode;
- }
-
- @Override
- protected boolean applyPeriod() {
- return protocolMode != ProtocolMode.D;
- }
-
- @Override
- protected boolean applyRetryHandling() {
- return protocolMode != ProtocolMode.D;
- }
-
- @Override
- protected Publisher<?> getRetryPublisher(Duration period, Publisher<Throwable> attempts) {
- if (protocolMode == ProtocolMode.D) {
- return Flowable.empty();
- } else {
- return super.getRetryPublisher(period, attempts);
- }
- }
-
- @Override
- protected DataMessage readNext(byte @Nullable [] initMessage) throws IOException {
- if (iec21Port != null) {
- DataMessage dataMessage = iec21Port.read();
- logger.debug("Datamessage read: {}", dataMessage);
- return dataMessage;
- }
- throw new IOException("SerialPort was not yet created!");
- }
-
- @Override
- protected void emitValues(byte @Nullable [] initMessage, FlowableEmitter<@Nullable DataMessage> emitter)
- throws IOException {
- switch (protocolMode) {
- case ABC:
- super.emitValues(initMessage, emitter);
- break;
- case D:
- if (iec21Port != null) {
- iec21Port.listen(new ModeDListener() {
-
- @Override
- public void newDataMessage(@Nullable DataMessage dataMessage) {
- logger.debug("Datamessage read: {}", dataMessage);
- emitter.onNext(dataMessage);
- }
-
- @Override
- public void exceptionWhileListening(@Nullable Exception e) {
- logger.warn("Exception while listening for mode D data message", e);
- }
- });
- }
- break;
- case SML:
- throw new IOException("SML mode not supported");
- }
- }
-
- @Override
- public void openConnection() throws IOException {
- Builder iec21Builder = new Iec21Port.Builder(getPortName());
- if (Baudrate.fromBaudrate(this.baudrate) != Baudrate.AUTO) {
- iec21Builder.setInitialBaudrate(this.baudrate);
- }
- iec21Builder.setBaudRateChangeDelay(baudrateChangeDelay);
- iec21Builder.enableVerboseMode(true);
- iec21Port = iec21Builder.buildAndOpen();
- }
-
- @Override
- public void closeConnection() {
- if (iec21Port != null) {
- iec21Port.close();
- }
- }
-}
+++ /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.smartmeter.internal.iec62056;
-
-import javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.types.util.UnitUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Converts a unit from IEC62056-21 protocol to a {@link Unit}
- *
- * @author Matthias Steigenberger - Initial contribution
- *
- */
-@NonNullByDefault
-public class Iec62056_21UnitConversion {
-
- private static final Logger logger = LoggerFactory.getLogger(Iec62056_21UnitConversion.class);
-
- @SuppressWarnings("unchecked")
- public static @Nullable <Q extends Quantity<Q>> Unit<Q> getUnit(String unit) {
- if (!unit.isEmpty()) {
- try {
- return (Unit<Q>) UnitUtils.parseUnit(" " + unit);
- } catch (Exception e) {
- logger.warn("Failed to parse unit {}: {}", unit, e.getMessage());
- return null;
- }
- }
- 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.smartmeter.internal.iec62056;
+
+import java.util.function.Supplier;
+
+import javax.measure.Quantity;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.smartmeter.connectors.IMeterReaderConnector;
+import org.openhab.binding.smartmeter.internal.MeterDevice;
+import org.openhab.binding.smartmeter.internal.MeterValue;
+import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
+import org.openhab.core.io.transport.serial.SerialPortManager;
+import org.openmuc.j62056.DataMessage;
+import org.openmuc.j62056.DataSet;
+
+/**
+ * Reads meter values from an IEC 62056-21 compatible device with mode A,B,C or D.
+ *
+ * @author Matthias Steigenberger - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MeterReader extends MeterDevice<DataMessage> {
+
+ public MeterReader(Supplier<SerialPortManager> serialPortManagerSupplier, String deviceId, String serialPort,
+ byte @Nullable [] initMessage, int baudrate, int baudrateChangeDelay, ProtocolMode protocolMode) {
+ super(serialPortManagerSupplier, deviceId, serialPort, initMessage, baudrate, baudrateChangeDelay,
+ protocolMode);
+ }
+
+ @Override
+ protected IMeterReaderConnector<DataMessage> createConnector(Supplier<SerialPortManager> serialPortManagerSupplier,
+ String serialPort, int baudrate, int baudrateChangeDelay, ProtocolMode protocolMode) {
+ return new SerialConnector(serialPortManagerSupplier, serialPort, baudrate, baudrateChangeDelay, protocolMode);
+ }
+
+ @Override
+ protected <Q extends Quantity<Q>> void populateValueCache(DataMessage smlFile) {
+ for (DataSet dataSet : smlFile.getDataSets()) {
+ String address = dataSet.getAddress();
+ if (address != null && !address.isEmpty()) {
+ addObisCache(new MeterValue<Q>(address, dataSet.getValue(), UnitConversion.getUnit(dataSet.getUnit())));
+ }
+ }
+ }
+}
--- /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.smartmeter.internal.iec62056;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.function.Supplier;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.smartmeter.connectors.ConnectorBase;
+import org.openhab.binding.smartmeter.internal.helper.Baudrate;
+import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
+import org.openhab.core.io.transport.serial.SerialPortManager;
+import org.openmuc.j62056.DataMessage;
+import org.openmuc.j62056.Iec21Port;
+import org.openmuc.j62056.Iec21Port.Builder;
+import org.openmuc.j62056.ModeDListener;
+import org.reactivestreams.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.reactivex.Flowable;
+import io.reactivex.FlowableEmitter;
+
+/**
+ * This connector reads meter values with IEC62056-21 protocol.
+ *
+ * @author Matthias Steigenberger - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SerialConnector extends ConnectorBase<DataMessage> {
+
+ private final Logger logger = LoggerFactory.getLogger(SerialConnector.class);
+ private int baudrate;
+ private int baudrateChangeDelay;
+ private ProtocolMode protocolMode;
+ @Nullable
+ private Iec21Port iec21Port;
+
+ public SerialConnector(Supplier<SerialPortManager> serialPortManagerSupplier, String portName, int baudrate,
+ int baudrateChangeDelay, ProtocolMode protocolMode) {
+ super(portName);
+ this.baudrate = baudrate;
+ this.baudrateChangeDelay = baudrateChangeDelay;
+ this.protocolMode = protocolMode;
+ }
+
+ @Override
+ protected boolean applyPeriod() {
+ return protocolMode != ProtocolMode.D;
+ }
+
+ @Override
+ protected boolean applyRetryHandling() {
+ return protocolMode != ProtocolMode.D;
+ }
+
+ @Override
+ protected Publisher<?> getRetryPublisher(Duration period, Publisher<Throwable> attempts) {
+ if (protocolMode == ProtocolMode.D) {
+ return Flowable.empty();
+ } else {
+ return super.getRetryPublisher(period, attempts);
+ }
+ }
+
+ @Override
+ protected DataMessage readNext(byte @Nullable [] initMessage) throws IOException {
+ Iec21Port iec21Port = this.iec21Port;
+ if (iec21Port != null) {
+ DataMessage dataMessage = iec21Port.read();
+ logger.debug("Datamessage read: {}", dataMessage);
+ return dataMessage;
+ }
+ throw new IOException("SerialPort was not yet created!");
+ }
+
+ @Override
+ protected void emitValues(byte @Nullable [] initMessage, FlowableEmitter<@Nullable DataMessage> emitter)
+ throws IOException {
+ switch (protocolMode) {
+ case ABC:
+ super.emitValues(initMessage, emitter);
+ break;
+ case D:
+ Iec21Port iec21Port = this.iec21Port;
+ if (iec21Port != null) {
+ iec21Port.listen(new ModeDListener() {
+
+ @Override
+ public void newDataMessage(@Nullable DataMessage dataMessage) {
+ logger.debug("Datamessage read: {}", dataMessage);
+ emitter.onNext(dataMessage);
+ }
+
+ @Override
+ public void exceptionWhileListening(@Nullable Exception e) {
+ logger.warn("Exception while listening for mode D data message", e);
+ }
+ });
+ this.iec21Port = iec21Port;
+ }
+ break;
+ case SML:
+ throw new IOException("SML mode not supported");
+ }
+ }
+
+ @Override
+ public void openConnection() throws IOException {
+ Builder iec21Builder = new Iec21Port.Builder(getPortName());
+ if (Baudrate.fromBaudrate(this.baudrate) != Baudrate.AUTO) {
+ iec21Builder.setInitialBaudrate(this.baudrate);
+ }
+ iec21Builder.setBaudRateChangeDelay(baudrateChangeDelay);
+ iec21Builder.enableVerboseMode(true);
+ iec21Port = iec21Builder.buildAndOpen();
+ }
+
+ @Override
+ public void closeConnection() {
+ Iec21Port iec21Port = this.iec21Port;
+ if (iec21Port != null) {
+ iec21Port.close();
+ }
+ }
+}
--- /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.smartmeter.internal.iec62056;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.types.util.UnitUtils;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Converts a unit from IEC62056-21 protocol to a {@link Unit}
+ *
+ * @author Matthias Steigenberger - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class UnitConversion {
+
+ @SuppressWarnings("unchecked")
+ public static @Nullable <Q extends Quantity<Q>> Unit<Q> getUnit(String unit) {
+ if (!unit.isEmpty()) {
+ try {
+ return (Unit<Q>) UnitUtils.parseUnit(" " + unit);
+ } catch (Exception e) {
+ LoggerFactory.getLogger(UnitConversion.class).warn("Failed to parse unit {}: {}", unit, e.getMessage());
+ return null;
+ }
+ }
+ return null;
+ }
+}
private static void parseGetListResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got GetListResponse");
- SmlGetListRes sml_listRes = (SmlGetListRes) smlMessage.getMessageBody().getChoice();
+ SmlGetListRes smlListRes = (SmlGetListRes) smlMessage.getMessageBody().getChoice();
// consumer.accept(sml_listRes.toString());
- consumer.accept(sml_listRes.toStringIndent(" "));
+ consumer.accept(smlListRes.toStringIndent(" "));
}
private static void parseAttentionResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got AttentionResponse");
- SmlAttentionRes sml_attentionRes = (SmlAttentionRes) smlMessage.getMessageBody().getChoice();
- consumer.accept(sml_attentionRes.toString());
+ SmlAttentionRes smlAttentionRes = (SmlAttentionRes) smlMessage.getMessageBody().getChoice();
+ consumer.accept(smlAttentionRes.toString());
}
private static void parseGetProcParameterResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got GetProcParameterResponse");
- SmlGetProcParameterRes sml_getProcParameterRes = (SmlGetProcParameterRes) smlMessage.getMessageBody()
+ SmlGetProcParameterRes smlGetProcParameterRes = (SmlGetProcParameterRes) smlMessage.getMessageBody()
.getChoice();
- consumer.accept(sml_getProcParameterRes.toString());
+ consumer.accept(smlGetProcParameterRes.toString());
}
private static void parseGetProfileListResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got GetProfileListResponse");
- SmlGetProfileListRes sml_getProfileListRes = (SmlGetProfileListRes) smlMessage.getMessageBody().getChoice();
- consumer.accept(sml_getProfileListRes.toString());
+ SmlGetProfileListRes smlGetProfileListRes = (SmlGetProfileListRes) smlMessage.getMessageBody().getChoice();
+ consumer.accept(smlGetProfileListRes.toString());
}
private static void parseOpenResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got OpenResponse");
- SmlPublicOpenRes sml_PublicOpenRes = (SmlPublicOpenRes) smlMessage.getMessageBody().getChoice();
- consumer.accept(sml_PublicOpenRes.toString());
+ SmlPublicOpenRes smlPublicOpenRes = (SmlPublicOpenRes) smlMessage.getMessageBody().getChoice();
+ consumer.accept(smlPublicOpenRes.toString());
}
private static void parseCloseResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got CloseResponse");
- SmlPublicCloseRes sml_PublicCloseRes = (SmlPublicCloseRes) smlMessage.getMessageBody().getChoice();
- consumer.accept(sml_PublicCloseRes.toString());
+ SmlPublicCloseRes smlPublicCloseRes = (SmlPublicCloseRes) smlMessage.getMessageBody().getChoice();
+ consumer.accept(smlPublicCloseRes.toString());
}
private static void parseGetProfilePackResponse(SmlMessage smlMessage, Consumer<String> consumer) {
consumer.accept("Got GetProfilePackResponse");
- SmlGetProfilePackRes sml_getProfilePackRes = (SmlGetProfilePackRes) smlMessage.getMessageBody().getChoice();
- consumer.accept(sml_getProfilePackRes.toString());
+ SmlGetProfilePackRes smlGetProfilePackRes = (SmlGetProfilePackRes) smlMessage.getMessageBody().getChoice();
+ consumer.accept(smlGetProfilePackRes.toString());
}
// ========================= Requests =================================
protected SmlFile readNext(byte @Nullable [] initMessage) throws IOException {
if (initMessage != null) {
logger.debug("Writing init message: {}", HexUtils.bytesToHex(initMessage, " "));
+ DataOutputStream os = this.os;
if (os != null) {
os.write(initMessage);
os.flush();
// read out the whole buffer. We are only interested in the most recent SML file.
Stack<SmlFile> smlFiles = new Stack<>();
+ DataInputStream is = this.is;
do {
logger.trace("Reading {}. SML message", smlFiles.size() + 1);
smlFiles.push(TRANSPORT.getSMLFile(is));
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void closeConnection() {
try {
+ DataInputStream is = this.is;
if (is != null) {
is.close();
is = null;
logger.error("Failed to close serial input stream", e);
}
try {
+ DataOutputStream os = this.os;
if (os != null) {
os.close();
os = null;
import java.io.IOException;
import java.util.function.Supplier;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.smartmeter.connectors.ConnectorBase;
/**
* @author Matthias Steigenberger - Initial contribution
*
*/
+@NonNullByDefault
public class MockMeterReaderConnector extends ConnectorBase<Object> {
private boolean applyRetry;
}
@Override
- protected Object readNext(byte[] initMessage) throws IOException {
+ protected Object readNext(byte @Nullable [] initMessage) throws IOException {
try {
return readNextSupplier.get();
} catch (RuntimeException e) {
- if (e.getCause() instanceof IOException) {
- throw (IOException) e.getCause();
+ if (e.getCause() instanceof IOException cause) {
+ throw cause;
}
throw e;
}
import javax.measure.Quantity;
-import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
* @author Matthias Steigenberger - Initial contribution
*
*/
+@NonNullByDefault
public class TestMeterReading {
@Test
throw new RuntimeException(new IOException("fucked up"));
}));
MeterDevice<Object> meter = getMeterDevice(connector);
+ @SuppressWarnings("unchecked")
Consumer<Throwable> errorHandler = mock(Consumer.class);
RxJavaPlugins.setErrorHandler(errorHandler);
MeterValueListener changeListener = Mockito.mock(MeterValueListener.class);
return new MeterDevice<>(() -> mock(SerialPortManager.class), "id", "port", null, 9600, 0, ProtocolMode.SML) {
@Override
- protected @NonNull IMeterReaderConnector<Object> createConnector(
- @NonNull Supplier<@NonNull SerialPortManager> serialPortManagerSupplier, @NonNull String serialPort,
- int baudrate, int baudrateChangeDelay, @NonNull ProtocolMode protocolMode) {
+ protected IMeterReaderConnector<Object> createConnector(
+ Supplier<SerialPortManager> serialPortManagerSupplier, String serialPort, int baudrate,
+ int baudrateChangeDelay, ProtocolMode protocolMode) {
return connector;
}
+ @SuppressWarnings({ "rawtypes", "unchecked" })
@Override
- protected <Q extends @NonNull Quantity<Q>> void populateValueCache(Object smlFile) {
+ protected <Q extends Quantity<Q>> void populateValueCache(Object smlFile) {
addObisCache(new MeterValue("123", "333", null));
}
};
import static org.junit.jupiter.api.Assertions.*;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.smartmeter.internal.MeterValue;
import org.openhab.binding.smartmeter.internal.conformity.negate.NegateBitModel;
* @author Matthias Steigenberger - Initial contribution
*
*/
+@NonNullByDefault
public class TestNegateBit {
@Test