diff --git a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AbstractAmsAdsThingHandler.java b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AbstractAmsAdsThingHandler.java index 93f713cd..ce930eb8 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AbstractAmsAdsThingHandler.java +++ b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AbstractAmsAdsThingHandler.java @@ -36,13 +36,16 @@ import org.connectorio.addons.binding.amsads.internal.config.AmsConfiguration; import org.connectorio.addons.binding.amsads.internal.handler.channel.AdsChannelHandler; import org.connectorio.addons.binding.amsads.internal.handler.channel.ChannelHandlerFactory; -import org.connectorio.addons.binding.amsads.internal.handler.polling.FetchContainer; -import org.connectorio.addons.binding.amsads.internal.handler.polling.PollFetchContainer; -import org.connectorio.addons.binding.amsads.internal.handler.polling.SubscribeFetchContainer; import org.connectorio.addons.binding.amsads.internal.symbol.SymbolEntry; import org.connectorio.addons.binding.amsads.internal.symbol.SymbolReader; import org.connectorio.addons.binding.amsads.internal.symbol.SymbolReaderFactory; import org.connectorio.addons.binding.handler.GenericThingHandlerBase; +import org.connectorio.addons.binding.plc4x.sampler.DefaultPlc4xSampler; +import org.connectorio.addons.binding.plc4x.sampler.DefaultPlc4xSamplerComposer; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; +import org.connectorio.addons.binding.plc4x.source.SubscriberSource; +import org.connectorio.addons.binding.source.sampling.SamplingSource; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -62,17 +65,20 @@ public abstract class AbstractAmsAdsThingHandler> handlerMap = new ConcurrentHashMap<>(); private final CompletableFuture initializer = new CompletableFuture<>();; - private FetchContainer subscriber; - private FetchContainer poller; + private SubscriberSource subscriber; + private SamplingSource> poller; - public AbstractAmsAdsThingHandler(Thing thing, SymbolReaderFactory symbolReaderFactory, ChannelHandlerFactory channelHandlerFactory) { + public AbstractAmsAdsThingHandler(Thing thing, SymbolReaderFactory symbolReaderFactory, + ChannelHandlerFactory channelHandlerFactory, SourceFactory sourceFactory) { super(thing); this.symbolReaderFactory = symbolReaderFactory; this.channelHandlerFactory = channelHandlerFactory; + this.sourceFactory = sourceFactory; } @Override @@ -160,8 +166,8 @@ public void initialize() { } List channels = getThing().getChannels(); - poller = new PollFetchContainer(scheduler, connection); - subscriber = new SubscribeFetchContainer(connection); + poller = sourceFactory.sampling(scheduler, new DefaultPlc4xSamplerComposer<>(connection)); + subscriber = sourceFactory.subscriber(connection); for (Channel channel : channels) { AdsChannelHandler handler = channelHandlerFactory.map(thing, getCallback(), channel); if (handler != null) { @@ -172,9 +178,9 @@ public void initialize() { continue; } if (handler.getRefreshInterval() != null) { - poller.add(handler.getRefreshInterval(), channelId, tag, handler::onChange); + poller.add(handler.getRefreshInterval(), channelId, new DefaultPlc4xSampler<>(connection, channelId, tag, handler::onChange)); } else { - subscriber.add(null, channelId, tag, handler::onChange); + subscriber.add(channelId, tag, handler::onChange); } // register handler so we can dispatch commands handlerMap.put(channelId, new SimpleEntry<>(tag, handler)); diff --git a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsHandlerFactory.java b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsHandlerFactory.java index cb794b03..19c65cc2 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsHandlerFactory.java +++ b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsHandlerFactory.java @@ -27,6 +27,7 @@ import org.connectorio.addons.binding.amsads.internal.symbol.DefaultSymbolReaderFactory; import org.connectorio.addons.binding.amsads.internal.symbol.SymbolReaderFactory; import org.connectorio.addons.binding.plc4x.Plc4xHandlerFactory; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; import org.connectorio.plc4x.extras.osgi.PlcDriverManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -48,6 +49,7 @@ public class AmsAdsHandlerFactory extends Plc4xHandlerFactory { private final PlcDriverManager driverManager; + private final SourceFactory sourceFactory; private final SymbolReaderFactory symbolReaderFactory = new DefaultSymbolReaderFactory(); @@ -56,9 +58,10 @@ public class AmsAdsHandlerFactory extends Plc4xHandlerFactory { private AmsAdsDiscoveryDriver discoveryDriver; @Activate - public AmsAdsHandlerFactory(@Reference PlcDriverManager driverManager) { + public AmsAdsHandlerFactory(@Reference PlcDriverManager driverManager, @Reference(target = "(plc4x=true)") SourceFactory sourceFactory) { super(THING_TYPE_AMS, THING_TYPE_NETWORK, THING_TYPE_SERIAL); this.driverManager = driverManager; + this.sourceFactory = sourceFactory; } @Override @@ -68,9 +71,10 @@ protected ThingHandler createHandler(Thing thing) { if (THING_TYPE_AMS.equals(thingTypeUID)) { return new AmsBridgeHandler((Bridge) thing, discoveryDriver); } else if (THING_TYPE_NETWORK.equals(thingTypeUID)) { - return new AmsAdsNetworkBridgeHandler(thing, symbolReaderFactory, channelHandlerFactory, driverManager, discoveryDriver); + return new AmsAdsNetworkBridgeHandler(thing, symbolReaderFactory, channelHandlerFactory, driverManager, sourceFactory, discoveryDriver); } else if (THING_TYPE_SERIAL.equals(thingTypeUID)) { - return new AmsAdsSerialBridgeHandler(thing, symbolReaderFactory, channelHandlerFactory, driverManager); + return new AmsAdsSerialBridgeHandler(thing, symbolReaderFactory, channelHandlerFactory, + driverManager, sourceFactory); } return null; diff --git a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandler.java b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandler.java index 08b0b728..90b75d61 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandler.java @@ -36,7 +36,6 @@ import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; -import org.apache.plc4x.java.transport.serial.SerialTransport; import org.connectorio.addons.binding.amsads.internal.AmsConverter; import org.connectorio.addons.binding.amsads.internal.config.AmsConfiguration; import org.connectorio.addons.binding.amsads.internal.config.NetworkConfiguration; @@ -45,12 +44,11 @@ import org.connectorio.addons.binding.amsads.internal.discovery.DiscoverySender.Envelope; import org.connectorio.addons.binding.amsads.internal.handler.channel.ChannelHandlerFactory; import org.connectorio.addons.binding.amsads.internal.symbol.SymbolReaderFactory; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; import org.connectorio.plc4x.extras.osgi.PlcDriverManager; -import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,8 +67,8 @@ public class AmsAdsNetworkBridgeHandler extends AbstractAmsAdsThingHandler> operations = new ConcurrentHashMap<>(); - Map> futures = new ConcurrentHashMap<>(); - - public PollFetchContainer(ScheduledExecutorService executor, PlcConnection connection) { - this.executor = executor; - this.connection = connection; - } - - @Override - public void add(Long interval, String channelId, AdsTag tag, Consumer onChange) { - if (!operations.containsKey(interval)) { - operations.put(interval, new HashMap<>()); - } - operations.get(interval).put(channelId, new Operation(tag, onChange)); - } - - @Override - public boolean start() { - if (operations.isEmpty()) { - return false; - } - - for (Entry> operation : operations.entrySet()) { - Builder requestBuilder = connection.readRequestBuilder(); - Map polledValues = operation.getValue(); - for (Entry polledValue : polledValues.entrySet()) { - requestBuilder.addTag(polledValue.getKey(), polledValue.getValue().tag); - } - - ScheduledFuture future = executor.scheduleAtFixedRate(new PollingRunnable(requestBuilder.build(), polledValues), - operation.getKey(), operation.getKey(), TimeUnit.MILLISECONDS); - futures.put(operation.getKey(), future); - } - - return true; - } - - @Override - public void stop() { - futures.forEach((key, future) -> future.cancel(true)); - } - - static class Operation { - - private final AdsTag tag; - private final Consumer onChange; - - public Operation(AdsTag tag, Consumer onChange) { - this.tag = tag; - this.onChange = onChange; - } - - public void onChange(Object object) { - onChange.accept(object); - } - } - - static class PollingRunnable implements Runnable { - - private final Logger logger = LoggerFactory.getLogger(PollingRunnable.class); - - private final PlcReadRequest request; - private final Map polledValues; - - PollingRunnable(PlcReadRequest request, Map polledValues) { - this.request = request; - this.polledValues = polledValues; - } - - @Override - public void run() { - logger.debug("Fetching data for channels {}", polledValues.keySet()); - request.execute().whenComplete((result, error) -> { - if (error != null) { - logger.warn("Failed to poll data through cyclic read request", error); - return; - } - - for (String tag : result.getTagNames()) { - PlcResponseCode responseCode = result.getResponseCode(tag); - if (polledValues.containsKey(tag) && responseCode == PlcResponseCode.OK) { - polledValues.get(tag).onChange(result.getObject(tag)); - } else { - logger.warn("Unexpected response code {} for channel {}", responseCode, tag); - } - } - }); - } - } -} diff --git a/bundles/org.connectorio.addons.binding.amsads/src/test/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandlerTest.java b/bundles/org.connectorio.addons.binding.amsads/src/test/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandlerTest.java index e0a60279..bfd2764c 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/test/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandlerTest.java +++ b/bundles/org.connectorio.addons.binding.amsads/src/test/java/org/connectorio/addons/binding/amsads/internal/handler/AmsAdsNetworkBridgeHandlerTest.java @@ -39,6 +39,7 @@ import org.connectorio.addons.binding.amsads.internal.config.AmsConfiguration; import org.connectorio.addons.binding.amsads.internal.config.NetworkConfiguration; import org.connectorio.addons.binding.amsads.internal.discovery.AmsAdsDiscoveryDriver; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; import org.connectorio.addons.binding.test.ThingMock; import org.connectorio.plc4x.extras.osgi.core.internal.OsgiDriverManager; import org.connectorio.addons.binding.test.BridgeMock; @@ -61,6 +62,9 @@ class AmsAdsNetworkBridgeHandlerTest { @Mock ChannelHandlerFactory channelHandlerFactory; + @Mock + SourceFactory sourceFactory; + @Mock AmsAdsDiscoveryDriver discoveryDriver; @@ -70,7 +74,7 @@ void testHandlerInitializationWithNoConfig() { .create(); AmsAdsNetworkBridgeHandler handler = new AmsAdsNetworkBridgeHandler(bridge, symbolReaderFactory, - channelHandlerFactory, new OsgiDriverManager(), discoveryDriver); + channelHandlerFactory, new OsgiDriverManager(), sourceFactory, discoveryDriver); handler.initialize(); CompletableFuture initializer = handler.getPlcConnection(); @@ -101,7 +105,7 @@ void testHandlerInitializationWithConfig() { Thing thing = thingMock.create(); AmsAdsNetworkBridgeHandler handler = new AmsAdsNetworkBridgeHandler(thing, symbolReaderFactory, - channelHandlerFactory, new OsgiDriverManager(), discoveryDriver); + channelHandlerFactory, new OsgiDriverManager(), sourceFactory, discoveryDriver); handler.setCallback(thingMock.getCallback()); handler.initialize(); diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/BACnetThingHandlerFactory.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/BACnetThingHandlerFactory.java index 633e62ff..4b77968c 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/BACnetThingHandlerFactory.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/BACnetThingHandlerFactory.java @@ -21,20 +21,13 @@ */ package org.connectorio.addons.binding.bacnet.internal; +import org.code_house.bacnet4j.wrapper.api.Type; import org.connectorio.addons.binding.bacnet.internal.handler.network.BACnetIpv4BridgeHandler; import org.connectorio.addons.binding.bacnet.internal.handler.network.BACnetMstpBridgeHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.AnalogInputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.AnalogOutputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.AnalogValueHandler; import org.connectorio.addons.binding.bacnet.internal.handler.object.BACnetIpDeviceHandler; import org.connectorio.addons.binding.bacnet.internal.handler.object.BACnetMstpDeviceHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.BinaryInputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.BinaryOutputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.BinaryValueHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.MultiStateInputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.MultiStateOutputHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.MultiStateValueHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.ScheduleHandler; +import org.connectorio.addons.binding.bacnet.internal.handler.object.BACnetObjectThingHandler; +import org.connectorio.addons.binding.source.SourceFactory; import org.connectorio.addons.communication.watchdog.WatchdogManager; import org.connectorio.addons.link.LinkManager; import org.openhab.core.thing.Bridge; @@ -44,7 +37,6 @@ import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.io.transport.serial.SerialPortManager; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -59,12 +51,15 @@ public class BACnetThingHandlerFactory extends BaseThingHandlerFactory implement private final SerialPortManager serialPortManager; private final LinkManager linkManager; private final WatchdogManager watchdogManager; + private final SourceFactory sourceFactory; @Activate public BACnetThingHandlerFactory(@Reference SerialPortManager serialPortManager, @Reference LinkManager linkManager, + @Reference(target = "(default=true)") SourceFactory sourceFactory, @Reference WatchdogManager watchdogManager) { this.serialPortManager = serialPortManager; this.linkManager = linkManager; + this.sourceFactory = sourceFactory; this.watchdogManager = watchdogManager; } @@ -74,9 +69,9 @@ protected ThingHandler createHandler(Thing thing) { if (thing instanceof Bridge) { if (IP_DEVICE_THING_TYPE.equals(thingTypeUID)) { - return new BACnetIpDeviceHandler((Bridge) thing, linkManager, watchdogManager); + return new BACnetIpDeviceHandler((Bridge) thing, linkManager, sourceFactory, watchdogManager); } else if (MSTP_DEVICE_THING_TYPE.equals(thingTypeUID)) { - return new BACnetMstpDeviceHandler((Bridge) thing, linkManager, watchdogManager); + return new BACnetMstpDeviceHandler((Bridge) thing, linkManager, sourceFactory, watchdogManager); } else if (IPV4_BRIDGE_THING_TYPE.equals(thingTypeUID)) { return new BACnetIpv4BridgeHandler((Bridge) thing); // } else if (IPV6_BRIDGE_THING_TYPE.equals(thingTypeUID)) { @@ -87,25 +82,25 @@ protected ThingHandler createHandler(Thing thing) { } if (ANALOG_INPUT_THING_TYPE.equals(thingTypeUID)) { - return new AnalogInputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.ANALOG_INPUT, sourceFactory); } else if (ANALOG_OUTPUT_THING_TYPE.equals(thingTypeUID)) { - return new AnalogOutputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.ANALOG_OUTPUT, sourceFactory); } else if (ANALOG_VALUE_THING_TYPE.equals(thingTypeUID)) { - return new AnalogValueHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.ANALOG_VALUE, sourceFactory); } else if (BINARY_INPUT_THING_TYPE.equals(thingTypeUID)) { - return new BinaryInputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.BINARY_INPUT, sourceFactory); } else if (BINARY_OUTPUT_THING_TYPE.equals(thingTypeUID)) { - return new BinaryOutputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.BINARY_OUTPUT, sourceFactory); } else if (BINARY_VALUE_THING_TYPE.equals(thingTypeUID)) { - return new BinaryValueHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.BINARY_VALUE, sourceFactory); } else if (MULTISTATE_INPUT_THING_TYPE.equals(thingTypeUID)) { - return new MultiStateInputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.MULTISTATE_INPUT, sourceFactory); } else if (MULTISTATE_OUTPUT_THING_TYPE.equals(thingTypeUID)) { - return new MultiStateOutputHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.MULTISTATE_OUTPUT, sourceFactory); } else if (MULTISTATE_VALUE_THING_TYPE.equals(thingTypeUID)) { - return new MultiStateValueHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.MULTISTATE_VALUE, sourceFactory); } else if (SCHEDULE_THING_TYPE.equals(thingTypeUID)) { - return new ScheduleHandler(thing); + return new BACnetObjectThingHandler<>(thing, Type.SCHEDULE, sourceFactory); } return null; diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/AbstractTask.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/channel/converter/CompositeConverter.java similarity index 95% rename from bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/AbstractTask.java rename to bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/channel/converter/CompositeConverter.java index 6ef3577c..f95f931c 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/AbstractTask.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/channel/converter/CompositeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. + * Copyright (C) 2019-2024 ConnectorIO sp. z o.o. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ * * SPDX-License-Identifier: GPL-3.0-or-later */ -package org.connectorio.addons.binding.bacnet.internal.handler.object.task; +package org.connectorio.addons.binding.bacnet.internal.handler.channel.converter; import com.serotonin.bacnet4j.enums.DayOfWeek; import com.serotonin.bacnet4j.enums.Month; @@ -50,21 +50,21 @@ import java.util.Map; import java.util.function.Function; import org.code_house.bacnet4j.wrapper.api.BacNetToJavaConverter; -import org.connectorio.addons.temporal.calendar.CalendarEntryType; -import org.connectorio.addons.temporal.calendar.CalendarType; -import org.connectorio.addons.temporal.calendar.DateCalendarEntryType; import org.connectorio.addons.temporal.DayOfWeekType; import org.connectorio.addons.temporal.DayScheduleType; import org.connectorio.addons.temporal.LocalDateRangeType; import org.connectorio.addons.temporal.LocalDateType; import org.connectorio.addons.temporal.LocalTimeType; +import org.connectorio.addons.temporal.WeekInMonthType; +import org.connectorio.addons.temporal.WeeklyScheduleType; +import org.connectorio.addons.temporal.calendar.CalendarEntryType; +import org.connectorio.addons.temporal.calendar.CalendarType; +import org.connectorio.addons.temporal.calendar.CompositeCalendarEntryType; +import org.connectorio.addons.temporal.calendar.DateCalendarEntryType; +import org.connectorio.addons.temporal.calendar.RangeCalendarEntryType; import org.connectorio.addons.temporal.month.BasicMonthType; import org.connectorio.addons.temporal.month.ExtendedMonthType; import org.connectorio.addons.temporal.month.MonthType; -import org.connectorio.addons.temporal.calendar.RangeCalendarEntryType; -import org.connectorio.addons.temporal.calendar.CompositeCalendarEntryType; -import org.connectorio.addons.temporal.WeekInMonthType; -import org.connectorio.addons.temporal.WeeklyScheduleType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringListType; @@ -73,9 +73,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractTask implements Runnable, BacNetToJavaConverter { +/** + * Converter which attempts to map all known types of BACnet value kinds. + */ +public class CompositeConverter implements BacNetToJavaConverter { + + public static final BacNetToJavaConverter INSTANCE = new CompositeConverter(); + + private final Logger logger = LoggerFactory.getLogger(CompositeConverter.class); - private final Logger logger = LoggerFactory.getLogger(AbstractTask.class); + private CompositeConverter() {} @Override public State fromBacNet(Encodable encodable) { @@ -102,7 +109,7 @@ public State fromBacNet(Encodable encodable) { return new LocalDateType(date.calculateGC().toZonedDateTime()); } else if (encodable instanceof Enumerated) { return new DecimalType(((Enumerated) encodable).intValue()); - } else if (encodable instanceof SequenceOf) { + } else if (encodable instanceof SequenceOf) { SequenceOf sequence = (SequenceOf) encodable; if (sequence.getCount() > 0) { Encodable value = sequence.get(0); diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogInputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogInputHandler.java deleted file mode 100644 index 12284498..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogInputHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class AnalogInputHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public AnalogInputHandler(Thing thing) { - super(thing, Type.ANALOG_INPUT); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogOutputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogOutputHandler.java deleted file mode 100644 index 146b3a28..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogOutputHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class AnalogOutputHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public AnalogOutputHandler(Thing thing) { - super(thing, Type.ANALOG_OUTPUT); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogValueHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogValueHandler.java deleted file mode 100644 index f4c12237..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/AnalogValueHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class AnalogValueHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public AnalogValueHandler(Thing thing) { - super(thing, Type.ANALOG_VALUE); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetDeviceHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetDeviceHandler.java index b4c0869d..1eb5c2aa 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetDeviceHandler.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetDeviceHandler.java @@ -31,15 +31,10 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import org.code_house.bacnet4j.wrapper.api.BacNetClient; import org.code_house.bacnet4j.wrapper.api.BacNetObject; import org.code_house.bacnet4j.wrapper.api.Device; @@ -51,17 +46,21 @@ import org.connectorio.addons.binding.bacnet.internal.command.PrioritizedCommand; import org.connectorio.addons.binding.bacnet.internal.command.ResetCommand; import org.connectorio.addons.binding.bacnet.internal.config.DeviceChannelConfig; -import org.connectorio.addons.binding.bacnet.internal.discovery.BACnetPropertyDiscoveryService; import org.connectorio.addons.binding.bacnet.internal.config.DeviceConfig; +import org.connectorio.addons.binding.bacnet.internal.discovery.BACnetPropertyDiscoveryService; import org.connectorio.addons.binding.bacnet.internal.handler.BACnetObjectBridgeHandler; +import org.connectorio.addons.binding.bacnet.internal.handler.channel.converter.CompositeConverter; import org.connectorio.addons.binding.bacnet.internal.handler.network.BACnetNetworkBridgeHandler; -import org.connectorio.addons.binding.bacnet.internal.handler.object.task.ReadObjectTask; -import org.connectorio.addons.binding.bacnet.internal.handler.object.task.Readout; -import org.connectorio.addons.binding.bacnet.internal.handler.object.task.RefreshDeviceTask; +import org.connectorio.addons.binding.bacnet.internal.handler.source.BACnetObjectsSampler; +import org.connectorio.addons.binding.bacnet.internal.handler.source.BACnetPropertySampler; +import org.connectorio.addons.binding.bacnet.internal.handler.source.BACnetSamplerComposer; +import org.connectorio.addons.binding.bacnet.internal.handler.source.ChannelCallback; +import org.connectorio.addons.binding.bacnet.internal.handler.source.SamplerCallback; +import org.connectorio.addons.binding.source.SourceFactory; +import org.connectorio.addons.binding.source.sampling.SamplingSource; import org.connectorio.addons.communication.watchdog.Watchdog; -import org.connectorio.addons.communication.watchdog.contrib.ThingStatusWatchdogListener; -import org.connectorio.addons.communication.watchdog.WatchdogBuilder; import org.connectorio.addons.communication.watchdog.WatchdogManager; +import org.connectorio.addons.link.LinkListener; import org.connectorio.addons.link.LinkManager; import org.connectorio.addons.temporal.item.TemporalItemFactory; import org.openhab.core.config.core.Configuration; @@ -75,7 +74,6 @@ import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.builder.BridgeBuilder; import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -83,27 +81,29 @@ import org.slf4j.LoggerFactory; public abstract class BACnetDeviceHandler extends BACnetObjectBridgeHandler, C> - implements BACnetDeviceBridgeHandler, C> { + implements BACnetDeviceBridgeHandler, C>, LinkListener { private final Logger logger = LoggerFactory.getLogger(getClass()); private final LinkManager linkManager; + private final SourceFactory sourceFactory; private WatchdogManager watchdogManager; private Device device; private CompletableFuture clientFuture = new CompletableFuture<>(); - private Map> pollers = new LinkedHashMap<>(); private boolean discoverObjects; private Watchdog watchdog; + private SamplingSource source; /** * Creates a new instance of this class for the {@link Thing}. * * @param bridge the thing that should be handled, not null */ - public BACnetDeviceHandler(Bridge bridge, LinkManager linkManager, WatchdogManager watchdogManager) { + public BACnetDeviceHandler(Bridge bridge, LinkManager linkManager, SourceFactory sourceFactory, WatchdogManager watchdogManager) { super(bridge); this.linkManager = linkManager; + this.sourceFactory = sourceFactory; this.watchdogManager = watchdogManager; } @@ -137,7 +137,7 @@ public void initialize() { } protected void initializeChannels(BacNetClient client) { - WatchdogBuilder watchdogBuilder = watchdogManager.builder(getThing()); + //WatchdogBuilder watchdogBuilder = watchdogManager.builder(getThing()); DeviceConfig deviceConfig = getConfigAs(DeviceConfig.class); discoverObjects = deviceConfig.discoverObjects; @@ -145,48 +145,26 @@ protected void initializeChannels(BacNetClient client) { updateChannels(client); } - Map> pollingMap = new LinkedHashMap<>(); - for (Channel channel : thing.getChannels()) { - DeviceChannelConfig deviceChannelConfig = channel.getConfiguration().as(DeviceChannelConfig.class); - Long refreshInterval = Optional.ofNullable(deviceChannelConfig.refreshInterval) - .filter(value -> value != 0) - .orElse(getRefreshInterval()); - watchdogBuilder.withChannel(channel.getUID(), refreshInterval); - if (!pollingMap.containsKey(refreshInterval)) { - pollingMap.put(refreshInterval, new LinkedHashSet<>()); - } - BacNetObject object = new BacNetObject(device, deviceChannelConfig.instance, deviceChannelConfig.type); - PropertyIdentifier propertyIdentifier = PropertyIdentifier.forName(deviceChannelConfig.propertyIdentifier); - Readout readout = new Readout(channel.getUID(), object, propertyIdentifier); - pollingMap.get(refreshInterval).add(readout); - } + this.source = sourceFactory.sampling(scheduler, new BACnetSamplerComposer(client)); + configureSource(client); //this.watchdog = watchdogBuilder.build(getCallback(), new ThingStatusWatchdogListener(getThing(), getCallback())); - for (Entry> entry : pollingMap.entrySet()) { - ScheduledFuture poller = scheduler.scheduleAtFixedRate(new RefreshDeviceTask(() -> clientFuture, thing, getCallback(), device, entry.getValue(), linkManager), - 0, entry.getKey(), TimeUnit.MILLISECONDS); - pollers.put(entry.getKey(), poller); - } + source.start(); + linkManager.registerListener(thing, this); updateStatus(ThingStatus.ONLINE); clientFuture.complete(client); } @Override public void dispose() { + linkManager.deregisterListener(thing, this); + if (watchdog != null) { watchdog.close(); } - if (pollers != null) { - for (Entry> entry : pollers.entrySet()) { - try { - ScheduledFuture value = entry.getValue(); - value.cancel(true); - } catch (Exception e) { - logger.warn("Error during shutdown of poller checking device {} every {}ms", device, entry.getKey()); - } - } - pollers = null; + if (source != null) { + source.stop(); } super.dispose(); } @@ -346,14 +324,20 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } - final CompletableFuture client = getBridgeHandler().get().getClient(); - DeviceChannelConfig config = getThing().getChannel(channelUID).getConfiguration().as(DeviceChannelConfig.class); + final CompletableFuture clientFuture = getBridgeHandler().get().getClient(); + Channel channel = getThing().getChannel(channelUID); + DeviceChannelConfig config = channel.getConfiguration().as(DeviceChannelConfig.class); BacNetObject object = new BacNetObject(device, config.instance, config.type); String attribute = config.propertyIdentifier; //Integer writePriority = config.writePriority; if (command == RefreshType.REFRESH) { - scheduler.execute(new ReadObjectTask(() -> client, getCallback(), object, Collections.singleton(channelUID))); + if (source != null) { + clientFuture.thenAccept(client -> { + source.request(new BACnetObjectsSampler(client, object, attribute, new SamplerCallback( + CompositeConverter.INSTANCE, new ChannelCallback(getCallback(), channel)))); + }); + } } else if (command instanceof ResetCommand) { ResetCommand reset = (ResetCommand) command; JavaToBacNetConverter converter = (value) -> { @@ -363,16 +347,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (reset.getPriority() == null) { if (config.writePriority == null) { logger.debug("Submitting NULL value for channel {} to {}", channelUID, object); - client.join().setObjectPropertyValue(object, attribute, null, converter); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter); } else { logger.debug("Submitting NULL value for channel {} to {} with priority {}", channelUID, object, config.writePriority); - client.join().setObjectPropertyValue(object, attribute, null, converter, config.writePriority); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter, config.writePriority); } } else { logger.debug("Submitting NULL value for channel {} to {} with custom reset priority {}", channelUID, object, config.writePriority); - client.join().setObjectPropertyValue(object, attribute, null, converter, reset.getPriority()); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter, reset.getPriority()); } } else { Priority priority = config.writePriority == null ? null : Priorities.get(config.writePriority) @@ -389,10 +373,10 @@ public void handleCommand(ChannelUID channelUID, Command command) { }; if (priority == null) { logger.debug("Submitting write {} from channel {} to {}", channelUID, command, object); - client.join().setObjectPropertyValue(object, attribute, command, converter); + clientFuture.join().setObjectPropertyValue(object, attribute, command, converter); } else { logger.debug("Submitting write {} from channel {} to {} with priority {}", channelUID, command, object, priority); - client.join().setObjectPropertyValue(object, attribute, command, converter, priority); + clientFuture.join().setObjectPropertyValue(object, attribute, command, converter, priority); } logger.debug("Command {} for property {} executed successfully", command, object); } @@ -416,4 +400,44 @@ public Device getDevice() { return device; } + @Override + public void linked(ChannelUID channelUID) { + reconfigureSource(); + } + + @Override + public void unlinked(ChannelUID channelUID) { + reconfigureSource(); + } + + + private void reconfigureSource() { + if (this.source != null) { + // if source is non-null then it was started before + getClient().thenAccept(client -> { + this.source.stop(); + configureSource(client); + this.source.start(); + }); + } + } + + private void configureSource(BacNetClient client) { + for (Channel channel : thing.getChannels()) { + if (!linkManager.isLinked(channel.getUID())) { + // do not poll unlinked channels + continue; + } + + DeviceChannelConfig deviceChannelConfig = channel.getConfiguration().as(DeviceChannelConfig.class); + Long refreshInterval = Optional.ofNullable(deviceChannelConfig.refreshInterval) + .filter(value -> value != 0) + .orElse(getRefreshInterval()); + BacNetObject object = new BacNetObject(device, deviceChannelConfig.instance, deviceChannelConfig.type); + Consumer consumer = new SamplerCallback(CompositeConverter.INSTANCE, new ChannelCallback(getCallback(), channel)); + source.add(refreshInterval, channel.getUID().getAsString(), new BACnetObjectsSampler(client, object, deviceChannelConfig.propertyIdentifier, consumer)); + } + } + + } diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetIpDeviceHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetIpDeviceHandler.java index d39258bd..3eb46870 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetIpDeviceHandler.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetIpDeviceHandler.java @@ -24,6 +24,7 @@ import org.code_house.bacnet4j.wrapper.api.Device; import org.code_house.bacnet4j.wrapper.device.ip.IpDevice; import org.connectorio.addons.binding.bacnet.internal.config.IpDeviceConfig; +import org.connectorio.addons.binding.source.SourceFactory; import org.connectorio.addons.communication.watchdog.WatchdogManager; import org.connectorio.addons.link.LinkManager; import org.openhab.core.thing.Bridge; @@ -36,10 +37,11 @@ public class BACnetIpDeviceHandler extends BACnetDeviceHandler { * * @param bridge the thing that should be handled, not null * @param linkManager manager to track channel links + * @param sourceFactory source factory used to sample BACnet devices * @param watchdogManager communication watchdog */ - public BACnetIpDeviceHandler(Bridge bridge, LinkManager linkManager, WatchdogManager watchdogManager) { - super(bridge, linkManager, watchdogManager); + public BACnetIpDeviceHandler(Bridge bridge, LinkManager linkManager, SourceFactory sourceFactory, WatchdogManager watchdogManager) { + super(bridge, linkManager, sourceFactory, watchdogManager); } @Override diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetMstpDeviceHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetMstpDeviceHandler.java index 4cc0a893..34c2de41 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetMstpDeviceHandler.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BACnetMstpDeviceHandler.java @@ -24,6 +24,7 @@ import org.code_house.bacnet4j.wrapper.api.Device; import org.code_house.bacnet4j.wrapper.device.mstp.MstpDevice; import org.connectorio.addons.binding.bacnet.internal.config.MstpDeviceConfig; +import org.connectorio.addons.binding.source.SourceFactory; import org.connectorio.addons.communication.watchdog.WatchdogManager; import org.connectorio.addons.link.LinkManager; import org.openhab.core.thing.Bridge; @@ -36,10 +37,11 @@ public class BACnetMstpDeviceHandler extends BACnetDeviceHandler, C extends ObjectConfig> +public class BACnetObjectThingHandler, C extends ObjectConfig> extends BasePollingThingHandler implements ObjectHandler { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Type type; + private final SourceFactory sourceFactory; private BacNetObject object; private Priority writePriority; - private Set> readers; + private SamplingSource source; - public BACnetObjectThingHandler(Thing thing, Type type) { + public BACnetObjectThingHandler(Thing thing, Type type, SourceFactory sourceFactory) { super(thing); this.type = type; + this.sourceFactory = sourceFactory; } @Override @@ -90,18 +91,20 @@ public void initialize() { logger.info("Using custom write priority {} for property {}", writePriority, object); } - Map> pollers = getThing().getChannels() - .stream() - .map(channel -> new SimpleEntry<>(channelPollInterval(channel.getUID()), channel)) - .collect(Collectors.groupingBy( - SimpleEntry::getKey, - Collectors.mapping(entry -> entry.getValue().getUID(), Collectors.toCollection(LinkedHashSet::new)) - )); - this.readers = new LinkedHashSet<>(); - for (Entry> poller : pollers.entrySet()) { - poll(poller.getKey(), poller.getValue()); - } - updateStatus(ThingStatus.ONLINE); + getBridgeHandler().ifPresent(bridge -> { + bridge.getClient().thenAccept(client -> { + + this.source = sourceFactory.sampling(scheduler, new BACnetSamplerComposer(client)); + for (Channel channel : thing.getChannels()) { + Long pollInterval = channelPollInterval(channel.getUID()); + + Consumer consumer = new SamplerCallback(CompositeConverter.INSTANCE, new ChannelCallback(getCallback(), channel)); + source.add(pollInterval, channel.getUID().getAsString(), new BACnetObjectsSampler(client, object, Names.dashed(channel.getUID().getId()), consumer)); + } + this.source.start(); + updateStatus(ThingStatus.ONLINE); + }); + }); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Link object to device"); } @@ -130,10 +133,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } - final CompletableFuture client = getBridgeHandler().get().getClient(); + final CompletableFuture clientFuture = getBridgeHandler().get().getClient(); if (command == RefreshType.REFRESH) { - scheduler.execute(new ReadObjectTask(() -> client, getCallback(), object, Collections.singleton(channelUID))); + clientFuture.thenAccept(client -> { + source.request(new BACnetObjectsSampler(client, object, attribute, new SamplerCallback( + CompositeConverter.INSTANCE, new ChannelCallback(getCallback(), thing.getChannel(channelUID))))); + }); } else if (command instanceof ResetCommand) { ResetCommand reset = (ResetCommand) command; JavaToBacNetConverter converter = (value) -> { @@ -144,16 +150,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (reset.getPriority() == null) { if (writePriority == null) { logger.debug("Submitting NULL value for channel {} to {}", channelUID, object); - client.join().setObjectPropertyValue(object, attribute, null, converter); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter); } else { logger.debug("Submitting NULL value for channel {} to {} with priority {}", channelUID, object, writePriority); - client.join().setObjectPropertyValue(object, attribute, null, converter, writePriority); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter, writePriority); } } else { logger.debug("Submitting NULL value for channel {} to {} with custom reset priority {}", channelUID, object, writePriority); - client.join().setObjectPropertyValue(object, attribute, null, converter, reset.getPriority()); + clientFuture.join().setObjectPropertyValue(object, attribute, null, converter, reset.getPriority()); } } else { Priority priority = writePriority; @@ -169,11 +175,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { }; if (priority == null) { logger.debug("Submitting write {} from channel {} to {}", channelUID, command, object); - client.join().setObjectPropertyValue(object, attribute, command, converter); + clientFuture.join().setObjectPropertyValue(object, attribute, command, converter); } else { logger.debug("Submitting write {} from channel {} to {} with priority {}", channelUID, command, object, priority); - client.join().setObjectPropertyValue(object, attribute, command, converter, priority); + clientFuture.join().setObjectPropertyValue(object, attribute, command, converter, priority); } logger.debug("Command {} for property {} executed successfully", command, object); } @@ -184,23 +190,14 @@ private void poll(long cycle, Set channels) { logger.error("Handler is not attached to an bridge or bridge initialization failed!"); return; } - - logger.info("Setting up BACnet object {} channels scrapper {} at rate {} ms", object, channels, cycle); - Supplier> client = () -> getBridgeHandler().get().getClient(); - if (object != null) { - this.readers.add(scheduler.scheduleAtFixedRate(new ReadObjectTask(client, getCallback(), - object, channels), - 0, cycle, TimeUnit.MILLISECONDS)); - } } @Override public void dispose() { super.dispose(); - if (readers != null) { - readers.forEach(future -> future.cancel(true)); - readers = null; + if (source != null) { + source.stop(); } } diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryOutputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryOutputHandler.java deleted file mode 100644 index d6cc0fd1..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryOutputHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class BinaryOutputHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public BinaryOutputHandler(Thing thing) { - super(thing, Type.BINARY_OUTPUT); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryValueHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryValueHandler.java deleted file mode 100644 index b4e2622b..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryValueHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class BinaryValueHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public BinaryValueHandler(Thing thing) { - super(thing, Type.BINARY_VALUE); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateInputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateInputHandler.java deleted file mode 100644 index c8140b9b..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateInputHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class MultiStateInputHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public MultiStateInputHandler(Thing thing) { - super(thing, Type.MULTISTATE_INPUT); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateOutputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateOutputHandler.java deleted file mode 100644 index c847460c..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateOutputHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class MultiStateOutputHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public MultiStateOutputHandler(Thing thing) { - super(thing, Type.MULTISTATE_OUTPUT); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateValueHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateValueHandler.java deleted file mode 100644 index 255fcfa3..00000000 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/MultiStateValueHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * https://www.gnu.org/licenses/gpl-3.0.txt - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; - -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; - -public class MultiStateValueHandler extends - BACnetObjectThingHandler, ObjectConfig> { - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public MultiStateValueHandler(Thing thing) { - super(thing, Type.MULTISTATE_VALUE); - } - - -} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Names.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Names.java index 113a53af..112e600a 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Names.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Names.java @@ -23,6 +23,8 @@ public class Names { + public final static String PRESENT_VALUE = "present-value"; + public static String dashed(String channel) { StringBuilder name = new StringBuilder(); for (char ch : channel.toCharArray()) { diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/ReadObjectTask.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/ReadObjectTask.java index 820270dd..d7c4ee84 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/ReadObjectTask.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/ReadObjectTask.java @@ -36,14 +36,18 @@ import org.code_house.bacnet4j.wrapper.api.BacNetClient; import org.code_house.bacnet4j.wrapper.api.BacNetClientException; import org.code_house.bacnet4j.wrapper.api.BacNetObject; +import org.code_house.bacnet4j.wrapper.api.BacNetToJavaConverter; +import org.connectorio.addons.binding.bacnet.internal.handler.channel.converter.CompositeConverter; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.binding.ThingHandlerCallback; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; +import org.osgi.util.converter.Converter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ReadObjectTask extends AbstractTask { +@Deprecated +public class ReadObjectTask implements Runnable { private final Logger logger = LoggerFactory.getLogger(ReadObjectTask.class); private final Supplier> client; @@ -76,7 +80,7 @@ public void run() { Object state = states.get(index++); State channelState = UnDefType.UNDEF; if (state instanceof Encodable) { - channelState = fromBacNet((Encodable) state); + channelState = CompositeConverter.INSTANCE.fromBacNet((Encodable) state); } logger.debug("Retrieved state for property {} attribute {}: {}", object, entry, channelState); diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Readout.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Readout.java index dc3b9bbe..e31d6931 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Readout.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/Readout.java @@ -25,6 +25,7 @@ import org.code_house.bacnet4j.wrapper.api.BacNetObject; import org.openhab.core.thing.ChannelUID; +@Deprecated public class Readout { public final ChannelUID channel; diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/RefreshDeviceTask.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/RefreshDeviceTask.java index bcbc8929..6e05048c 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/RefreshDeviceTask.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/task/RefreshDeviceTask.java @@ -38,6 +38,7 @@ import org.code_house.bacnet4j.wrapper.api.BacNetClientException; import org.code_house.bacnet4j.wrapper.api.BacNetObject; import org.code_house.bacnet4j.wrapper.api.Device; +import org.connectorio.addons.binding.bacnet.internal.handler.channel.converter.CompositeConverter; import org.connectorio.addons.link.LinkManager; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -46,7 +47,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RefreshDeviceTask extends AbstractTask { +@Deprecated +public class RefreshDeviceTask implements Runnable { private final Logger logger = LoggerFactory.getLogger(RefreshDeviceTask.class); private final Supplier> client; @@ -129,7 +131,7 @@ private void processAnswers(List values, List channels) { for (int index = 0; index < values.size(); index++) { Object value = values.get(index); if (value instanceof Encodable) { - State state = fromBacNet((Encodable) value); + State state = CompositeConverter.INSTANCE.fromBacNet((Encodable) value); ChannelUID channel = channels.get(index); logger.debug("Retrieved state for property {} attribute {}: {}", device, channel, state); diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetObjectsSampler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetObjectsSampler.java new file mode 100644 index 00000000..51582a22 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetObjectsSampler.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0.txt + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package org.connectorio.addons.binding.bacnet.internal.handler.source; + +import com.serotonin.bacnet4j.type.Encodable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.code_house.bacnet4j.wrapper.api.BacNetClient; +import org.code_house.bacnet4j.wrapper.api.BacNetObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BACnetObjectsSampler implements BACnetPropertySampler { + private final Logger logger = LoggerFactory.getLogger(BACnetObjectsSampler.class); + + private final BacNetClient client; + private final Map> samples; + private final Consumer> callback; + + public BACnetObjectsSampler(BacNetClient client, BacNetObject object, String property, Consumer callback) { + this(client, Map.of(object, List.of(property)), new SingleValueCallback(callback, new Sample(object, property))); + } + + public BACnetObjectsSampler(BacNetClient client, Map> samples, Consumer> callback) { + this.client = client; + this.samples = samples; + this.callback = callback; + } + + @Override + public Map> getSamples() { + return samples; + } + + @Override + public Consumer> getCallback() { + return callback; + } + + @Override + public CompletableFuture fetch() { + CompletableFuture> future = null; + for (Entry> object : samples.entrySet()) { + if (future == null) { + future = read(object.getKey(), object.getValue()); + } else { + future = future.thenCombine(read(object.getKey(), object.getValue()), (known, retrieved) -> { + known.putAll(retrieved); + return known; + }); + } + } + + if (future == null) { + return CompletableFuture.failedFuture(new NullPointerException("Sampler brought no results")); + } + + return future.whenComplete((result, error) -> { + if (error != null) { + logger.warn("Could not retrieve"); + return; + } + callback.accept(result); + }); + } + + private CompletableFuture> read(BacNetObject object, List properties) { + Map values = new HashMap<>(); + List answers = client.getObjectAttributeValues(object, properties); + for (int index = 0; index < answers.size(); index++) { + Object value = answers.get(index); + String property = properties.get(index); + if (value instanceof Encodable) { + values.put(new Sample(object, property), (Encodable) value); + } + } + return CompletableFuture.completedFuture(values); + } + + static class SingleValueCallback implements Consumer> { + + private final Consumer callback; + private final Sample sample; + + SingleValueCallback(Consumer callback, Sample sample) { + this.callback = callback; + this.sample = sample; + } + + @Override + public void accept(Map result) { + callback.accept(result.get(sample)); + } + } +} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPresentValueSampler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPresentValueSampler.java new file mode 100644 index 00000000..5b113777 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPresentValueSampler.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0.txt + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package org.connectorio.addons.binding.bacnet.internal.handler.source; + +import com.serotonin.bacnet4j.type.Encodable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.code_house.bacnet4j.wrapper.api.BacNetClient; +import org.code_house.bacnet4j.wrapper.api.BacNetObject; +import org.connectorio.addons.binding.bacnet.internal.handler.object.task.Names; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BACnetPresentValueSampler implements BACnetPropertySampler { + private final Logger logger = LoggerFactory.getLogger(BACnetPresentValueSampler.class); + + private final BacNetClient client; + private final Map> samples; + private final Consumer> callback; + + public BACnetPresentValueSampler(BacNetClient client, Map>> samples) { + this.client = client; + this.samples = samples.keySet().stream() + .collect(Collectors.toMap(Function.identity(), key -> List.of(Names.PRESENT_VALUE), (l, r) -> l, LinkedHashMap::new)); + this.callback = new PresentValueCallback(new ArrayList<>(samples.keySet()), new ArrayList<>(samples.values())); + } + + @Override + public Map> getSamples() { + return samples; + } + + @Override + public Consumer> getCallback() { + return null; //callback; + } + + @Override + public CompletableFuture fetch() { + return CompletableFuture.completedFuture(client.getPresentValues(new ArrayList<>(samples.keySet()))) + .whenComplete((result, error) -> { + if (error != null) { + logger.debug("Error while fetching present values of {}", samples.keySet(), error); + return; + } + callback.accept(result); + }); + } + + static class PresentValueCallback implements Consumer> { + + private final List objects; + private final List>> callbacks; + + public PresentValueCallback(List objects, List>> callbacks) { + this.objects = objects; + this.callbacks = callbacks; + } + + @Override + public void accept(List values) { + for (int index = 0; index < values.size(); index++) { + Object value = values.get(index); + if (value instanceof Encodable) { + callbacks.get(index).accept(Map.of(new Sample(objects.get(index), Names.PRESENT_VALUE), (Encodable) value)); + } + } + } + } +} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/ScheduleHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPropertySampler.java similarity index 54% rename from bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/ScheduleHandler.java rename to bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPropertySampler.java index c5796dc6..900148c7 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/ScheduleHandler.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetPropertySampler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,23 +19,19 @@ * * SPDX-License-Identifier: GPL-3.0-or-later */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; +package org.connectorio.addons.binding.bacnet.internal.handler.source; -import com.serotonin.bacnet4j.obj.ScheduleObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; +import com.serotonin.bacnet4j.type.Encodable; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.code_house.bacnet4j.wrapper.api.BacNetObject; +import org.connectorio.addons.binding.source.sampling.Sampler; -public class ScheduleHandler extends - BACnetObjectThingHandler, ObjectConfig> { +public interface BACnetPropertySampler extends Sampler { - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public ScheduleHandler(Thing thing) { - super(thing, Type.SCHEDULE); - } + Map> getSamples(); + + Consumer> getCallback(); } diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetSamplerComposer.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetSamplerComposer.java new file mode 100644 index 00000000..af3ad318 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/BACnetSamplerComposer.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0.txt + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package org.connectorio.addons.binding.bacnet.internal.handler.source; + +import com.serotonin.bacnet4j.type.Encodable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.code_house.bacnet4j.wrapper.api.BacNetClient; +import org.code_house.bacnet4j.wrapper.api.BacNetObject; +import org.connectorio.addons.binding.bacnet.internal.handler.object.task.Names; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; + +public class BACnetSamplerComposer implements SamplerComposer { + + private final BacNetClient client; + + public BACnetSamplerComposer(BacNetClient client) { + this.client = client; + } + + @Override + public List merge(List samplers) { + if (samplers.size() == 1) { + return samplers; + } + + List>> consumers = new ArrayList<>(); + // we start with set as it does not permit duplicates, so any property which would be read twice + // will be read once + Map>> presentValues = new LinkedHashMap<>(); + Map> mergedSamples = new HashMap<>(); + + for (BACnetPropertySampler sampler : samplers) { + Map> samples = sampler.getSamples(); + for (Entry> entry : samples.entrySet()) { + for (String property : entry.getValue()) { + if (Names.PRESENT_VALUE.equals(property)) { + presentValues.put(entry.getKey(), sampler.getCallback()); + } else { + if (!mergedSamples.containsKey(entry.getKey())) { + consumers.add(sampler.getCallback()); + mergedSamples.put(entry.getKey(), new HashSet<>()); + } + mergedSamples.get(entry.getKey()).addAll(entry.getValue()); + } + } + } + } + + if (presentValues.isEmpty() && !mergedSamples.isEmpty()) { + return List.of(new BACnetObjectsSampler(client, flattenSamples(mergedSamples), new CompositeCallback(consumers))); + } else if (!presentValues.isEmpty() && mergedSamples.isEmpty()) { + return List.of(new BACnetPresentValueSampler(client, presentValues)); + } + return Arrays.asList( + new BACnetPresentValueSampler(client, presentValues), + new BACnetObjectsSampler(client, flattenSamples(mergedSamples), new CompositeCallback(consumers)) + ); + } + + private static Map> flattenSamples(Map> samples) { + // align samples input to rely on list of properties + Map> targetSamples = samples.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), new ArrayList<>(entry.getValue()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + return targetSamples; + } + + static class CompositeCallback implements Consumer> { + + private final List>> consumers; + + CompositeCallback(List>> consumers) { + this.consumers = consumers; + } + + @Override + public void accept(Map values) { + for (Consumer> consumer : consumers) { + consumer.accept(values); + } + } + + } + +} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryInputHandler.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/ChannelCallback.java similarity index 51% rename from bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryInputHandler.java rename to bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/ChannelCallback.java index 1aa31ff5..ea7c7112 100644 --- a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/object/BinaryInputHandler.java +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/ChannelCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 ConnectorIO sp. z o.o. + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,24 +19,31 @@ * * SPDX-License-Identifier: GPL-3.0-or-later */ -package org.connectorio.addons.binding.bacnet.internal.handler.object; +package org.connectorio.addons.binding.bacnet.internal.handler.source; -import com.serotonin.bacnet4j.obj.AnalogInputObject; -import org.code_house.bacnet4j.wrapper.api.Type; -import org.connectorio.addons.binding.bacnet.internal.config.ObjectConfig; -import org.openhab.core.thing.Thing; +import java.util.function.Consumer; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; -public class BinaryInputHandler extends - BACnetObjectThingHandler, ObjectConfig> { +public class ChannelCallback implements Consumer { - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public BinaryInputHandler(Thing thing) { - super(thing, Type.BINARY_INPUT); + private final ThingHandlerCallback callback; + private final Channel channel; + + public ChannelCallback(ThingHandlerCallback callback, Channel channel) { + this.callback = callback; + this.channel = channel; + } + + @Override + public void accept(State state) { + callback.stateUpdated(channel.getUID(), state); } + @Override + public String toString() { + return "ChannelCallback [" + channel.getUID() + "]"; + } -} +} \ No newline at end of file diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/Sample.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/Sample.java new file mode 100644 index 00000000..5d6f7bab --- /dev/null +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/Sample.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0.txt + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package org.connectorio.addons.binding.bacnet.internal.handler.source; + +import java.util.Objects; +import org.code_house.bacnet4j.wrapper.api.BacNetObject; + +public class Sample { + + private final BacNetObject object; + private final String property; + + public Sample(BacNetObject object, String property) { + this.object = object; + this.property = property; + } + + public BacNetObject getObject() { + return this.object; + } + + public String getProperty() { + return this.property; + } + + @Override + public boolean equals(Object object) { + + if (this == object) { + return true; + } + if (!(object instanceof Sample)) { + return false; + } + Sample sample = (Sample) object; + return Objects.equals(getObject(), sample.getObject()) + && Objects.equals(property, sample.property); + } + + @Override + public int hashCode() { + return Objects.hash(getObject(), property); + } +} diff --git a/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/SamplerCallback.java b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/SamplerCallback.java new file mode 100644 index 00000000..ecd52985 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.bacnet/src/main/java/org/connectorio/addons/binding/bacnet/internal/handler/source/SamplerCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO sp. z o.o. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0.txt + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package org.connectorio.addons.binding.bacnet.internal.handler.source; + +import com.serotonin.bacnet4j.type.Encodable; +import java.util.function.Consumer; +import org.code_house.bacnet4j.wrapper.api.BacNetToJavaConverter; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SamplerCallback implements Consumer { + + private final Logger logger = LoggerFactory.getLogger(SamplerCallback.class); + + private final BacNetToJavaConverter converter; + private final Consumer callback; + + public SamplerCallback(BacNetToJavaConverter converter, Consumer callback) { + this.converter = converter; + this.callback = callback; + } + + @Override + public void accept(Encodable value) { + if (value != null) { + State state = converter.fromBacNet(value); + if (state != null) { + callback.accept(state); + return; + } + logger.debug("Could not map value {} to valid valid state, ignoring callback {}", value, callback); + } + } + +} \ No newline at end of file diff --git a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/TACANopenBindingConstants.java b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/TACANopenBindingConstants.java index a6b54a77..28ed0bc6 100644 --- a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/TACANopenBindingConstants.java +++ b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/TACANopenBindingConstants.java @@ -65,9 +65,6 @@ public interface TACANopenBindingConstants extends CANopenBindingConstants { String TA_DIGITAL_SWITCH = TA_DIGITAL_PREFIX + "-switch"; String TA_DIGITAL_CONTACT = TA_DIGITAL_PREFIX + "-contact"; - @Deprecated - ThingTypeUID TA_UVR_16x2_THING_TYPE = new ThingTypeUID(BINDING_ID, TA_UVR_16x2); - ThingTypeUID TA_DEVICE_THING_TYPE = new ThingTypeUID(BINDING_ID, TA_DEVICE); ChannelTypeUID TA_ANALOG_RAS_MODE_CHANNEL_TYPE = new ChannelTypeUID(BINDING_ID, TA_ANALOG_RAS_MODE); @@ -97,7 +94,7 @@ public interface TACANopenBindingConstants extends CANopenBindingConstants { ThingTypeUID TA_FUNCTION_THING_TYPE = new ThingTypeUID(BINDING_ID, TA_FUNCTION); - Set SUPPORTED_DEVICES = new HashSet<>(Arrays.asList(TA_UVR_16x2_THING_TYPE, TA_DEVICE_THING_TYPE)); + Set SUPPORTED_DEVICES = new HashSet<>(Arrays.asList(TA_DEVICE_THING_TYPE)); Set DISCOVERABLE_CAN_THINGS = new HashSet<>(Arrays.asList(TA_FUNCTION_THING_TYPE // TA_ANALOG_RAS_THING_TYPE, TA_ANALOG_TEMPERATURE_THING_TYPE, TA_ANALOG_LENGTH_THING_TYPE, diff --git a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TADeviceThingHandler.java b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TADeviceThingHandler.java index 92fffc93..1aec2d43 100644 --- a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TADeviceThingHandler.java +++ b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TADeviceThingHandler.java @@ -30,6 +30,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.canopen.tag.CANOpenTag; import org.connectorio.addons.binding.canopen.api.CoConnection; import org.connectorio.addons.binding.canopen.api.CoNode; import org.connectorio.addons.binding.canopen.handler.CoBridgeHandler; @@ -61,8 +62,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class TADeviceThingHandler extends PollingPlc4xBridgeHandler - implements Plc4xBridgeHandler, Consumer, Runnable { +public class TADeviceThingHandler extends PollingPlc4xBridgeHandler + implements Plc4xBridgeHandler, Consumer, Runnable { // no safe caller since initialization might wait much longer than default 5000 ms private final static ExecutorService initializer = Executors.newSingleThreadExecutor(new NamedThreadFactory("initializer")); diff --git a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAThingHandlerFactory.java b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAThingHandlerFactory.java index 0c177980..002b9efd 100644 --- a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAThingHandlerFactory.java +++ b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAThingHandlerFactory.java @@ -45,10 +45,6 @@ protected ThingHandler createHandler(Thing thing) { } } - if (TA_UVR_16x2_THING_TYPE.equals(thing.getThingTypeUID())) { - return new TAUVR16x2ThingHandler(thing, semaphore); - } - return null; } } diff --git a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAUVR16x2ThingHandler.java b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAUVR16x2ThingHandler.java deleted file mode 100644 index 9dd46749..00000000 --- a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/java/org/connectorio/addons/binding/canopen/ta/internal/handler/TAUVR16x2ThingHandler.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2019-2021 ConnectorIO Sp. z o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.connectorio.addons.binding.canopen.ta.internal.handler; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Timer; -import java.util.concurrent.Semaphore; -import java.util.function.Consumer; -import javax.measure.Quantity; -import org.apache.plc4x.java.api.PlcConnection; -import org.connectorio.addons.binding.canopen.handler.CoBridgeHandler; -import org.connectorio.addons.binding.canopen.ta.internal.config.ComplexUnit; -import org.connectorio.addons.binding.canopen.ta.internal.config.ControllerConfig; -import org.connectorio.addons.binding.canopen.ta.internal.config.DigitalUnit; -import org.connectorio.addons.binding.canopen.ta.internal.handler.protocol.TAOperations; -import org.connectorio.addons.binding.canopen.ta.internal.provider.ChannelTypeEntry; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAObject; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAOutput; -import org.connectorio.addons.binding.canopen.config.CoNodeConfig; -import org.connectorio.addons.binding.canopen.ta.internal.config.AnalogUnit; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAADigitalOutput; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAAnalogOutput; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAUnit; -import org.connectorio.addons.binding.canopen.ta.internal.type.TAValue; -import org.connectorio.addons.binding.handler.polling.PollingThingHandler; -import org.connectorio.addons.binding.plc4x.handler.base.PollingPlc4xThingHandler; -import org.connectorio.plc4x.extras.decorator.phase.Phase; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelKind; -import org.openhab.core.types.Command; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import tec.uom.se.AbstractUnit; - -@Deprecated -public class TAUVR16x2ThingHandler extends PollingPlc4xThingHandler, CoNodeConfig> - implements PollingThingHandler, CoNodeConfig>, Consumer { - - private final Logger logger = LoggerFactory.getLogger(TAUVR16x2ThingHandler.class); - private int nodeId; - private int clientId; - private TAOperations operations; - private final Semaphore semaphore; - private final Timer logoutTimer; - - public TAUVR16x2ThingHandler(Thing thing, Semaphore semaphore) { - super(thing); - this.logoutTimer = new Timer("canopen-ta-logout-" + thing.getLabel(), true); - this.semaphore = semaphore; - } - - @Override - public void initialize() { - ControllerConfig config = getConfigAs(ControllerConfig.class); - nodeId = config.nodeId; - clientId = getBridgeHandler().map(CoBridgeHandler::getNodeId).orElse(-1); - - updateStatus(ThingStatus.OFFLINE); - - getBridgeConnection().ifPresent(connection -> { - // block initialisation of other handlers until we complete reading all SDOs - semaphore.acquireUninterruptibly(); - - logger.debug("Retrieving UVR {} configuration", nodeId); - operations = new TAOperations(connection); - - ValueListener valueListener = new ThingChannelValueListener(getCallback(), getThing(), this::createState); - - Phase phase = new Phase("Initialize " + thing.getUID() + " " + thing.getLabel()); - phase.onCompletion(new Runnable() { - @Override - public void run() { - operations.logout(nodeId, clientId); - } - }); - - operations.subscribeStatus(connected -> { - if (connected) { - operations.reload(nodeId); - // lets release SDO lock within 90 seconds, should be sufficient to complete all SDO detection in most of cases - updateStatus(ThingStatus.ONLINE); - return; - } - if (config.ignoreLoginFailure) { - operations.reload(nodeId); - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE); - } - }, nodeId, clientId); - operations.subscribeInputOutputState(valueListener, nodeId); - operations.subscribeInputOutputConfig(this, nodeId); - operations.login(nodeId, clientId).whenComplete((response, error) -> { - if (error != null) { - logger.error("Could not complete initialization, device login failed", error); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.getMessage()); - } - }); - }); - } - - @Override - public void dispose() { - if (operations != null) { - operations.logout(nodeId, clientId); - operations.close(); - } - super.dispose(); - } - - @Override - protected void updateStatus(ThingStatus status) { - super.updateStatus(status); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - - } - - @Override - protected Long getDefaultPollingInterval() { - return 1000L; - } - - @Override - public void accept(TAObject object) { - logger.debug("Discovered new input/output {} for node {}", object, nodeId); - if (object instanceof TAAnalogOutput) { - updateThingChannel((TAOutput) object, "analog#","Analog #"); - } else if (object instanceof TAADigitalOutput) { - updateThingChannel((TAOutput) object, "digital#", "Digital #"); - } else { - logger.info("Found unsupported object {}", object); - } - } - - private void updateThingChannel(TAOutput output, String channelId, String fallbackLabel) { - ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId + output.getIndex()); - - ChannelTypeEntry channelType = ChannelTypeHelper.channelType(output); - logger.debug("Selected channel {} type {} for {} on node {}", channelUID, channelType, output, nodeId); - String label = output.getLabel().orElse(fallbackLabel + output.getIndex()); - - ThingBuilder thingBuilder = editThing(); - if (getThing().getChannel(channelUID) != null) { - thingBuilder.withoutChannel(channelUID); - } - - Map properties = new HashMap<>(); - properties.put("index", output.getIndex()); - if (output instanceof TAAnalogOutput) { - TAUnit unit = AnalogUnit.valueOf(output.getUnit()); - if (unit != null) { - properties.put("unit", unit.name()); - } else { - ComplexUnit complexUnit = ComplexUnit.valueOf(output.getUnit()); - if (complexUnit != null) { - properties.put("unit", complexUnit.name()); - } else { - logger.warn("Received output with unsupported analog unit {} ({}), falling back to dimensionless", output.getUnit(), Integer.toHexString(output.getUnit())); - properties.put("unit", AnalogUnit.DIMENSIONLESS.name()); - } - } - } else if (output instanceof TAADigitalOutput) { - DigitalUnit unit = DigitalUnit.valueOf(output.getUnit()); - if (unit != null) { - properties.put("unit", unit.name()); - } else { - logger.warn("Received output with unsupported digital unit {} ({}), falling back to basic ON/OFF unit", output.getUnit(), Integer.toHexString(output.getUnit())); - properties.put("unit", DigitalUnit.OFF_ON.name()); - } - } - Configuration configuration = new Configuration(properties); - - ChannelBuilder channelBuilder = ChannelBuilder.create(channelUID) - .withKind(ChannelKind.STATE) - .withAcceptedItemType(channelType.getItemType()) - .withLabel(label) - .withType(channelType.getChannelType()) - .withConfiguration(configuration); - thingBuilder.withChannel(channelBuilder.build()); - - try { - updateThing(thingBuilder.build()); - - Optional.ofNullable(getCallback()) - .ifPresent(callback -> output.getState().ifPresent(value -> callback.stateUpdated(channelUID, createState(value)))); - } catch (Exception e) { - logger.error("Failed to configure channel {} for node {}", channelUID, nodeId, e); - } - } - - private State createState(TAValue taValue) { - Object value = taValue.getValue(); - if (value instanceof State) { - // digital - return (State) value; - } - - // analog - if (value instanceof Quantity) { - Quantity quantity = (Quantity) value; - return new QuantityType<>(quantity.getValue(), quantity.getUnit()); - } - - if (value instanceof Number) { - return QuantityType.valueOf(((Number) value).doubleValue(), AbstractUnit.ONE); - } - - if (value instanceof List) { - List list = (List) value; - if (list.size() > 0 && list.get(0) instanceof Quantity) { - Quantity quantity = (Quantity) list.get(0); - return new QuantityType<>(quantity.getValue(), quantity.getUnit()); - } - } - - // unknown - return UnDefType.UNDEF; - } - -} diff --git a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/resources/OH-INF/thing/thing-types.xml index b2027196..9433d2f4 100644 --- a/bundles/org.connectorio.addons.binding.canopen.ta/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.connectorio.addons.binding.canopen.ta/src/main/resources/OH-INF/thing/thing-types.xml @@ -88,29 +88,4 @@ - - - - - - - Freely programmable logic controller. - - - - - - Identifier of device in CANopen network. - - - - - - Determine if channel configuration should be refreshed even if controller refused "login" registration of this node. - - false - - - - diff --git a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/handler/CoBridgeHandler.java b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/handler/CoBridgeHandler.java index db435d4c..428f88c1 100644 --- a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/handler/CoBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/handler/CoBridgeHandler.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -import org.apache.plc4x.java.api.PlcConnection; import org.connectorio.addons.binding.canopen.api.CoConnection; import org.connectorio.addons.binding.canopen.config.CoNodeConfig; import org.connectorio.addons.binding.canopen.discovery.CoDiscoveryParticipant; @@ -27,7 +26,7 @@ import org.connectorio.addons.binding.plc4x.handler.Plc4xBridgeHandler; import org.connectorio.plc4x.extras.decorator.Decorator; -public interface CoBridgeHandler extends Plc4xBridgeHandler, PollingBridgeHandler { +public interface CoBridgeHandler extends Plc4xBridgeHandler, PollingBridgeHandler { String getName(); diff --git a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoNodeBridgeHandler.java b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoNodeBridgeHandler.java index 9e3b7e7d..5ab936b1 100644 --- a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoNodeBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoNodeBridgeHandler.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.canopen.readwrite.CANOpenDataType; +import org.apache.plc4x.java.canopen.tag.CANOpenTag; import org.connectorio.addons.binding.canopen.CANopenBindingConstants; import org.connectorio.addons.binding.canopen.api.CoConnection; import org.connectorio.addons.binding.canopen.api.CoNode; @@ -48,7 +49,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CoNodeBridgeHandler extends PollingPlc4xBridgeHandler { +public class CoNodeBridgeHandler extends PollingPlc4xBridgeHandler { private final Logger logger = LoggerFactory.getLogger(CoNodeBridgeHandler.class); diff --git a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoSocketCANBridgeHandler.java b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoSocketCANBridgeHandler.java index 3c61d849..74a70e69 100644 --- a/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoSocketCANBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.canopen/src/main/java/org/connectorio/addons/binding/canopen/internal/handler/CoSocketCANBridgeHandler.java @@ -27,6 +27,7 @@ import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; +import org.apache.plc4x.java.canopen.tag.CANOpenTag; import org.apache.plc4x.java.spi.connection.AbstractPlcConnection; import org.connectorio.addons.binding.can.statistic.CANStatisticCollector; import org.connectorio.addons.binding.canopen.api.CoConnection; @@ -55,7 +56,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CoSocketCANBridgeHandler extends PollingPlc4xBridgeHandler +public class CoSocketCANBridgeHandler extends PollingPlc4xBridgeHandler implements CoBridgeHandler { private final Logger logger = LoggerFactory.getLogger(CoSocketCANBridgeHandler.class); diff --git a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/FetchContainer.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/ChannelConfig.java similarity index 65% rename from bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/FetchContainer.java rename to bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/ChannelConfig.java index 5d28d337..22ed97fc 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/FetchContainer.java +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/ChannelConfig.java @@ -15,18 +15,13 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.connectorio.addons.binding.amsads.internal.handler.polling; +package org.connectorio.addons.binding.mbus.config; -import java.util.function.Consumer; -import org.apache.plc4x.java.ads.tag.AdsTag; +import org.connectorio.addons.binding.config.Configuration; -public interface FetchContainer { +public class ChannelConfig implements Configuration { - void add(Long interval, String channelId, AdsTag tag, Consumer onChange); - - // indicate if thing was actually started - boolean start(); - - void stop(); + public String dib; + public String vib; } diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/DeviceConfig.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/DeviceConfig.java index 5b815c46..27dab526 100644 --- a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/DeviceConfig.java +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/config/DeviceConfig.java @@ -2,18 +2,44 @@ import org.connectorio.addons.binding.config.PollingConfiguration; import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; public class DeviceConfig extends PollingConfiguration { // primary address public Integer address; - public int serialNumber; + public Integer serialNumber; public String manufacturerId; - public int version; + public Integer version; public DeviceType deviceType; public boolean discoverChannels = true; + public SecondaryAddress getSecondaryAddress() { + if (serialNumber == null || manufacturerId == null || version == null || deviceType == null) { + return null; + } + + // create m-bus address using long variant + return SecondaryAddress.newFromManufactureId( + bcd(serialNumber), + manufacturerId, + version.byteValue(), + Integer.valueOf(deviceType.getId()).byteValue(), + true + ); + } + + private static byte[] bcd(int value){ + byte[] bcd = new byte[4]; + for (int index = 0; index < 4; index++){ + bcd[index] = (byte) (value % 10); + value /= 10; + bcd[index] |= (byte) ((value % 10) << 4); + value /= 10; + } + return bcd; + } } diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusDeviceThingHandler.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusDeviceThingHandler.java index ba04ef17..04ad44f9 100644 --- a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusDeviceThingHandler.java +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusDeviceThingHandler.java @@ -17,28 +17,127 @@ */ package org.connectorio.addons.binding.mbus.internal.handler; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import org.connectorio.addons.binding.handler.polling.common.BasePollingThingHandler; import org.connectorio.addons.binding.mbus.config.BridgeConfig; +import org.connectorio.addons.binding.mbus.config.ChannelConfig; import org.connectorio.addons.binding.mbus.config.DeviceConfig; +import org.connectorio.addons.binding.mbus.internal.handler.converter.Converter; +import org.connectorio.addons.binding.mbus.internal.handler.source.ChannelKey; +import org.connectorio.addons.binding.mbus.internal.handler.source.MBusChannelCallback; +import org.connectorio.addons.binding.mbus.internal.handler.source.MBusPrimaryAddressSampler; +import org.connectorio.addons.binding.mbus.internal.handler.source.MBusRecordCallback; +import org.connectorio.addons.binding.mbus.internal.handler.source.MBusSampler; +import org.connectorio.addons.binding.mbus.internal.handler.source.MBusSecondaryAddressSampler; +import org.connectorio.addons.binding.source.SourceFactory; +import org.connectorio.addons.binding.source.sampling.SamplingSource; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.binding.BridgeHandler; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.MBusConnection; +import org.openmuc.jmbus.SecondaryAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MBusDeviceThingHandler extends BasePollingThingHandler, DeviceConfig> { + private final Logger logger = LoggerFactory.getLogger(MBusDeviceThingHandler.class); - public MBusDeviceThingHandler(Thing thing) { + private final Map channelMapping = new ConcurrentHashMap<>(); + private final Converter converter; + private final SourceFactory sourceFactory; + private MBusConnection connection; + private SamplingSource source; + private Integer address; + private SecondaryAddress secondaryAddress; + + public MBusDeviceThingHandler(Thing thing, Converter converter, SourceFactory sourceFactory) { super(thing); + this.converter = converter; + this.sourceFactory = sourceFactory; } @Override public void initialize() { + CompletableFuture bridgeConnection = getBridgeHandler() + .map(MBusBridgeHandler::getConnection) + .orElse(null); + + if (bridgeConnection == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Could not find bridge"); + return; + } + + DeviceConfig config = getConfigAs(DeviceConfig.class); + if (config.address == null && config.getSecondaryAddress() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing device address and/or secondary address components"); + return; + } + + bridgeConnection.whenComplete((result, error) -> { + if (error != null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge supplied connection is not available"); + return; + } + this.connection = result; + this.address = config.address; + this.secondaryAddress = config.getSecondaryAddress(); + this.source = sourceFactory.sampling(scheduler); + Map> callbacks = new HashMap<>(); + for (Channel channel : getThing().getChannels()) { + ChannelConfig channelConfig = channel.getConfiguration().as(ChannelConfig.class); + ChannelKey channelKey = new ChannelKey(channelConfig.dib, channelConfig.vib); + callbacks.put(channelKey, new MBusRecordCallback(converter, new MBusChannelCallback(getCallback(), channel))); + if (channelMapping.containsValue(channelKey)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Detected duplicate mapping " + channelKey + ", channel key must be unique."); + return; + } + channelMapping.put(channel.getUID(), channelKey); + } + + if (secondaryAddress != null) { + source.add(config.refreshInterval, thing.getUID().getAsString(), new MBusSecondaryAddressSampler(connection, secondaryAddress, callbacks)); + } else { + source.add(config.refreshInterval, thing.getUID().getAsString(), new MBusPrimaryAddressSampler(connection, address, callbacks)); + } + + source.start(); + + updateStatus(ThingStatus.ONLINE); + }); } @Override public void handleCommand(ChannelUID channelUID, Command command) { - + if (channelMapping.containsKey(channelUID)) { + if (RefreshType.REFRESH.equals(command) && this.source != null) { + ChannelKey channelKey = channelMapping.get(channelUID); + // read channel status independently of scheduled tasks + Channel channel = thing.getChannel(channelUID); + if (address != null) { + source.request(new MBusPrimaryAddressSampler(connection, address, Map.of( + channelKey, new MBusRecordCallback(converter, new MBusChannelCallback(getCallback(), channel)) + ))); + } else if (secondaryAddress != null) { + source.request(new MBusSecondaryAddressSampler(connection, secondaryAddress, Map.of( + channelKey, new MBusRecordCallback(converter, new MBusChannelCallback(getCallback(), channel)) + ))); + } else { + logger.warn("Could not determine target M-Bus device address, ignoring REFRESH call"); + } + return; + } + } + logger.warn("Unsupported command {} for channel {}, M-Bus binding is read only integration", command, channelUID); } @Override diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusThingHandlerFactory.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusThingHandlerFactory.java index d2192561..63f684fe 100644 --- a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusThingHandlerFactory.java +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/MBusThingHandlerFactory.java @@ -20,6 +20,8 @@ import org.connectorio.addons.binding.handler.factory.BaseThingHandlerFactory; import org.connectorio.addons.binding.mbus.MBusBindingConstants; import org.connectorio.addons.binding.mbus.internal.discovery.DiscoveryCoordinator; +import org.connectorio.addons.binding.mbus.internal.handler.converter.Converter; +import org.connectorio.addons.binding.source.SourceFactory; import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -33,12 +35,17 @@ public class MBusThingHandlerFactory extends BaseThingHandlerFactory { private final SerialPortManager serialPortManager; + private final SourceFactory sourceFactory; + private final Converter converter; private final DiscoveryCoordinator discoveryCoordinator; @Activate - public MBusThingHandlerFactory(@Reference SerialPortManager serialPortManager, @Reference DiscoveryCoordinator discoveryCoordinator) { + public MBusThingHandlerFactory(@Reference SerialPortManager serialPortManager, @Reference(target = "(default=true)") SourceFactory sourceFactory, + @Reference Converter converter, @Reference DiscoveryCoordinator discoveryCoordinator) { super(MBusBindingConstants.SUPPORTED_THING_TYPES); this.serialPortManager = serialPortManager; + this.sourceFactory = sourceFactory; + this.converter = converter; this.discoveryCoordinator = discoveryCoordinator; } @@ -54,7 +61,7 @@ protected ThingHandler createHandler(Thing thing) { } if (MBusBindingConstants.DEVICE_THING_TYPE.equals(thing.getThingTypeUID())) { - return new MBusDeviceThingHandler(thing); + return new MBusDeviceThingHandler(thing, converter, sourceFactory); } return null; diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/Converter.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/Converter.java new file mode 100644 index 00000000..4fb6031a --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/Converter.java @@ -0,0 +1,10 @@ +package org.connectorio.addons.binding.mbus.internal.handler.converter; + +import org.openhab.core.types.State; +import org.openmuc.jmbus.DataRecord; + +public interface Converter { + + State toState(DataRecord record); + +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/DataRecordConverter.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/DataRecordConverter.java new file mode 100644 index 00000000..338dd0df --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/converter/DataRecordConverter.java @@ -0,0 +1,51 @@ +package org.connectorio.addons.binding.mbus.internal.handler.converter; + +import java.time.ZonedDateTime; +import java.util.Date; +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.StringType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.openmuc.jmbus.DataRecord; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component(service = Converter.class) +public class DataRecordConverter implements Converter { + + private final TimeZoneProvider timeZoneProvider; + + @Activate + public DataRecordConverter(@Reference TimeZoneProvider timeZoneProvider) { + this.timeZoneProvider = timeZoneProvider; + } + + @Override + public State toState(DataRecord record) { + switch (record.getDataValueType()) { + case LONG: + case DOUBLE: + case BCD: + return new DecimalType(record.getScaledDataValue()); + case DATE: + return convertDate(record.getDataValue()); + case STRING: + case NONE: + return new StringType(record.getDataValue().toString()); + } + return null; + } + + private State convertDate(Object input) { + if (input instanceof Date) { + Date date = (Date) input; + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), timeZoneProvider.getTimeZone()); + return new DateTimeType(zonedDateTime); + } + + return UnDefType.NULL; + } +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/ChannelKey.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/ChannelKey.java new file mode 100644 index 00000000..f5929052 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/ChannelKey.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.util.Arrays; +import org.openhab.core.util.HexUtils; + +public class ChannelKey { + + private final byte[] dib; + private final byte[] vib; + + public ChannelKey(String dib, String vib) { + this(HexUtils.hexToBytes(dib), HexUtils.hexToBytes(vib)); + } + + public ChannelKey(byte[] dib, byte[] vib) { + this.dib = dib; + this.vib = vib; + } + + public String asString() { + return HexUtils.bytesToHex(dib) + "_" + HexUtils.bytesToHex(vib); + } + + @Override + public String toString() { + return "ChannelKey[DIB: " + HexUtils.bytesToHex(dib) + ", VIB:" + HexUtils.bytesToHex(vib) + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ChannelKey)) { + return false; + } + ChannelKey that = (ChannelKey) o; + return Arrays.equals(dib, that.dib) && Arrays.equals(vib, that.vib); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(dib); + result = 31 * result + Arrays.hashCode(vib); + return result; + } + +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusBaseSampler.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusBaseSampler.java new file mode 100644 index 00000000..21b69236 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusBaseSampler.java @@ -0,0 +1,59 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.openhab.core.types.State; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.MBusConnection; +import org.openmuc.jmbus.VariableDataStructure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class MBusBaseSampler implements MBusSampler { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + protected final MBusConnection connection; + protected final int primaryAddress; + private final Map> callbacks; + + protected MBusBaseSampler(MBusConnection connection, int primaryAddress, Map> callbacks) { + this.connection = connection; + this.primaryAddress = primaryAddress; + this.callbacks = callbacks; + } + + protected final CompletableFuture> read() { + try { + List records = new ArrayList<>(); + VariableDataStructure vdr; + do { + vdr = connection.read(primaryAddress); + records.addAll(vdr.getDataRecords()); + } while(vdr.moreRecordsFollow()); + return CompletableFuture.completedFuture(records).whenComplete((result, error) -> { + if (error != null) { + logger.debug("Failure while reading out meter {}", primaryAddress, error); + return; + } + for (DataRecord record : result) { + Consumer consumer = callbacks.get(new ChannelKey(record.getDib(), record.getVib())); + if (consumer != null) { + consumer.accept(record); + } + } + }); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } finally { + try { + connection.resetReadout(primaryAddress); + } catch (IOException e) { + logger.warn("Could not reset readout for device {}", primaryAddress, e); + } + } + } +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusChannelCallback.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusChannelCallback.java new file mode 100644 index 00000000..ab2062c2 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusChannelCallback.java @@ -0,0 +1,23 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.util.function.Consumer; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; + +public class MBusChannelCallback implements Consumer { + + private final ThingHandlerCallback callback; + private final Channel channel; + + public MBusChannelCallback(ThingHandlerCallback callback, Channel channel) { + this.callback = callback; + this.channel = channel; + } + + @Override + public void accept(State state) { + callback.stateUpdated(channel.getUID(), state); + } + +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusPrimaryAddressSampler.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusPrimaryAddressSampler.java new file mode 100644 index 00000000..f6fa137c --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusPrimaryAddressSampler.java @@ -0,0 +1,34 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.MBusConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MBusPrimaryAddressSampler extends MBusBaseSampler implements MBusSampler { + + private final Logger logger = LoggerFactory.getLogger(MBusPrimaryAddressSampler.class); + + public MBusPrimaryAddressSampler(MBusConnection connection, int address, Map> callbacks) { + super(connection, address, callbacks); + } + + @Override + public CompletableFuture> fetch() { + try { + return read(); + } finally { + try { + connection.resetReadout(primaryAddress); + } catch (IOException e) { + logger.warn("Could not reset readout for device {}", primaryAddress, e); + } + } + } + +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusRecordCallback.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusRecordCallback.java new file mode 100644 index 00000000..5931ff5d --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusRecordCallback.java @@ -0,0 +1,23 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.util.function.Consumer; +import org.connectorio.addons.binding.mbus.internal.handler.converter.Converter; +import org.openhab.core.types.State; +import org.openmuc.jmbus.DataRecord; + +public class MBusRecordCallback implements Consumer { + + private final Converter converter; + private final Consumer callback; + + public MBusRecordCallback(Converter converter, Consumer callback) { + this.converter = converter; + this.callback = callback; + } + + @Override + public void accept(DataRecord dataRecord) { + converter.toState(dataRecord); + } + +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSampler.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSampler.java new file mode 100644 index 00000000..1a1b9ede --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSampler.java @@ -0,0 +1,12 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.connectorio.addons.binding.source.sampling.Sampler; +import org.openmuc.jmbus.DataRecord; + +public interface MBusSampler extends Sampler { + + @Override + CompletableFuture> fetch(); +} diff --git a/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSecondaryAddressSampler.java b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSecondaryAddressSampler.java new file mode 100644 index 00000000..c4833b93 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.mbus/src/main/java/org/connectorio/addons/binding/mbus/internal/handler/source/MBusSecondaryAddressSampler.java @@ -0,0 +1,48 @@ +package org.connectorio.addons.binding.mbus.internal.handler.source; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.openhab.core.types.State; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.MBusConnection; +import org.openmuc.jmbus.SecondaryAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MBusSecondaryAddressSampler extends MBusBaseSampler implements MBusSampler { + + public static final int PRIMARY_ADDRESS_FOR_SECONDARY_READOUT = 0xFD; + private final Logger logger = LoggerFactory.getLogger(MBusSecondaryAddressSampler.class); + private final SecondaryAddress secondaryAddress; + + public MBusSecondaryAddressSampler(MBusConnection connection, SecondaryAddress secondaryAddress, Map> callbacks) { + super(connection, PRIMARY_ADDRESS_FOR_SECONDARY_READOUT, callbacks); + this.secondaryAddress = secondaryAddress; + } + + @Override + public CompletableFuture> fetch() { + try { + connection.selectComponent(secondaryAddress); + return read(); + } catch (IOException e) { + logger.warn("Could not select component for readout {}", secondaryAddress, e); + return CompletableFuture.failedFuture(e); + } finally { + try { + connection.resetReadout(PRIMARY_ADDRESS_FOR_SECONDARY_READOUT); + } catch (IOException e) { + logger.warn("Failure while deselecting readout component {}", secondaryAddress, e); + } + try { + connection.deselectComponent(); + } catch (IOException e) { + logger.warn("Failure while deselecting readout component {}", secondaryAddress, e); + } + } + } + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xBridgeHandler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xBridgeHandler.java index 9bd81cc9..a4f69da9 100644 --- a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xBridgeHandler.java @@ -24,8 +24,8 @@ import org.connectorio.addons.binding.handler.polling.PollingBridgeHandler; import org.openhab.core.thing.binding.BridgeHandler; -public interface Plc4xBridgeHandler extends GenericBridgeHandler, PollingBridgeHandler, BridgeHandler { +public interface Plc4xBridgeHandler extends GenericBridgeHandler, PollingBridgeHandler, BridgeHandler { - CompletableFuture getConnection(); + CompletableFuture getConnection(); } diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xThingHandler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xThingHandler.java index e933e141..c5ceb82e 100644 --- a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xThingHandler.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/Plc4xThingHandler.java @@ -17,13 +17,13 @@ */ package org.connectorio.addons.binding.plc4x.handler; -import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.model.PlcTag; import org.connectorio.addons.binding.config.PollingConfiguration; import org.connectorio.addons.binding.handler.GenericThingHandler; import org.connectorio.addons.binding.handler.polling.PollingThingHandler; import org.openhab.core.thing.binding.ThingHandler; -public interface Plc4xThingHandler, C extends PollingConfiguration> +public interface Plc4xThingHandler, C extends PollingConfiguration> extends ThingHandler, GenericThingHandler, PollingThingHandler { } diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xBridgeHandler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xBridgeHandler.java index c3f88b33..c214dd3d 100644 --- a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xBridgeHandler.java @@ -19,6 +19,7 @@ import java.util.concurrent.CompletableFuture; import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.model.PlcTag; import org.connectorio.addons.binding.config.PollingConfiguration; import org.connectorio.addons.binding.handler.polling.common.BasePollingBridgeHandler; import org.connectorio.addons.binding.plc4x.handler.Plc4xBridgeHandler; @@ -26,18 +27,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class PollingPlc4xBridgeHandler extends BasePollingBridgeHandler - implements Plc4xBridgeHandler { +public abstract class PollingPlc4xBridgeHandler extends BasePollingBridgeHandler + implements Plc4xBridgeHandler { private final Logger logger = LoggerFactory.getLogger(getClass()); - private CompletableFuture connection; + private CompletableFuture connection; public PollingPlc4xBridgeHandler(Bridge bridge) { super(bridge); } @Override - public CompletableFuture getConnection() { + public CompletableFuture getConnection() { // make sure we create new connection only if there is none. if (connection == null) { logger.debug("Opening new connection to device"); @@ -46,13 +47,13 @@ public CompletableFuture getConnection() { return connection; } - protected abstract CompletableFuture getPlcConnection(); + protected abstract CompletableFuture getPlcConnection(); @Override public void dispose() { if (connection != null && connection.isDone()) { try { - final T deviceConnection = connection.get(); + final PlcConnection deviceConnection = connection.get(); if (deviceConnection.isConnected()) { deviceConnection.close(); logger.debug("Closed connection to device"); diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xThingHandler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xThingHandler.java index 8c30b703..7685f1fa 100644 --- a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xThingHandler.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/base/PollingPlc4xThingHandler.java @@ -23,18 +23,25 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; +import org.apache.plc4x.java.api.model.PlcTag; import org.connectorio.addons.binding.config.PollingConfiguration; import org.connectorio.addons.binding.handler.polling.common.BasePollingThingHandler; import org.connectorio.addons.binding.plc4x.handler.Plc4xBridgeHandler; import org.connectorio.addons.binding.plc4x.handler.Plc4xThingHandler; import org.connectorio.addons.binding.plc4x.handler.task.WriteTask; import org.connectorio.addons.binding.plc4x.config.CommonChannelConfiguration; -import org.connectorio.addons.binding.plc4x.handler.task.ReadTask; +import org.connectorio.addons.binding.plc4x.sampler.DefaultPlc4xSampler; +import org.connectorio.addons.binding.plc4x.sampler.DefaultPlc4xSamplerComposer; +import org.connectorio.addons.binding.plc4x.source.ChannelCallback; +import org.connectorio.addons.binding.plc4x.source.Converter; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.connectorio.addons.binding.plc4x.source.SamplerCallback; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; +import org.connectorio.addons.binding.source.sampling.SamplingSource; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -46,15 +53,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class PollingPlc4xThingHandler, C extends PollingConfiguration> extends +public abstract class PollingPlc4xThingHandler, C extends PollingConfiguration> extends BasePollingThingHandler implements Plc4xThingHandler { - protected final Map futures = new ConcurrentHashMap<>(); private final Logger logger = LoggerFactory.getLogger(getClass()); - private T connection; + private final Map channelMapping = new ConcurrentHashMap<>(); + private final SourceFactory sourceFactory; + private final Converter converter; + private SamplingSource> source; - public PollingPlc4xThingHandler(Thing thing) { + public PollingPlc4xThingHandler(Thing thing, SourceFactory sourceFactory, Converter converter) { super(thing); + this.sourceFactory = sourceFactory; + this.converter = converter; } @Override @@ -63,13 +74,14 @@ public void initialize() { .map(future -> future.whenCompleteAsync(this::connect, this.scheduler)); } - private void connect(T connection, Throwable e) { + private void connect(PlcConnection connection, Throwable e) { if (e != null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); return; } List configErrors = new ArrayList<>(); + this.source = this.sourceFactory.sampling(scheduler, new DefaultPlc4xSamplerComposer<>(connection)); for (Channel channel : thing.getChannels()) { final ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); if (channelTypeUID == null) { @@ -80,10 +92,15 @@ private void connect(T connection, Throwable e) { CommonChannelConfiguration.class); try { + T tag = createTag(channel); + if (tag == null) { + logger.warn("Could not determine tag for channel {}. This channel will be excluded from polling", channel); + continue; + } + channelMapping.put(channel, tag); Long cycleTime = channelConfig.refreshInterval == null ? getRefreshInterval() : channelConfig.refreshInterval; - ScheduledFuture future = scheduler.scheduleAtFixedRate(new ReadTask(connection, getCallback(), channel), 0, - cycleTime, TimeUnit.MILLISECONDS); - futures.put(channel.getUID(), future); + Consumer callback = new SamplerCallback(converter, new ChannelCallback(getCallback(), channel)); + source.add(cycleTime, channel.getUID().getAsString(), new DefaultPlc4xSampler<>(connection, channel.getUID().getAsString(), tag, callback)); } catch (PlcRuntimeException er) { logger.warn("Channel configuration error", er); configErrors.add(channel.getLabel() + ": " + er.getMessage()); @@ -100,6 +117,7 @@ private void connect(T connection, Throwable e) { return; } + source.start(); updateStatus(ThingStatus.ONLINE); } @@ -107,16 +125,20 @@ private void connect(T connection, Throwable e) { public void handleCommand(ChannelUID channelUID, Command command) { Channel channel = getThing().getChannel(channelUID); - if (RefreshType.REFRESH == command) { - getBridgeConnection().ifPresent(connection -> scheduler.submit(new ReadTask(connection, - getCallback(), channel))); + T tag = channelMapping.get(channelUID); + if (tag == null) { + logger.info("Could not determine tag for channel {}", channel); + } + + if (RefreshType.REFRESH == command && this.source != null) { + Consumer callback = new SamplerCallback(converter, new ChannelCallback(getCallback(), channel)); + getBridgeConnection().ifPresent(connection -> source.request(new DefaultPlc4xSampler<>(connection, channelUID.getAsString(), tag, callback))); } else { - getBridgeConnection() - .ifPresent(connection -> scheduler.submit(new WriteTask(connection, channel, command))); + getBridgeConnection().ifPresent(connection -> scheduler.submit(new WriteTask(connection, channel, command))); } } - protected Optional getBridgeConnection() { + protected Optional getBridgeConnection() { return getBridgeHandler().map(Plc4xBridgeHandler::getConnection).map(CompletableFuture::join); } @@ -127,7 +149,11 @@ public void dispose() { } private void clearTasks() { - futures.forEach((k, v) -> v.cancel(false)); + if (this.source != null) { + this.source.stop(); + } } + protected abstract T createTag(Channel channel); + } diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/task/ReadTask.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/task/ReadTask.java index 8fc4e091..1c72cc80 100644 --- a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/task/ReadTask.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/handler/task/ReadTask.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated public class ReadTask implements Runnable { private final Logger logger = LoggerFactory.getLogger(ReadTask.class); diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/Plc4xSourceFactory.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/Plc4xSourceFactory.java new file mode 100644 index 00000000..24d0dd9c --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/Plc4xSourceFactory.java @@ -0,0 +1,34 @@ +package org.connectorio.addons.binding.plc4x.internal; + +import java.util.concurrent.ScheduledExecutorService; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.plc4x.internal.source.DefaultPlc4xSamplingSource; +import org.connectorio.addons.binding.plc4x.internal.source.DefaultSubscriberSource; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; +import org.connectorio.addons.binding.plc4x.source.SubscriberSource; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.SamplingSource; +import org.osgi.service.component.annotations.Component; + +@Component(property = { + "plc4x=true" +}, service = SourceFactory.class) +public class Plc4xSourceFactory implements SourceFactory { + + @Override + public SubscriberSource subscriber(PlcConnection connection) { + return new DefaultSubscriberSource<>(connection); + } + + @Override + public SamplingSource> sampling(ScheduledExecutorService executor) { + return new DefaultPlc4xSamplingSource<>(executor, null); + } + + @Override + public SamplingSource> sampling(ScheduledExecutorService executor, SamplerComposer> composer) { + return new DefaultPlc4xSamplingSource<>(executor, composer); + } +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultPlc4xSamplingSource.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultPlc4xSamplingSource.java new file mode 100644 index 00000000..89df8485 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultPlc4xSamplingSource.java @@ -0,0 +1,103 @@ +package org.connectorio.addons.binding.plc4x.internal.source; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.connectorio.addons.binding.source.sampling.Sampler; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.SamplingSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultPlc4xSamplingSource implements SamplingSource> { + + private final Map>> operations = new ConcurrentHashMap<>(); + private final Set> futures = new CopyOnWriteArraySet<>(); + + private final ScheduledExecutorService executor; + private final SamplerComposer> composer; + + public DefaultPlc4xSamplingSource(ScheduledExecutorService executor, SamplerComposer> composer) { + this.executor = executor; + this.composer = composer; + } + + @Override + public void add(Long interval, String id, Plc4xSampler sampler) { + if (!operations.containsKey(interval)) { + operations.put(interval, new ConcurrentHashMap<>()); + } + operations.get(interval).put(id, sampler); + } + + @Override + public void request(Plc4xSampler task) { + executor.submit(new Plc4xSamplerRunnable(task)); + } + + @Override + public void remove(String id) { + Long interval = null; + for (Entry>> entry : operations.entrySet()) { + if (entry.getValue().remove(id) != null) { + interval = entry.getKey(); + } + } + + if (interval != null && operations.get(interval).isEmpty()) { + operations.remove(interval); + } + } + + @Override + public boolean start() { + if (operations.isEmpty()) { + return false; + } + + for (Entry>> sampleOperations : operations.entrySet()) { + Map> polledValues = sampleOperations.getValue(); + + Collection> samplers = composer == null ? polledValues.values() : composer.merge(new ArrayList<>(polledValues.values())); + for (Plc4xSampler sampler : samplers) { + ScheduledFuture future = executor.scheduleAtFixedRate(new Plc4xSamplerRunnable(sampler), + sampleOperations.getKey(), sampleOperations.getKey(), TimeUnit.MILLISECONDS); + futures.add(future); + } + + } + + return true; + } + + @Override + public void stop() { + futures.forEach((future) -> future.cancel(true)); + } + + static class Plc4xSamplerRunnable implements Runnable { + + private final Logger logger = LoggerFactory.getLogger(Plc4xSamplerRunnable.class); + + private final Sampler sampler; + + Plc4xSamplerRunnable(Sampler sampler) { + this.sampler = sampler; + } + + @Override + public void run() { + logger.trace("Executing operation {}", sampler); + sampler.fetch(); + } + } +} diff --git a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/SubscribeFetchContainer.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultSubscriberSource.java similarity index 51% rename from bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/SubscribeFetchContainer.java rename to bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultSubscriberSource.java index f9eda5c2..b9b5bf2b 100644 --- a/bundles/org.connectorio.addons.binding.amsads/src/main/java/org/connectorio/addons/binding/amsads/internal/handler/polling/SubscribeFetchContainer.java +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/internal/source/DefaultSubscriberSource.java @@ -1,52 +1,42 @@ -/* - * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.connectorio.addons.binding.amsads.internal.handler.polling; +package org.connectorio.addons.binding.plc4x.internal.source; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import org.apache.plc4x.java.ads.tag.AdsTag; import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent; import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest; import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest; import org.apache.plc4x.java.api.model.PlcSubscriptionHandle; +import org.apache.plc4x.java.api.model.PlcTag; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.connectorio.addons.binding.plc4x.source.SubscriberSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SubscribeFetchContainer implements FetchContainer { +public class DefaultSubscriberSource implements SubscriberSource { - private final Logger logger = LoggerFactory.getLogger(SubscribeFetchContainer.class); + private final Logger logger = LoggerFactory.getLogger(DefaultSubscriberSource.class); private final Map> handlers = new ConcurrentHashMap<>(); private final PlcSubscriptionRequest.Builder subscribeBuilder; private final PlcUnsubscriptionRequest.Builder unsubscribeBuilder; - public SubscribeFetchContainer(PlcConnection connection) { + public DefaultSubscriberSource(PlcConnection connection) { subscribeBuilder = connection.subscriptionRequestBuilder(); unsubscribeBuilder = connection.unsubscriptionRequestBuilder(); } @Override - public void add(Long interval, String channelId, AdsTag tag, Consumer onChange) { - subscribeBuilder.addChangeOfStateTag(channelId, tag); - handlers.put(channelId, onChange); + public void add(String id, T tag, Consumer onChange) { + subscribeBuilder.addChangeOfStateTag(id, tag); + handlers.put(id, onChange); + } + + @Override + public void remove(String id) { + handlers.remove(id); } @Override @@ -60,19 +50,25 @@ public boolean start() { return; } - for (String channelId : response.getTagNames()) { - PlcSubscriptionHandle subscriptionHandle = response.getSubscriptionHandle(channelId); - subscriptionHandle.register(new Consumer() { + for (String id : response.getTagNames()) { + PlcSubscriptionHandle subscriptionHandle = response.getSubscriptionHandle(id); + subscriptionHandle.register(new Consumer<>() { @Override public void accept(PlcSubscriptionEvent plcSubscriptionEvent) { - Object value = plcSubscriptionEvent.getObject(channelId); + Object value = plcSubscriptionEvent.getObject(id); + PlcResponseCode code = plcSubscriptionEvent.getResponseCode(id); + if (code != PlcResponseCode.OK) { + logger.debug("Received non-OK subscription response code {} for channel {}. Ignoring value {}.", id, code, value); + return; + } + if (value != null) { - logger.debug("Channel {} received update {}", channelId, value); - Consumer consumer = handlers.get(channelId); + logger.debug("Channel {} received update {}", id, value); + Consumer consumer = handlers.get(id); if (consumer != null) { consumer.accept(value); } else { - logger.warn("Unknown channel/tag association: {}", channelId); + logger.warn("Unknown channel/tag association: {}", id); } } } @@ -88,4 +84,4 @@ public void stop() { unsubscribeBuilder.build().execute().join(); } -} +} \ No newline at end of file diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSampler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSampler.java new file mode 100644 index 00000000..159511ae --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSampler.java @@ -0,0 +1,67 @@ +package org.connectorio.addons.binding.plc4x.sampler; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcReadRequest.Builder; +import org.apache.plc4x.java.api.model.PlcTag; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class DefaultPlc4xSampler implements Plc4xSampler { + + private final Logger logger = LoggerFactory.getLogger(DefaultPlc4xSampler.class); + private final PlcConnection connection; + private final Map tags; + private final Map> callbacks; + + public DefaultPlc4xSampler(PlcConnection connection, String id, T tag, Consumer callback) { + this(connection, Map.of(id, tag), Map.of(id, callback)); + } + + public DefaultPlc4xSampler(PlcConnection connection, Map tags, Map> callbacks) { + this.connection = connection; + this.tags = tags; + this.callbacks = callbacks; + } + + @Override + public Map getTags() { + return tags; + } + + @Override + public Map> getCallbacks() { + return callbacks; + } + + @Override + public CompletableFuture fetch() { + Builder requestBuilder = connection.readRequestBuilder(); + for (Entry entry : tags.entrySet()) { + requestBuilder.addTag(entry.getKey(), entry.getValue()); + } + return requestBuilder.build().execute().whenComplete((response, error) -> { + if (error != null) { + logger.warn("Could not retrieve tag {} values", tags, error); + return; + } + for (String tag : response.getTagNames()) { + PlcResponseCode code = response.getResponseCode(tag); + Object value = response.getObject(tag); + if (code != PlcResponseCode.OK) { + logger.warn("Received non-OK status {} for tag {}, ignoring its value {}", code, tag, value); + return; + } + if (value != null) { + callbacks.get(tag).accept(value); + } + } + }); + } + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSamplerComposer.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSamplerComposer.java new file mode 100644 index 00000000..f942b06f --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/sampler/DefaultPlc4xSamplerComposer.java @@ -0,0 +1,40 @@ +package org.connectorio.addons.binding.plc4x.sampler; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.plc4x.source.Plc4xSampler; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; + +public class DefaultPlc4xSamplerComposer implements SamplerComposer> { + + private final PlcConnection connection; + + public DefaultPlc4xSamplerComposer(PlcConnection connection) { + this.connection = connection; + } + + @Override + public List> merge(List> samplers) { + Map tags = new HashMap<>(); + Map> callbacks = new HashMap<>(); + + for (Plc4xSampler sampler : samplers) { + Map samplerTags = sampler.getTags(); + for (String tag : samplerTags.keySet()) { + if (tags.containsKey(tag)) { + throw new IllegalArgumentException("Duplicated tag found: " + tag); + } + } + tags.putAll(samplerTags); + callbacks.putAll(sampler.getCallbacks()); + } + + return Collections.singletonList(new DefaultPlc4xSampler<>(connection, tags, callbacks)); + } + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/BasicConverter.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/BasicConverter.java new file mode 100644 index 00000000..cbb719e4 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/BasicConverter.java @@ -0,0 +1,12 @@ +package org.connectorio.addons.binding.plc4x.source; + +import org.openhab.core.types.State; + +public class BasicConverter implements Converter { + + @Override + public State fromValue(Object value) { + return null; + } + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/ChannelCallback.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/ChannelCallback.java new file mode 100644 index 00000000..aaad7604 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/ChannelCallback.java @@ -0,0 +1,22 @@ +package org.connectorio.addons.binding.plc4x.source; + +import java.util.function.Consumer; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; + +public class ChannelCallback implements Consumer { + + private final ThingHandlerCallback callback; + private final Channel channel; + + public ChannelCallback(ThingHandlerCallback callback, Channel channel) { + this.callback = callback; + this.channel = channel; + } + + @Override + public void accept(State state) { + callback.stateUpdated(channel.getUID(), state); + } +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Converter.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Converter.java new file mode 100644 index 00000000..2777f959 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Converter.java @@ -0,0 +1,9 @@ +package org.connectorio.addons.binding.plc4x.source; + +import org.openhab.core.types.State; + +public interface Converter { + + State fromValue(Object value); + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Plc4xSampler.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Plc4xSampler.java new file mode 100644 index 00000000..8ddad39c --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/Plc4xSampler.java @@ -0,0 +1,14 @@ +package org.connectorio.addons.binding.plc4x.source; + +import java.util.Map; +import java.util.function.Consumer; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.source.sampling.Sampler; + +public interface Plc4xSampler extends Sampler { + + Map getTags(); + + Map> getCallbacks(); + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SamplerCallback.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SamplerCallback.java new file mode 100644 index 00000000..33840aeb --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SamplerCallback.java @@ -0,0 +1,25 @@ +package org.connectorio.addons.binding.plc4x.source; + +import java.util.function.Consumer; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; + +public class SamplerCallback implements Consumer { + + private final Converter converter; + private final Consumer callback; + + public SamplerCallback(Converter converter, Consumer callback) { + this.converter = converter; + this.callback = callback; + } + + @Override + public void accept(Object value) { + State state = converter.fromValue(value); + if (state != null) { + callback.accept(state); + } + } +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SourceFactory.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SourceFactory.java new file mode 100644 index 00000000..bc696a56 --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SourceFactory.java @@ -0,0 +1,17 @@ +package org.connectorio.addons.binding.plc4x.source; + +import java.util.concurrent.ScheduledExecutorService; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.SamplingSource; + +public interface SourceFactory { + + SubscriberSource subscriber(PlcConnection connection); + + SamplingSource> sampling(ScheduledExecutorService executor); + + SamplingSource> sampling(ScheduledExecutorService executor, SamplerComposer> composer); + +} diff --git a/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SubscriberSource.java b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SubscriberSource.java new file mode 100644 index 00000000..6b8cd42e --- /dev/null +++ b/bundles/org.connectorio.addons.binding.plc4x/src/main/java/org/connectorio/addons/binding/plc4x/source/SubscriberSource.java @@ -0,0 +1,13 @@ +package org.connectorio.addons.binding.plc4x.source; + +import java.util.function.Consumer; +import org.apache.plc4x.java.api.model.PlcTag; +import org.connectorio.addons.binding.source.Source; + +public interface SubscriberSource extends Source { + + void add(String id, T tag, Consumer consumer); + + void remove(String id); + +} diff --git a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7HandlerFactory.java b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7HandlerFactory.java index 6a4f397c..e7a90448 100644 --- a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7HandlerFactory.java +++ b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7HandlerFactory.java @@ -21,6 +21,7 @@ import static org.connectorio.addons.binding.s7.S7BindingConstants.THING_TYPE_TCP_IP; import org.connectorio.addons.binding.plc4x.Plc4xHandlerFactory; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; import org.connectorio.plc4x.extras.osgi.PlcDriverManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -40,11 +41,13 @@ public class S7HandlerFactory extends Plc4xHandlerFactory { private final PlcDriverManager driverManager; + private final SourceFactory sourceFactory; @Activate - public S7HandlerFactory(@Reference PlcDriverManager driverManager) { + public S7HandlerFactory(@Reference PlcDriverManager driverManager, @Reference SourceFactory sourceFactory) { super(THING_TYPE_TCP_IP, THING_TYPE_S7); this.driverManager = driverManager; + this.sourceFactory = sourceFactory; } @Override @@ -54,7 +57,7 @@ protected ThingHandler createHandler(Thing thing) { if (THING_TYPE_TCP_IP.equals(thingTypeUID)) { return new S7NetworkBridgeHandler((Bridge) thing, driverManager); } else if (THING_TYPE_S7.equals(thingTypeUID)) { - return new S7PlcHandler(thing); + return new S7PlcHandler(thing, sourceFactory); } return null; diff --git a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7NetworkBridgeHandler.java b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7NetworkBridgeHandler.java index 28cd7fb3..7bbd769b 100644 --- a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7NetworkBridgeHandler.java +++ b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7NetworkBridgeHandler.java @@ -21,6 +21,7 @@ import org.apache.plc4x.java.api.PlcConnection; import org.apache.plc4x.java.api.exceptions.PlcConnectionException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; +import org.apache.plc4x.java.s7.readwrite.tag.S7Tag; import org.connectorio.addons.binding.plc4x.handler.base.PollingPlc4xBridgeHandler; import org.connectorio.plc4x.extras.osgi.PlcDriverManager; import org.connectorio.addons.binding.s7.S7BindingConstants; @@ -39,7 +40,7 @@ * * @author Lukasz Dywicki - Initial contribution */ -public class S7NetworkBridgeHandler extends PollingPlc4xBridgeHandler { +public class S7NetworkBridgeHandler extends PollingPlc4xBridgeHandler { private final Logger logger = LoggerFactory.getLogger(S7NetworkBridgeHandler.class); private final PlcDriverManager driverManager; diff --git a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7PlcHandler.java b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7PlcHandler.java index 04a64743..f70605c7 100644 --- a/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7PlcHandler.java +++ b/bundles/org.connectorio.addons.binding.s7/src/main/java/org/connectorio/addons/binding/s7/internal/handler/S7PlcHandler.java @@ -17,16 +17,31 @@ */ package org.connectorio.addons.binding.s7.internal.handler; -import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.s7.readwrite.tag.S7Tag; import org.connectorio.addons.binding.config.PollingConfiguration; +import org.connectorio.addons.binding.plc4x.config.CommonChannelConfiguration; import org.connectorio.addons.binding.plc4x.handler.base.PollingPlc4xThingHandler; +import org.connectorio.addons.binding.plc4x.source.BasicConverter; +import org.connectorio.addons.binding.plc4x.source.SourceFactory; import org.connectorio.addons.binding.s7.S7BindingConstants; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.Thing; -public class S7PlcHandler extends PollingPlc4xThingHandler { +public class S7PlcHandler extends PollingPlc4xThingHandler { - public S7PlcHandler(Thing thing) { - super(thing); + public S7PlcHandler(Thing thing, SourceFactory sourceFactory) { + super(thing, sourceFactory, new BasicConverter()); + } + + @Override + public void initialize() { + super.initialize(); + } + + @Override + protected S7Tag createTag(Channel channel) { + CommonChannelConfiguration configuration = channel.getConfiguration().as(CommonChannelConfiguration.class); + return S7Tag.of(configuration.field); } @Override diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/ChainingComposer.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/ChainingComposer.java new file mode 100644 index 00000000..cfbcc415 --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/ChainingComposer.java @@ -0,0 +1,34 @@ +package org.connectorio.addons.binding.internal.source; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.Sampler; + +public class ChainingComposer implements SamplerComposer { + + @Override + public List merge(List samplers) { + return List.of(new ChainedSampler(samplers)); + } + + static class ChainedSampler implements Sampler { + + private final Collection samplers; + + ChainedSampler(Collection samplers) { + this.samplers = samplers; + } + + @Override + public CompletableFuture fetch() { + CompletableFuture completedFuture = CompletableFuture.completedFuture(null); + for (Sampler sampler : samplers) { + completedFuture = completedFuture.thenCombine(sampler.fetch(), (left, right) -> null); + } + return completedFuture; + } + } + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/DefaultSourceFactory.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/DefaultSourceFactory.java new file mode 100644 index 00000000..ef4e076c --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/DefaultSourceFactory.java @@ -0,0 +1,33 @@ +package org.connectorio.addons.binding.internal.source; + +import java.util.concurrent.ScheduledExecutorService; +import org.connectorio.addons.binding.internal.source.sampling.DefaultSamplingSource; +import org.connectorio.addons.binding.source.SourceFactory; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.Sampler; +import org.connectorio.addons.binding.source.sampling.SamplingSource; +import org.osgi.service.component.annotations.Component; + +@Component(property = {"default=true"}, service = SourceFactory.class) +public class DefaultSourceFactory implements SourceFactory { + + @Override + public SamplingSource sampling(ScheduledExecutorService executor) { + return createSamplingSource(executor, null); + } + + @Override + public SamplingSource sampling(ScheduledExecutorService executor, SamplerComposer reducer) { + if (reducer == null) { + throw new IllegalArgumentException("Reducer should not be null, use other factory method variant instead"); + } + return createSamplingSource(executor, reducer); + } + + private static DefaultSamplingSource createSamplingSource(ScheduledExecutorService executor, SamplerComposer reducer) { + return new DefaultSamplingSource<>(executor, reducer); + } + + + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/sampling/DefaultSamplingSource.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/sampling/DefaultSamplingSource.java new file mode 100644 index 00000000..b43e5ac4 --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/internal/source/sampling/DefaultSamplingSource.java @@ -0,0 +1,101 @@ +package org.connectorio.addons.binding.internal.source.sampling; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.Sampler; +import org.connectorio.addons.binding.source.sampling.SamplingSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultSamplingSource implements SamplingSource { + + private final Map> operations = new ConcurrentHashMap<>(); + private final Set> futures = new CopyOnWriteArraySet<>(); + + private final ScheduledExecutorService executor; + private final SamplerComposer composer; + + public DefaultSamplingSource(ScheduledExecutorService executor, SamplerComposer composer) { + this.executor = executor; + this.composer = composer; + } + + @Override + public void add(Long interval, String id, T sampler) { + if (!operations.containsKey(interval)) { + operations.put(interval, new ConcurrentHashMap<>()); + } + operations.get(interval).put(id, sampler); + } + + @Override + public void request(T task) { + executor.submit(new SamplerRunnable(task)); + } + + @Override + public void remove(String id) { + Long interval = null; + for (Entry> entry : operations.entrySet()) { + if (entry.getValue().remove(id) != null) { + interval = entry.getKey(); + } + } + + if (interval != null && operations.get(interval).isEmpty()) { + operations.remove(interval); + } + } + + @Override + public boolean start() { + if (operations.isEmpty()) { + return false; + } + + for (Entry> sampleOperations : operations.entrySet()) { + Map polledValues = sampleOperations.getValue(); + + Collection samplers = composer == null ? polledValues.values() : composer.merge(new ArrayList<>(polledValues.values())); + for (T sampler : samplers) { + ScheduledFuture future = executor.scheduleAtFixedRate(new SamplerRunnable(sampler), + sampleOperations.getKey(), sampleOperations.getKey(), TimeUnit.MILLISECONDS); + futures.add(future); + } + + } + + return true; + } + + @Override + public void stop() { + futures.forEach((future) -> future.cancel(true)); + } + + static class SamplerRunnable implements Runnable { + + private final Logger logger = LoggerFactory.getLogger(SamplerRunnable.class); + + private final Sampler sampler; + + SamplerRunnable(Sampler sampler) { + this.sampler = sampler; + } + + @Override + public void run() { + logger.trace("Executing operation {}", sampler); + sampler.fetch(); + } + } +} \ No newline at end of file diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/Source.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/Source.java new file mode 100644 index 00000000..3a59f01f --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/Source.java @@ -0,0 +1,9 @@ +package org.connectorio.addons.binding.source; + +public interface Source { + + boolean start(); + + void stop(); + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/SourceFactory.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/SourceFactory.java new file mode 100644 index 00000000..603cd337 --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/SourceFactory.java @@ -0,0 +1,13 @@ +package org.connectorio.addons.binding.source; + +import java.util.concurrent.ScheduledExecutorService; +import org.connectorio.addons.binding.source.sampling.SamplerComposer; +import org.connectorio.addons.binding.source.sampling.Sampler; +import org.connectorio.addons.binding.source.sampling.SamplingSource; + +public interface SourceFactory { + + SamplingSource sampling(ScheduledExecutorService executor); + SamplingSource sampling(ScheduledExecutorService executor, SamplerComposer reducer); + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/Sampler.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/Sampler.java new file mode 100644 index 00000000..be2b6539 --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/Sampler.java @@ -0,0 +1,9 @@ +package org.connectorio.addons.binding.source.sampling; + +import java.util.concurrent.CompletableFuture; + +public interface Sampler { + + CompletableFuture fetch(); + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplerComposer.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplerComposer.java new file mode 100644 index 00000000..e6e2e787 --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplerComposer.java @@ -0,0 +1,9 @@ +package org.connectorio.addons.binding.source.sampling; + +import java.util.List; + +public interface SamplerComposer { + + List merge(List samplers); + +} diff --git a/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplingSource.java b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplingSource.java new file mode 100644 index 00000000..e8e39def --- /dev/null +++ b/bundles/org.connectorio.addons.binding/src/main/java/org/connectorio/addons/binding/source/sampling/SamplingSource.java @@ -0,0 +1,22 @@ +package org.connectorio.addons.binding.source.sampling; + +import org.connectorio.addons.binding.source.Source; + +public interface SamplingSource extends Source { + + void add(Long interval, String id, T task); + + void remove(String id); + + void request(T task); + + /** + * Creates unoptimized (linked) operation which just calls second operation after first. + * + * @param first First operation to call. + * @param second Second operation to call. + * @return Composite operation. + */ +// CompositeOperation compose(Operation first, Operation second); + +}