diff --git a/CODEOWNERS b/CODEOWNERS index 0526f553154f3..9f7a7ce8b6187 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -171,6 +171,7 @@ /bundles/org.openhab.binding.icloud/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.ihc/ @paulianttila /bundles/org.openhab.binding.insteon/ @jsetton +/bundles/org.openhab.binding.intellicenter2/ @valdisrigdon /bundles/org.openhab.binding.intesis/ @hmerk /bundles/org.openhab.binding.iotawatt/ @PRosenb /bundles/org.openhab.binding.ipcamera/ @Skinah diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index a546e9c9cbed4..a36e876c5931b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -841,6 +841,11 @@ org.openhab.binding.insteon ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.intellicenter2 + ${project.version} + org.openhab.addons.bundles org.openhab.binding.intesis diff --git a/bundles/org.openhab.binding.intellicenter2/NOTICE b/bundles/org.openhab.binding.intellicenter2/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.intellicenter2/README.md b/bundles/org.openhab.binding.intellicenter2/README.md new file mode 100644 index 0000000000000..33ba0435b6f5f --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/README.md @@ -0,0 +1,76 @@ +# IntelliCenter2 Binding + +_Give some details about what this binding is meant for - a protocol, system, specific device._ + +_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._ +_You can place such resources into a `doc` folder next to this README.md._ + +_Put each sentence in a separate line to improve readability of diffs._ + +## Supported Things + +_Please describe the different supported things / devices including their ThingTypeUID within this section._ +_Which different types are supported, which models were tested etc.?_ +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +- `bridge`: Short description of the Bridge, if any +- `sample`: Short description of the Thing with the ThingTypeUID `sample` + +## Discovery + +_Describe the available auto-discovery features here._ +_Mention for what it works and what needs to be kept in mind when using it._ + +## Binding Configuration + +_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._ +_In this section, you should link to this file and provide some information about the options._ +_The file could e.g. look like:_ + +``` +# Configuration for the IntelliCenter2 Binding +# +# Default secret key for the pairing of the IntelliCenter2 Thing. +# It has to be between 10-40 (alphanumeric) characters. +# This may be changed by the user for security reasons. +secret=openHABSecret +``` + +_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._ + +_If your binding does not offer any generic configurations, you can remove this section completely._ + +## Thing Configuration + +_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._ +_This should be mainly about its mandatory and optional configuration parameters._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +### `sample` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-----------------|---------|---------------------------------------|---------|----------|----------| +| hostname | text | Hostname or IP address of the device | N/A | yes | no | +| password | text | Password to access the device | N/A | yes | no | +| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes | + +## Channels + +_Here you should provide information about available channel types, what their meaning is and how they can be used._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._ + +| Channel | Type | Read/Write | Description | +|---------|--------|------------|-----------------------------| +| control | Switch | RW | This is the control channel | + +## Full Example + +_Provide a full usage example based on textual configuration files._ +_*.things, *.items examples are mandatory as textual configuration is well used by many users._ +_*.sitemap examples are optional._ + +## Any custom content here! + +_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_ diff --git a/bundles/org.openhab.binding.intellicenter2/pom.xml b/bundles/org.openhab.binding.intellicenter2/pom.xml new file mode 100644 index 0000000000000..332ad6c78ccdc --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/pom.xml @@ -0,0 +1,25 @@ + + + + 4.0.0 + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.3.0-SNAPSHOT + + + org.openhab.binding.intellicenter2 + + openHAB Add-ons :: Bundles :: IntelliCenter2 Binding + + + + com.google.guava + guava + 14.0.1 + provided + + + + diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/feature/feature.xml b/bundles/org.openhab.binding.intellicenter2/src/main/feature/feature.xml new file mode 100644 index 0000000000000..29c9ea3fade73 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.intellicenter2/${project.version} + + diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2BindingConstants.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2BindingConstants.java new file mode 100644 index 0000000000000..2c8a5de87f801 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2BindingConstants.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link IntelliCenter2BindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class IntelliCenter2BindingConstants { + + private static final String BINDING_ID = "intellicenter2"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_POOL = new ThingTypeUID(BINDING_ID, "pool"); + public static final ThingTypeUID THING_TYPE_FEATURE = new ThingTypeUID(BINDING_ID, "feature"); + public static final ThingTypeUID THING_TYPE_LIGHT = new ThingTypeUID(BINDING_ID, "light"); + public static final ThingTypeUID THING_TYPE_PUMP = new ThingTypeUID(BINDING_ID, "pump"); + public static final ThingTypeUID THING_TYPE_SENSOR = new ThingTypeUID(BINDING_ID, "sensor"); + public static final ThingTypeUID BRIDGE_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + + // List of all Channel ids + public static final String CHANNEL_CURRENT_TEMPERATURE = "current-temperature"; + public static final String CHANNEL_TARGET_TEMPERATURE = "target-temperature"; + public static final String CHANNEL_HEATER_STATUS = "heater-status"; + public static final String CHANNEL_FEATURE_ON_OFF = "feature-on-off"; + public static final String CHANNEL_LIGHT_COLOR = "light-color"; + public static final String CHANNEL_LIGHT_POWER = "light-power"; + public static final String CHANNEL_PUMP_GPM = "pump-gpm"; + public static final String CHANNEL_PUMP_RPM = "pump-rpm"; + public static final String CHANNEL_PUMP_POWER = "pump-power"; + public static final String CHANNEL_SENSOR_PROBE = "sensor-probe"; + public static final String CHANNEL_SENSOR_SOURCE = "sensor-source"; + public static final String CHANNEL_SENSOR_CALIB = "sensor-calib"; +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2Configuration.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2Configuration.java new file mode 100644 index 0000000000000..d3a983b711b77 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2Configuration.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +/** + + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link IntelliCenter2Configuration} class contains fields mapping thing configuration parameters. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class IntelliCenter2Configuration { + + public String hostname = ""; + public int port = 6681; + + @Override + public String toString() { + return getClass().getSimpleName() + "{hostname=" + hostname + ", port=" + port + "}"; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2HandlerFactory.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2HandlerFactory.java new file mode 100644 index 0000000000000..d39e0ea348df7 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/IntelliCenter2HandlerFactory.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal; + +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.BRIDGE_TYPE_BRIDGE; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_FEATURE; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_LIGHT; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_POOL; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_PUMP; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_SENSOR; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2BridgeHandler; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2FeatureHandler; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2LightHandler; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2PoolHandler; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2PumpHandler; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2SensorHandler; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link IntelliCenter2HandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.intellicenter2", service = ThingHandlerFactory.class) +public class IntelliCenter2HandlerFactory extends BaseThingHandlerFactory { + + private final Logger logger = LoggerFactory.getLogger(IntelliCenter2HandlerFactory.class); + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_TYPE_BRIDGE, THING_TYPE_POOL, + THING_TYPE_FEATURE, THING_TYPE_LIGHT, THING_TYPE_PUMP, THING_TYPE_SENSOR); + + public IntelliCenter2HandlerFactory() { + super(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + final ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + logger.debug("Creating handler for {}", thingTypeUID); + if (thingTypeUID.equals(BRIDGE_TYPE_BRIDGE)) { + return new IntelliCenter2BridgeHandler((Bridge) thing); + } else if (thingTypeUID.equals(THING_TYPE_POOL)) { + return new IntelliCenter2PoolHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_FEATURE)) { + return new IntelliCenter2FeatureHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_LIGHT)) { + return new IntelliCenter2LightHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_PUMP)) { + return new IntelliCenter2PumpHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_SENSOR)) { + return new IntelliCenter2SensorHandler(thing); + } + logger.error("Unable to create handler for {}", thingTypeUID); + return null; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryService.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryService.java new file mode 100644 index 0000000000000..19f11b668be60 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryService.java @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.discovery; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_FEATURE; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_LIGHT; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_POOL; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_PUMP; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.THING_TYPE_SENSOR; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.IntelliCenter2HandlerFactory; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2BridgeHandler; +import org.openhab.binding.intellicenter2.internal.model.Body; +import org.openhab.binding.intellicenter2.internal.model.Circuit; +import org.openhab.binding.intellicenter2.internal.model.GetConfiguration; +import org.openhab.binding.intellicenter2.internal.model.GetHardwareDefinition; +import org.openhab.binding.intellicenter2.internal.model.GetHardwareDefinition.Argument; +import org.openhab.binding.intellicenter2.internal.model.Panel; +import org.openhab.binding.intellicenter2.internal.model.Pump; +import org.openhab.binding.intellicenter2.internal.model.ResponseModel; +import org.openhab.binding.intellicenter2.internal.model.Sensor; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Discovers Things from GetHardwareDefinition queries. + * + * @author Valdis Rigdon - Initial contribution + * + * @see GetHardwareDefinition + */ +@NonNullByDefault +public class IntelliCenter2DiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private final Logger logger = LoggerFactory.getLogger(IntelliCenter2DiscoveryService.class); + @Nullable + private IntelliCenter2BridgeHandler bridgeHandler; + + public IntelliCenter2DiscoveryService() { + super(IntelliCenter2HandlerFactory.SUPPORTED_THING_TYPES_UIDS, 30, false); + } + + @Override + public void setThingHandler(ThingHandler thingHandler) { + if (thingHandler instanceof IntelliCenter2BridgeHandler) { + this.bridgeHandler = (IntelliCenter2BridgeHandler) thingHandler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + public void activate() { + super.activate(new Hashtable<>()); + } + + @Override + public void deactivate() { + super.deactivate(); + } + + @Override + @VisibleForTesting + public void startScan() { + IntelliCenter2BridgeHandler h = bridgeHandler; + if (h != null) { + if (!h.getProtocolFuture().isDone()) { + return; + } + startScan(h.getProtocol()); + } + } + + private void startScan(final ICProtocol protocol) { + for (Argument discoveryArg : Argument.values()) { + logger.trace("Looking for devices with argument {}", discoveryArg); + final Future future = protocol.submit(discoveryArg.getRequest()); + try { + final GetHardwareDefinition hardware = new GetHardwareDefinition(future.get()); + for (Panel panel : hardware.getPanels()) { + if (discoveryArg == Argument.DEFAULT) { + for (final Body body : panel.getBodies()) { + // refresh the body to ensure that we have all the fields we want + final Body refreshedBody = new Body( + getUnchecked(protocol.submit(body.createRefreshRequest())).getObjectList().get(0)); + if (refreshedBody.isEnabled()) { + discoverPool(refreshedBody); + } + } + } else if (discoveryArg == Argument.CIRCUITS) { + for (Circuit circuit : panel.getCircuits()) { + if (circuit.isFeature()) { + discoverFeature(circuit); + } + if ("INTELLI".equals(circuit.getSubType())) { + discoverLight(circuit); + } + } + } else if (discoveryArg == Argument.PUMPS) { + for (Pump pump : panel.getPumps()) { + final Pump refreshed = new Pump( + getUnchecked(protocol.submit(pump.createRefreshRequest())).getObjectList().get(0)); + discoverPump(refreshed); + } + } else if (discoveryArg == Argument.SENSORS) { + for (Sensor sensor : panel.getSensors()) { + final Sensor refreshed = new Sensor( + getUnchecked(protocol.submit(sensor.createRefreshRequest())).getObjectList() + .get(0)); + discoverSensor(refreshed); + } + } + } + } catch (Exception e) { + logger.warn("Unable to discover IntelliCenter2 hardware for {}", discoveryArg, e); + } + } + try { + final Future future = protocol.submit(GetConfiguration.REQUEST); + final GetConfiguration configuration = new GetConfiguration(future.get()); + for (Circuit featureCircuit : configuration.getFeatureCircuits()) { + final Circuit refreshedCircuit = new Circuit( + getUnchecked(protocol.submit(featureCircuit.createRefreshRequest())).getObjectList().get(0)); + if (refreshedCircuit.isFeature()) { + discoverFeature(refreshedCircuit); + } + } + } catch (Exception e) { + logger.warn("Unable to discover IntelliCenter2 hardware via GetConfiguration", e); + } + } + + private void discoverLight(Circuit circuit) { + discoveryResult(THING_TYPE_LIGHT, circuit); + } + + private void discoverFeature(Circuit circuit) { + discoveryResult(THING_TYPE_FEATURE, circuit); + } + + private void discoverPump(Pump pump) { + discoveryResult(THING_TYPE_PUMP, pump); + } + + private void discoverSensor(Sensor pump) { + discoveryResult(THING_TYPE_SENSOR, pump); + } + + private void discoverPool(Body body) { + final Map properties = new HashMap<>(); + properties.put(Attribute.VOL.name(), body.getVolume()); + discoveryResult(THING_TYPE_POOL, body, properties); + } + + private void discoveryResult(ThingTypeUID bindingId, ResponseModel model) { + discoveryResult(bindingId, model, new HashMap<>()); + } + + private void discoveryResult(ThingTypeUID bindingId, ResponseModel model, final Map properties) { + logger.debug("Discovered object {}", model); + final IntelliCenter2BridgeHandler bh = bridgeHandler; + if (bh == null) { + logger.error("discovered result with a null bridgeHandler {} {}", bindingId, model); + return; + } + final ThingUID bridgeUID = bh.getThing().getUID(); + final ThingUID uid = new ThingUID(bindingId, bridgeUID, model.getObjectName()); + + properties.put(Thing.PROPERTY_VENDOR, "Pentair"); + properties.put(Thing.PROPERTY_MODEL_ID, "IntelliCenter"); + properties.put(Attribute.OBJNAM.name(), model.getObjectName()); + properties.put(Attribute.SUBTYP.name(), model.getSubType()); + properties.put(Attribute.SNAME.name(), model.getSname()); + + final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) + .withLabel(model.getSname()).withProperties(properties) + .withRepresentationProperty(Attribute.OBJNAM.name()).build(); + + thingDiscovered(result); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2BridgeHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2BridgeHandler.java new file mode 100644 index 0000000000000..6ff97b087575c --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2BridgeHandler.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Collections; + +import javax.measure.Unit; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.IntelliCenter2Configuration; +import org.openhab.binding.intellicenter2.internal.discovery.IntelliCenter2DiscoveryService; +import org.openhab.binding.intellicenter2.internal.model.SystemInfo; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.Bridge; +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.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +/** + * Handler for an IntelliCenter2 bridge, or system. This contains a connection to IntelliCenter. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class IntelliCenter2BridgeHandler extends BaseBridgeHandler { + + private final Logger logger = LoggerFactory.getLogger(IntelliCenter2BridgeHandler.class); + + private final IntelliCenter2Configuration config; + private final SettableFuture protocolFuture; + + @Nullable + private SystemInfo systemInfo; + + public IntelliCenter2BridgeHandler(Bridge bridge) { + this(bridge, null); + } + + public IntelliCenter2BridgeHandler(Bridge bridge, @Nullable IntelliCenter2Configuration config) { + super(bridge); + this.config = config == null ? getConfigAs(IntelliCenter2Configuration.class) : config; + this.protocolFuture = SettableFuture.create(); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + scheduler.execute(() -> { + try { + logger.debug("Attempting bridge connection to {}", config); + final ICProtocol protocol = new ICProtocol(config); + protocolFuture.set(protocol); + systemInfo = protocol.getSystemInfo(); + logger.debug("Connected to IntelliCenter2 {}", systemInfo); + SystemInfo si = systemInfo; + if (si != null) { + updateProperty(Thing.PROPERTY_MODEL_ID, "IntelliCenter"); + updateProperty("mode", si.getMode()); + updateProperty("propertyName", si.getPropertyName()); + updateProperty("version", si.getVersion()); + updateProperty("intellicenterVersion", si.getIntellicenterVersion()); + if (!si.getIntellicenterVersion().equals("1.064")) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, String.format( + "Running a non-supported version of IntelliCenter: %s", si.getIntellicenterVersion())); + return; + } + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Unable to get system info from IntelliCenter2."); + } + } catch (UnknownHostException e) { + updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + String.format("unknown host name: %s", config.hostname)); + } catch (IOException e) { + updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + String.format("Unable to connect to host %s", config.hostname)); + } + }); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + public ListenableFuture getProtocolFuture() { + return protocolFuture; + } + + public ICProtocol getProtocol() { + return Futures.getUnchecked(protocolFuture); + } + + public Unit getTemperatureUnits() { + if (systemInfo != null) { + return systemInfo.isMetricSystem() ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + } + throw new IllegalStateException("systemInfo was not set yet."); + } + + @Override + public void dispose() { + scheduler.execute(() -> { + if (protocolFuture.isDone()) { + try { + getProtocol().close(); + } catch (Exception ignored) { + } + } + updateStatus(ThingStatus.OFFLINE); + }); + } + + @Override + public Collection> getServices() { + return Collections.singleton(IntelliCenter2DiscoveryService.class); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2FeatureHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2FeatureHandler.java new file mode 100644 index 0000000000000..e88e6e4d71083 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2FeatureHandler.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_FEATURE_ON_OFF; + +import java.util.Map; +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.Circuit; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * Handler for an IntelliCenter2 feature. Features are exposed as On/Off, and represent a Circuit. + * + * @author Valdis Rigdon - Initial contribution + * + * @see Circuit + */ +@NonNullByDefault +public class IntelliCenter2FeatureHandler extends IntelliCenter2ThingHandler { + + public IntelliCenter2FeatureHandler(Thing thing) { + super(thing); + } + + @Override + protected Circuit queryModel(ICProtocol protocol) { + final String id = getObjectName(); + final ICRequest request = ICRequest.getParamList(null, Circuit.createRefreshRequest(id)); + final Future response = protocol.submit(request); + return new Circuit(getUnchecked(response).getObjectList().get(0)); + } + + @Override + protected void updateState(Circuit model) { + updateState(CHANNEL_FEATURE_ON_OFF, OnOffType.from(model.isOn())); + } + + @Override + protected Circuit createFromResponse(ResponseObject response) { + return new Circuit(response); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + final ICProtocol p = getProtocol(); + if (p != null) { + switch (channelUID.getId()) { + case CHANNEL_FEATURE_ON_OFF: + if (command instanceof OnOffType) { + var request = ICRequest.setParamList( + new RequestObject(getObjectName(), Map.of(Attribute.STATUS, command.toString()))); + p.submit(request); + } + break; + } + } + } + + @Override + protected void updateState(ChannelUID channelUID, Circuit model) { + switch (channelUID.getId()) { + case CHANNEL_FEATURE_ON_OFF: + updateState(channelUID, OnOffType.from(model.isOn())); + break; + default: + break; + } + } + + @Override + @Nullable + protected String toChannelId(Attribute a) { + switch (a) { + case STATUS: + return CHANNEL_FEATURE_ON_OFF; + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2LightHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2LightHandler.java new file mode 100644 index 0000000000000..3e39653b81d74 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2LightHandler.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_LIGHT_COLOR; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_LIGHT_POWER; + +import java.util.Map; +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.IntelliBrite; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * Handler for an IntelliCenter2 IntelliBrite light. + * + * @author Valdis Rigdon - Initial contribution + * + * @see IntelliBrite + */ +@NonNullByDefault +public class IntelliCenter2LightHandler extends IntelliCenter2ThingHandler { + + public IntelliCenter2LightHandler(Thing thing) { + super(thing); + } + + @Override + protected IntelliBrite queryModel(ICProtocol protocol) { + final String id = getObjectName(); + final ICRequest request = ICRequest.getParamList(null, IntelliBrite.createRefreshRequest(id)); + final Future response = protocol.submit(request); + return new IntelliBrite(getUnchecked(response).getObjectList().get(0)); + } + + @Override + protected void updateState(IntelliBrite model) { + updateState(CHANNEL_LIGHT_POWER, OnOffType.from(model.isOn())); + updateState(CHANNEL_LIGHT_COLOR, toHSB(model.getColor())); + } + + @Override + protected IntelliBrite createFromResponse(ResponseObject response) { + return new IntelliBrite(response); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + final ICProtocol p = getProtocol(); + if (p != null) { + switch (channelUID.getId()) { + case CHANNEL_LIGHT_POWER: + if (command instanceof OnOffType) { + var request = ICRequest.setParamList( + new RequestObject(getObjectName(), Map.of(Attribute.STATUS, command.toString()))); + p.submit(request); + } + break; + case CHANNEL_LIGHT_COLOR: + if (command instanceof HSBType) { + // ACT is the value to set, but USE is returned + var params = Map.of(Attribute.ACT, toColor(((HSBType) command)).intellicenterCode); + var request = ICRequest.setParamList(new RequestObject(getObjectName(), params)); + p.submit(request); + } + break; + } + } + } + + @Override + protected void updateState(ChannelUID channelUID, IntelliBrite model) { + switch (channelUID.getId()) { + case CHANNEL_LIGHT_POWER: + updateState(channelUID, OnOffType.from(model.isOn())); + break; + case CHANNEL_LIGHT_COLOR: + updateState(channelUID, toHSB(model.getColor())); + break; + default: + break; + } + } + + @Override + @Nullable + protected String toChannelId(Attribute a) { + switch (a) { + case STATUS: + return CHANNEL_LIGHT_POWER; + case ACT: + case USE: + return CHANNEL_LIGHT_COLOR; + case LIMIT: + // explicit ignore; we subscribe to LIMIT changes, but we don't really care + return null; + default: + return null; + } + } + + private static HSBType toHSB(IntelliBrite.Color color) { + return new HSBType(new DecimalType(color.hue), new PercentType(color.saturation), new PercentType(100)); + } + + private static IntelliBrite.Color toColor(HSBType hsb) { + int hue = hsb.getHue().intValue(); + int saturation = hsb.getSaturation().intValue(); + return IntelliBrite.Color.from(hue, saturation); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PoolHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PoolHandler.java new file mode 100644 index 0000000000000..cc9cb187d6668 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PoolHandler.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_CURRENT_TEMPERATURE; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_HEATER_STATUS; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_TARGET_TEMPERATURE; + +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.Body; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for an IntelliCenter body or pool. This represents a body of water. + * + * @author Valdis Rigdon - Initial contribution + * + * @see Body + */ +@NonNullByDefault +public class IntelliCenter2PoolHandler extends IntelliCenter2ThingHandler { + + private final Logger logger = LoggerFactory.getLogger(IntelliCenter2PoolHandler.class); + + public IntelliCenter2PoolHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateState(Body pool) { + final IntelliCenter2BridgeHandler bh = getBridgeHandler(); + if (bh == null) { + return; + } + updateState(CHANNEL_HEATER_STATUS, OnOffType.from(pool.isHeating())); + updateState(CHANNEL_CURRENT_TEMPERATURE, + new QuantityType<>(pool.getCurrentTemperature(), bh.getTemperatureUnits())); + updateState(CHANNEL_TARGET_TEMPERATURE, + new QuantityType<>(pool.getTargetTemperature(), bh.getTemperatureUnits())); + } + + @Override + protected Body queryModel(ICProtocol protocol) { + final String id = getObjectName(); + final ICRequest request = ICRequest.getParamList(null, Body.createRefreshRequest(id)); + final Future response = protocol.submit(request); + return new Body(getUnchecked(response).getObjectList().get(0)); + } + + @Override + protected Body createFromResponse(ResponseObject response) { + return new Body(response); + } + + @Override + protected void updateState(ChannelUID channelUID, Body pool) { + final IntelliCenter2BridgeHandler bh = getBridgeHandler(); + if (bh == null) { + return; + } + switch (channelUID.getId()) { + case CHANNEL_CURRENT_TEMPERATURE: + updateState(channelUID, new QuantityType<>(pool.getCurrentTemperature(), bh.getTemperatureUnits())); + break; + case CHANNEL_TARGET_TEMPERATURE: + updateState(channelUID, new QuantityType<>(pool.getTargetTemperature(), bh.getTemperatureUnits())); + break; + case CHANNEL_HEATER_STATUS: + updateState(channelUID, OnOffType.from(pool.isHeating())); + break; + default: + logger.warn("Unable to update state for {}", channelUID); + } + } + + @Override + @Nullable + protected String toChannelId(Attribute a) { + switch (a) { + case LSTTMP: + return CHANNEL_CURRENT_TEMPERATURE; + case LOTMP: + return CHANNEL_TARGET_TEMPERATURE; + case HTMODE: + return CHANNEL_HEATER_STATUS; + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PumpHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PumpHandler.java new file mode 100644 index 0000000000000..471d1f2d1227b --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2PumpHandler.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_PUMP_GPM; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_PUMP_POWER; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_PUMP_RPM; + +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.Pump; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; + +/** + * Handler for an IntelliCenter2 IntelliBrite light. + * + * @author Valdis Rigdon - Initial contribution + * + * @see IntelliBrite + */ +@NonNullByDefault +public class IntelliCenter2PumpHandler extends IntelliCenter2ThingHandler { + + public IntelliCenter2PumpHandler(Thing thing) { + super(thing); + } + + @Override + protected Pump queryModel(ICProtocol protocol) { + final String id = getObjectName(); + final ICRequest request = ICRequest.getParamList(null, Pump.createRefreshRequest(id)); + final Future response = protocol.submit(request); + return new Pump(getUnchecked(response).getObjectList().get(0)); + } + + @Override + protected void updateState(Pump model) { + updateState(CHANNEL_PUMP_GPM, new DecimalType(model.getGPM())); + updateState(CHANNEL_PUMP_RPM, new DecimalType(model.getRPM())); + updateState(CHANNEL_PUMP_POWER, new QuantityType<>(model.getPowerConsumption(), Units.WATT)); + } + + @Override + protected Pump createFromResponse(ResponseObject response) { + return new Pump(response); + } + + @Override + protected void updateState(ChannelUID channelUID, Pump model) { + switch (channelUID.getId()) { + case CHANNEL_PUMP_GPM: + updateState(channelUID, new DecimalType(model.getGPM())); + break; + case CHANNEL_PUMP_RPM: + updateState(channelUID, new DecimalType(model.getRPM())); + break; + case CHANNEL_PUMP_POWER: + updateState(channelUID, new QuantityType<>(model.getPowerConsumption(), Units.WATT)); + break; + default: + break; + } + } + + @Override + @Nullable + protected String toChannelId(Attribute a) { + switch (a) { + case GPM: + return CHANNEL_PUMP_GPM; + case PWR: + return CHANNEL_PUMP_POWER; + case RPM: + return CHANNEL_PUMP_RPM; + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2SensorHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2SensorHandler.java new file mode 100644 index 0000000000000..e767f5e4a1ae5 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2SensorHandler.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_SENSOR_CALIB; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_SENSOR_PROBE; +import static org.openhab.binding.intellicenter2.internal.IntelliCenter2BindingConstants.CHANNEL_SENSOR_SOURCE; + +import java.util.concurrent.Future; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.Sensor; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * Handler for an IntelliCenter2 IntelliBrite light. + * + * @author Valdis Rigdon - Initial contribution + * + * @see IntelliBrite + */ +@NonNullByDefault +public class IntelliCenter2SensorHandler extends IntelliCenter2ThingHandler { + + public IntelliCenter2SensorHandler(Thing thing) { + super(thing); + } + + @Override + protected Sensor queryModel(ICProtocol protocol) { + final String id = getObjectName(); + final ICRequest request = ICRequest.getParamList(null, Sensor.createRefreshRequest(id)); + final Future response = protocol.submit(request); + return new Sensor(getUnchecked(response).getObjectList().get(0)); + } + + @Override + protected void updateState(Sensor model) { + updateState(CHANNEL_SENSOR_CALIB, new DecimalType(model.getCalibrationAdjustment())); + updateState(CHANNEL_SENSOR_PROBE, new DecimalType(model.getProbeTemperature())); + updateState(CHANNEL_SENSOR_SOURCE, new DecimalType(model.getSourceTemperature())); + } + + @Override + protected Sensor createFromResponse(ResponseObject response) { + return new Sensor(response); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + protected void updateState(ChannelUID channelUID, Sensor model) { + switch (channelUID.getId()) { + case CHANNEL_SENSOR_CALIB: + updateState(channelUID, new DecimalType(model.getCalibrationAdjustment())); + break; + case CHANNEL_SENSOR_PROBE: + updateState(channelUID, new DecimalType(model.getProbeTemperature())); + break; + case CHANNEL_SENSOR_SOURCE: + updateState(channelUID, new DecimalType(model.getSourceTemperature())); + break; + default: + break; + } + } + + @Override + @Nullable + protected String toChannelId(Attribute a) { + switch (a) { + case CALIB: + return CHANNEL_SENSOR_CALIB; + case PROBE: + return CHANNEL_SENSOR_PROBE; + case SOURCE: + return CHANNEL_SENSOR_SOURCE; + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2ThingHandler.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2ThingHandler.java new file mode 100644 index 0000000000000..77eeab7e0254b --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/handler/IntelliCenter2ThingHandler.java @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.handler; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJNAM; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.model.ResponseModel; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICProtocol; +import org.openhab.binding.intellicenter2.internal.protocol.NotifyListListener; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; +import org.openhab.core.thing.Bridge; +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.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract Handler that provides a consistent way to support other IntelliCenter2 handlers. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public abstract class IntelliCenter2ThingHandler extends BaseThingHandler + implements NotifyListListener { + + private final Logger logger = LoggerFactory.getLogger(IntelliCenter2ThingHandler.class); + + public IntelliCenter2ThingHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + scheduler.execute(() -> { + try { + // get an updated model from IC + final ICProtocol protocol = getProtocol(); + if (protocol == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + return; + } + final T model = queryModel(protocol); + protocol.subscribe(this, model.asRequestObject()); + updateStatus(ThingStatus.ONLINE); + updateState(model); + } catch (Exception e) { + logger.error("Error refreshing model", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } + }); + } + + @Override + public void dispose() { + super.dispose(); + scheduler.execute(() -> { + final ICProtocol protocol = getProtocol(); + if (protocol != null) { + protocol.unsubscribe(this); + } + }); + } + + @Nullable + protected ICProtocol getProtocol() { + final IntelliCenter2BridgeHandler bh = getBridgeHandler(); + if (bh != null) { + return bh.getProtocol(); + } + return null; + } + + @Nullable + protected IntelliCenter2BridgeHandler getBridgeHandler() { + final Bridge b = getBridge(); + if (b == null) { + return null; + } + return (IntelliCenter2BridgeHandler) b.getHandler(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Handling {} for {}", channelUID, command); + final ICProtocol protocol = getProtocol(); + if (protocol == null) { + logger.warn("handleCommand had a null protocol for {} {}", channelUID, command); + return; + } + if (command instanceof RefreshType) { + final T model = queryModel(protocol); + logger.debug("updateState uid: {}, id: {}, model: {}", channelUID, channelUID.getId(), model); + updateState(channelUID, model); + } + } + + @Override + public void onNotifyList(ResponseObject response) { + try { + if (!isInitialized()) { + logger.warn("Notified of a NotifyList response but this is not initialized: {}", response); + return; + } + final T model = createFromResponse(response); + logger.debug("onNotifyList {}", model); + response.getParams().forEach((k, v) -> { + var id = toChannelId(k); + if (id != null) { + var channel = getThing().getChannel(id); + if (channel != null) { + updateState(channel.getUID(), model); + } else { + logger.warn("Unable to find a channel to update for {}", id); + } + } else { + logger.warn("Received notifyList update for attribute {} that doesn't map to a channelId", k); + } + }); + } catch (Exception e) { + logger.debug("Error handling onNotifyList", e); + } + } + + protected String getObjectName() { + return Objects.requireNonNull(getThing().getProperties().get(OBJNAM.name())); + } + + protected abstract T queryModel(ICProtocol protocol); + + protected abstract void updateState(T model); + + protected abstract T createFromResponse(ResponseObject response); + + protected abstract void updateState(ChannelUID channelUID, T model); + + @Nullable + protected abstract String toChannelId(Attribute a); +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Body.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Body.java new file mode 100644 index 0000000000000..80c28502b0e15 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Body.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.HITMP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.HTMODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.HTSRC; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LOTMP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LSTTMP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.MODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.VOL; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Body extends ResponseModel { + + public static RequestObject createRefreshRequest(String objectName) { + return new RequestObject(objectName, REQUEST_ATTRIBUTES); + } + + private static final List REQUEST_ATTRIBUTES = List.of(MODE, HITMP, LOTMP, LSTTMP, VOL, SNAME, HTSRC, + HTMODE, STATUS); + + Body() { + super(REQUEST_ATTRIBUTES); + } + + public Body(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + public int getCurrentTemperature() { + return getValueAsInt(LSTTMP); + } + + public int getMode() { + return getValueAsInt(MODE); + } + + public int getHighTemperature() { + return getValueAsInt(HITMP); + } + + public int getTargetTemperature() { + return getValueAsInt(LOTMP); + } + + public String getHeaterSource() { + return getValueAsString(HTSRC); + } + + public int getVolume() { + return getValueAsInt(VOL); + } + + public HeatMode getHeaterMode() { + return getValueAsEnum(HTMODE, HeatMode.class); + } + + public boolean isHeating() { + return getHeaterMode() != HeatMode.OFF; + } + + public boolean isEnabled() { + return getValueAsBoolean(STATUS); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Circuit.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Circuit.java new file mode 100644 index 0000000000000..f4a81fea8d9d4 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Circuit.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.FEATR; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LISTORD; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.MODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; + +import java.util.List; + +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Circuit extends ResponseModel implements Comparable { + + private final static List REQUEST_ATTRIBUTES = List.of(STATUS, MODE, LISTORD, FEATR); + + public static RequestObject createRefreshRequest(String objectName) { + return new RequestObject(objectName, REQUEST_ATTRIBUTES); + } + + Circuit() { + super(REQUEST_ATTRIBUTES); + } + + public Circuit(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + protected Circuit(List requestAttributes) { + super(requestAttributes); + } + + protected Circuit(List requestAttributes, ResponseObject response) { + super(requestAttributes, response); + } + + public boolean isOn() { + return getValueAsBoolean(STATUS); + } + + public int getListOrder() { + return getValueAsInt(LISTORD); + } + + public String getMode() { + return getValueAsString(MODE); + } + + public boolean isFeature() { + return getValueAsBoolean(FEATR); + } + + @Override + public int compareTo(Circuit that) { + return new CompareToBuilder().append(getListOrder(), that.getListOrder()).toComparison(); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetConfiguration.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetConfiguration.java new file mode 100644 index 0000000000000..593114ff75d0c --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetConfiguration.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static java.util.stream.Collectors.toList; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.CIRCUIT; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJTYP; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class GetConfiguration { + + private static final String GET_CONFIGURATION = "GetConfiguration"; + public static final ICRequest REQUEST = ICRequest.getQuery(GET_CONFIGURATION); + + private final ICResponse response; + + public GetConfiguration(ICResponse response) { + this.response = Objects.requireNonNull(response); + } + + @SuppressWarnings("null") + public List getFeatureCircuits() { + return response.getAnswer().stream() + .filter(r -> CIRCUIT.toString().equals(r.getOptionalValueAsString(OBJTYP).orElse(""))) + .filter(r -> r.getObjectName().startsWith("FTR")).map(Circuit::new).collect(toList()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + "response=" + response + '}'; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinition.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinition.java new file mode 100644 index 0000000000000..d5126697564a2 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinition.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static java.util.stream.Collectors.toList; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJTYP; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class GetHardwareDefinition { + + private static final String GET_HARDWARE_DEFINITION = "GetHardwareDefinition"; + + private final ICResponse response; + + // ['CIRCUITS', 'PUMPS', 'CHEMS', 'VALVES', 'HEATERS', 'SENSORS', 'GROUPS']; + public enum Argument { + DEFAULT, + CIRCUITS, + PUMPS, + SENSORS; + + private final ICRequest request; + + Argument() { + String argument = name(); + if ("DEFAULT".equals(argument)) { + argument = ""; + } + this.request = ICRequest.getQuery(GET_HARDWARE_DEFINITION, argument); + } + + public ICRequest getRequest() { + return request; + } + } + + public GetHardwareDefinition(ICResponse response) { + this.response = response; + } + + @SuppressWarnings("null") + public List getPanels() { + return response.getAnswer().stream().filter(r -> r.getValueAsString(OBJTYP).equals("PANEL")).map(Panel::new) + .collect(toList()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/HeatMode.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/HeatMode.java new file mode 100644 index 0000000000000..467f37cbc6f1f --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/HeatMode.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public enum HeatMode { + + @SerializedName("0") + OFF, + @SerializedName("1") + FLAME, + @SerializedName("2") + SOLAR, + @SerializedName("3") + FLAKE, + @SerializedName("4") + ULTRA, + @SerializedName("5") + HYBRID, + @SerializedName("6") + MASTERTEMP, + @SerializedName("7") + MAXETEMP; +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Heater.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Heater.java new file mode 100644 index 0000000000000..760c7c7ec5ac0 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Heater.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.HTMODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Heater extends ResponseModel { + + private static final List REQUEST_ATTRIBUTES = List.of(HTMODE, STATUS, SNAME); + + public Heater() { + super(REQUEST_ATTRIBUTES); + } + + public Heater(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + public HeatMode getHeaterMode() { + return getValueAsEnum(HTMODE, HeatMode.class); + } + + public String getStatus() { + return getValueAsString(STATUS); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/IntelliBrite.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/IntelliBrite.java new file mode 100644 index 0000000000000..5990ed28204f5 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/IntelliBrite.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.ACT; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.FEATR; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LISTORD; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.MODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.USE; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class IntelliBrite extends Circuit { + + private static final List REQUEST_ATTRIBUTES = List.of(STATUS, MODE, LISTORD, FEATR, USE, ACT); + + public IntelliBrite() { + super(REQUEST_ATTRIBUTES); + } + + public IntelliBrite(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + public static RequestObject createRefreshRequest(String objectName) { + return new RequestObject(objectName, REQUEST_ATTRIBUTES); + } + + // borrowed from + // https://github.com/dustindclark/homebridge-pentair-intellicenter/blob/676b7dab2fbf5107443678c3ef5bc271108941f8/src/types.ts + public enum Color { + + @SerializedName("WHITER") + WHITE("WHITER", 0, 0), + @SerializedName("REDR") + RED("REDR", 0, 100), + @SerializedName("GREENR") + GREEN("GREENR", 120, 100), + @SerializedName("BLUER") + BLUE("BLUER", 240, 100), + @SerializedName("MAGNTAR") + MAGENTA("MAGNTAR", 300, 100); + + public final String intellicenterCode; + public final int hue; + public final int saturation; + + public static Color from(int hue, int saturation) { + if (saturation > ((RED.saturation - WHITE.saturation) / 2)) { + if (hue < ((GREEN.hue - RED.hue) / 2 + RED.hue)) { + return RED; + } else if (hue < ((BLUE.hue - GREEN.hue) / 2 + GREEN.hue)) { + return GREEN; + } else if (hue < ((MAGENTA.hue - BLUE.hue) / 2 + BLUE.hue)) { + return BLUE; + } else { + return MAGENTA; + } + } + return WHITE; + } + + Color(String intellicenterCode, int hue, int saturation) { + this.intellicenterCode = intellicenterCode; + this.hue = hue; + this.saturation = saturation; + } + } + + public Color getColor() { + return getValueAsEnum(USE, Color.class); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Panel.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Panel.java new file mode 100644 index 0000000000000..fd63a02520e99 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Panel.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static java.util.stream.Collectors.toList; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.BODY; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.CIRCUIT; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.CIRCUITS; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.HNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LISTORD; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.MODULE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJLIST; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJTYP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.PANID; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.PUMP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SENSE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.VER; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Panel extends ResponseModel { + + private static final List REQUEST_ATTRIBUTES = List.of(HNAME, SNAME, PANID, LISTORD, VER, OBJLIST); + + Panel() { + super(REQUEST_ATTRIBUTES); + } + + public Panel(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + @SuppressWarnings("null") + public List getBodies() { + return getModuleCircuits().filter(r -> BODY.toString().equals(r.getValueAsString(OBJTYP))).map(Body::new) + .collect(toList()); + } + + @SuppressWarnings("null") + public List getCircuits() { + return getModuleCircuits().filter(r -> CIRCUIT.toString().equals(r.getValueAsString(OBJTYP))).map(Circuit::new) + .collect(toList()); + } + + @SuppressWarnings("null") + public List getPumps() { + return getValueAsResponseObjects(OBJLIST).stream() + .filter(r -> PUMP.toString().equals(r.getValueAsString(OBJTYP))).map(Pump::new).collect(toList()); + } + + @SuppressWarnings("null") + public List getSensors() { + return getValueAsResponseObjects(OBJLIST).stream() + .filter(r -> SENSE.toString().equals(r.getValueAsString(OBJTYP))).map(Sensor::new).collect(toList()); + } + + private Stream getModuleCircuits() { + return getValueAsResponseObjects(OBJLIST).stream() + .filter(r -> MODULE.toString().equals(r.getValueAsString(OBJTYP))) + .map(r -> r.getValueAsResponseObjects(CIRCUITS)).flatMap(Collection::stream); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Pump.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Pump.java new file mode 100644 index 0000000000000..14f409cc17ec6 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Pump.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.GPM; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LISTORD; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.PWR; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.RPM; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; + +import java.util.List; + +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Pump extends ResponseModel implements Comparable { + + private final static List REQUEST_ATTRIBUTES = List.of(STATUS, LISTORD, PWR, GPM, RPM); + + public static RequestObject createRefreshRequest(String objectName) { + return new RequestObject(objectName, REQUEST_ATTRIBUTES); + } + + Pump() { + super(REQUEST_ATTRIBUTES); + } + + public Pump(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + protected Pump(List requestAttributes) { + super(requestAttributes); + } + + protected Pump(List requestAttributes, ResponseObject response) { + super(requestAttributes, response); + } + + public boolean isOn() { + return getValueAsBoolean(STATUS); + } + + public int getListOrder() { + return getValueAsInt(LISTORD); + } + + public int getGPM() { + return getValueAsInt(GPM); + } + + public int getRPM() { + return getValueAsInt(RPM); + } + + public int getPowerConsumption() { + return getValueAsInt(PWR); + } + + @Override + public int compareTo(Pump that) { + return new CompareToBuilder().append(getListOrder(), that.getListOrder()).toComparison(); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/ResponseModel.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/ResponseModel.java new file mode 100644 index 0000000000000..d30a96c5867c7 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/ResponseModel.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJNAM; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.OBJTYP; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SUBTYP; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class ResponseModel extends ResponseObject { + + private final Set requestAttributes; + + protected ResponseModel(Collection requestAttributes) { + this(requestAttributes, null); + } + + public ResponseModel(Collection requestAttributes, @Nullable ResponseObject response) { + super(response); + this.requestAttributes = new HashSet<>(requestAttributes); + this.requestAttributes.add(OBJTYP); + this.requestAttributes.add(SUBTYP); + this.requestAttributes.add(OBJNAM); + this.requestAttributes.add(SNAME); + } + + public String getSubType() { + return getValueAsString(SUBTYP); + } + + public String getObjectType() { + return getValueAsString(OBJTYP); + } + + public String getSname() { + return getValueAsString(SNAME); + } + + public RequestObject asRequestObject() { + return new RequestObject(getObjectName(), requestAttributes); + } + + public ICRequest createRefreshRequest() { + return ICRequest.getParamList(null, asRequestObject()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Sensor.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Sensor.java new file mode 100644 index 0000000000000..b49bb50bbeb88 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/Sensor.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.CALIB; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.LISTORD; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.PROBE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SOURCE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.STATUS; + +import java.util.List; + +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class Sensor extends ResponseModel implements Comparable { + + private final static List REQUEST_ATTRIBUTES = List.of(STATUS, SNAME, LISTORD, SOURCE, PROBE, CALIB); + + public static RequestObject createRefreshRequest(String objectName) { + return new RequestObject(objectName, REQUEST_ATTRIBUTES); + } + + Sensor() { + super(REQUEST_ATTRIBUTES); + } + + public Sensor(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + protected Sensor(List requestAttributes) { + super(requestAttributes); + } + + protected Sensor(List requestAttributes, ResponseObject response) { + super(requestAttributes, response); + } + + public int getListOrder() { + return getValueAsInt(LISTORD); + } + + public int getSourceTemperature() { + return getValueAsInt(SOURCE); + } + + public int getProbeTemperature() { + return getValueAsInt(PROBE); + } + + public int getCalibrationAdjustment() { + return getValueAsInt(CALIB); + } + + @Override + public int compareTo(Sensor that) { + return new CompareToBuilder().append(getListOrder(), that.getListOrder()).toComparison(); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/SystemInfo.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/SystemInfo.java new file mode 100644 index 0000000000000..9e5b1188e9b3e --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/SystemInfo.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.MODE; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.PROPNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.SNAME; +import static org.openhab.binding.intellicenter2.internal.protocol.Attribute.VER; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.intellicenter2.internal.protocol.Attribute; +import org.openhab.binding.intellicenter2.internal.protocol.ICRequest; +import org.openhab.binding.intellicenter2.internal.protocol.RequestObject; +import org.openhab.binding.intellicenter2.internal.protocol.ResponseObject; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class SystemInfo extends ResponseModel { + + private static final List REQUEST_ATTRIBUTES = List.of(MODE, VER, PROPNAME, SNAME); + public static final ICRequest REQUEST = ICRequest.getParamList("OBJTYP=SYSTEM", + new RequestObject("INCR", REQUEST_ATTRIBUTES)); + + SystemInfo() { + super(REQUEST_ATTRIBUTES); + } + + public SystemInfo(ResponseObject response) { + super(REQUEST_ATTRIBUTES, response); + } + + public String getPropertyName() { + return getValueAsString(PROPNAME); + } + + public String getMode() { + return getValueAsString(MODE); + } + + public boolean isMetricSystem() { + return "METRIC".equals(getMode()); + } + + public String getVersion() { + return getValueAsString(VER); + } + + public String getIntellicenterVersion() { + return getVersion().split(",")[0].split(":")[1].trim(); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/package-info.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/package-info.java new file mode 100644 index 0000000000000..727f853ccc8a3 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/model/package-info.java @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Attribute.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Attribute.java new file mode 100644 index 0000000000000..ba0b3501f6783 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Attribute.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public enum Attribute { + + ACT, + ACT1, + ACT2, + ACT3, + ACT4, + BADGE, + BODY, + BOOST, + CALIB, + CIRCGRP, + CIRCUIT, + CIRCUITS, + COMUART, + COVER, + DLY, + DNTSTP, + ENABLE, + FEATR, + FILTER, + FREEZE, + GPM, + HEATER, + HITMP, + HNAME, + HTMODE, + HTSRC, + LIMIT, + LISTORD, + LOTMP, + LSTTMP, + MANHT, + MAX, + MAXF, + MIN, + MINF, + MODE, + MODULE, + OBJECT_NAME, + OBJLIST, + OBJNAM, + OBJTYP, + PANID, + PARENT, + PORT, + PRIM, + PRIMFLO, + PRIMTIM, + PRIOR, + PROBE, + PROPNAME, + PUMP, + PWR, + RLY, + RPM, + SEC, + SELECT, + SENSE, + SERVICE, + SETTMP, + SETTMPNC, + SHARE, + SHOMNU, + SNAME, + SOURCE, + SPEED, + STATUS, + SYSTIM, + SUBTYP, + TEMP, + TIME, + TIMZON, + USE, + USAGE, + VER, + VOL; + +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Command.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Command.java new file mode 100644 index 0000000000000..57c9c0f55abaa --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/Command.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public enum Command { + + // get values + GET_PARAM_LIST("GetParamList"), + // set values + SET_PARAM_LIST("SetParamList"), + GET_QUERY("GetQuery"), + // responses for subscriptions + NOTIFY_LIST("NotifyList"), + // request a subscription + REQUEST_PARAM_LIST("RequestParamList"), + // unsubscribe + RELEASE_PARAM_LIST("ReleaseParamList"); + + private final String command; + + Command(String command) { + this.command = command; + } + + @Override + public String toString() { + return command; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocol.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocol.java new file mode 100644 index 0000000000000..125b821b89e66 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocol.java @@ -0,0 +1,300 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import static com.google.common.util.concurrent.Futures.getUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.openhab.binding.intellicenter2.internal.protocol.Command.NOTIFY_LIST; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.intellicenter2.internal.IntelliCenter2Configuration; +import org.openhab.binding.intellicenter2.internal.model.SystemInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Represents how to communicate with the IntelliCenter control. + * + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class ICProtocol implements AutoCloseable { + + public final static Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + + private final Logger logger = LoggerFactory.getLogger(ICProtocol.class); + + private final IntelliCenter2Configuration config; + private final SystemInfo systemInfo; + + @Nullable + private ExecutorService writerService; + @Nullable + private ExecutorService readerService; + @Nullable + private ICRead readRunnable; + + private final AtomicReference<@Nullable Socket> clientSocket = new AtomicReference<>(); + private final AtomicReference<@Nullable BufferedOutputStream> out = new AtomicReference<>(); + private final AtomicReference<@Nullable BufferedReader> in = new AtomicReference<>(); + + private final ConcurrentMap requests = new ConcurrentHashMap<>(2); + private final ConcurrentMap> responses = new ConcurrentHashMap<>(2); + private final ConcurrentHashMap> subscriptions = new ConcurrentHashMap<>(); + private final ExecutorService listenersNotifier; + + public ICProtocol(IntelliCenter2Configuration config) throws IOException { + this.config = config; + this.listenersNotifier = newSingleThreadExecutor( + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ic-protocol-listeners-%s").build()); + connect(); + this.systemInfo = querySystemInfo(); + } + + private void connect() throws IOException { + final Socket socket = new Socket(config.hostname, config.port); + socket.setKeepAlive(true); + this.clientSocket.set(socket); + this.out.set(new BufferedOutputStream(new DataOutputStream(socket.getOutputStream()))); + this.in.set(new BufferedReader(new InputStreamReader(socket.getInputStream()))); + this.writerService = newSingleThreadExecutor( + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ic-protocol-writer-%s").build()); + var rs = newSingleThreadExecutor( + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ic-protocol-reader-%s").build()); + this.readerService = rs; + + this.readRunnable = new ICRead(); + rs.submit(readRunnable); + } + + private void reconnect() throws IOException { + close(); + connect(); + } + + @Override + public void close() throws IOException { + var rr = readRunnable; + if (rr != null) { + rr.stop = true; + } + safeClose(out.get()); + safeClose(in.get()); + safeClose(clientSocket.get()); + try { + final var ws = writerService; + if (ws != null) { + ws.shutdownNow(); + } + final var rs = readerService; + if (rs != null) { + rs.shutdownNow(); + } + } catch (Exception ignored) { + } + } + + private static void safeClose(@Nullable Closeable c) { + try { + if (c != null) { + c.close(); + } + } catch (IOException ignored) { + } + } + + public SystemInfo getSystemInfo() { + return systemInfo; + } + + @SuppressWarnings("null") + private SystemInfo querySystemInfo() { + return new SystemInfo(getUnchecked(submit(SystemInfo.REQUEST)).getObjectList().get(0)); + } + + @SuppressWarnings("null") + public synchronized void subscribe(NotifyListListener listener, RequestObject request) { + subscriptions.putIfAbsent(request.getObjectName(), new CopyOnWriteArrayList<>()); + subscriptions.get(request.getObjectName()).add(listener); + String response = getUnchecked(submit(ICRequest.requestParamList(request))).getResponse(); + if (!"200".equals(response)) { + logger.warn("Error subscribing listener {} for request {}", listener, request); + } + } + + public synchronized void unsubscribe(NotifyListListener listener) { + final List keysToRemove = new ArrayList<>(1); + subscriptions.forEach((k, v) -> { + v.remove(listener); + if (v.isEmpty()) { + submit(ICRequest.releaseParamList(k)); + keysToRemove.add(k); + } + }); + keysToRemove.forEach(subscriptions::remove); + } + + @SuppressWarnings("null") + private void notifyListeners(final ICResponse response) { + final var runnable = new Runnable() { + + @Override + public void run() { + response.getObjectList().forEach(r -> { + var listeners = subscriptions.get(r.getObjectName()); + if (listeners != null) { + listeners.forEach(l -> l.onNotifyList(r)); + } + }); + } + }; + listenersNotifier.submit(runnable); + } + + /** + * Submits a request to IntelliCenter2. Requests are single threaded to IntelliCenter2 to avoid overtaxing it. + * + * @param request + * + * @return a Future that will contain the eventual ICResponse + */ + public Future submit(ICRequest request) { + var messageID = request.getMessageID(); + requests.put(messageID, request); + final SettableFuture future = SettableFuture.create(); + responses.put(messageID, future); + var ws = writerService; + if (ws == null) { + throw new IllegalStateException("Null writerService."); + } + ws.submit(new ICWrite(request)); + return future; + } + + private class ICWrite implements Runnable { + + private final ICRequest request; + + private ICWrite(ICRequest request) { + this.request = request; + } + + @Override + public void run() { + var jsonRequest = GSON.toJson(request); + try { + logger.trace("Sending request {}", jsonRequest); + write(jsonRequest); + } catch (IOException e) { + throw new RuntimeException("Error writing request", e); + } + } + + /** + * Write out bytes, handling reconnecting to the server if we get a SocketException + * + * @param jsonRequest + * @throws IOException + */ + private void write(String jsonRequest) throws IOException { + var bytes = jsonRequest.getBytes(UTF_8); + try { + writeBytes(bytes); + } catch (SocketException e) { + reconnect(); + writeBytes(bytes); + } + } + + @SuppressWarnings("null") + private void writeBytes(byte[] bytes) throws IOException { + out.get().write(bytes); + out.get().flush(); + } + } + + private class ICRead implements Runnable { + + public volatile boolean stop = false; + + @Override + public void run() { + try { + while (!stop) { + var response = readNext(); + if (response == null) { + continue; + } + var future = responses.remove(response.getMessageID()); + // if you use the iphone app to set a temperature, the existing iphone app doesn't appear to read + // back the response, + // so the response shows up for the next client read, which we want to ignore, unless it's a + // "NotifyList" response + // which is a notification that something changed based on a subscription. + if (future == null) { + if (NOTIFY_LIST.toString().equals(response.getCommand())) { + notifyListeners(response); + } else { + logger.debug("Got a non-NotifyList response without a corresponding messageID {}", + response); + } + } else { + future.set(response); + } + } + } catch (Exception e) { + if (!stop) { + logger.warn("Error reading from socket", e); + } + } + } + + @Nullable + private ICResponse readNext() throws IOException { + var reader = in.get(); + if (reader == null) { + return null; + } + var jsonResponse = reader.readLine(); + logger.trace("Received response {}", jsonResponse); + if (jsonResponse == null) { + return null; + } + return GSON.fromJson(jsonResponse, ICResponse.class); + } + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICRequest.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICRequest.java new file mode 100644 index 0000000000000..55880fe501db6 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICRequest.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import static org.openhab.binding.intellicenter2.internal.protocol.Command.GET_QUERY; +import static org.openhab.binding.intellicenter2.internal.protocol.Command.RELEASE_PARAM_LIST; + +import java.util.List; +import java.util.UUID; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Valdis Rigdon - Initial contribution + */ +public class ICRequest { + + private final String command; + private @Nullable final String condition; + + @SuppressWarnings("unused") + private @Nullable final String queryName; + @SuppressWarnings("unused") + private @Nullable final String arguments; + + private @Nullable List objectList; + private final String messageID; + + public static final ICRequest getQuery(String queryName) { + return getQuery(queryName, ""); + } + + public static final ICRequest getQuery(String queryName, String arguments) { + return new ICRequest(GET_QUERY, null, queryName, arguments); + } + + public static final ICRequest getParamList(@Nullable String condition, RequestObject... objects) { + return new ICRequest(Command.GET_PARAM_LIST, condition, null, null, objects); + } + + public static final ICRequest requestParamList(RequestObject... objects) { + return new ICRequest(Command.REQUEST_PARAM_LIST, null, null, null, objects); + } + + public static final ICRequest setParamList(RequestObject... objects) { + return new ICRequest(Command.SET_PARAM_LIST, null, null, null, objects); + } + + public ICRequest(Command command, RequestObject... objects) { + this(command, null, null, null, objects); + } + + private ICRequest(Command command, @Nullable String condition, @Nullable String queryName, + @Nullable String arguments, RequestObject... objects) { + this.command = command.toString(); + this.condition = condition; + this.queryName = queryName; + this.arguments = arguments; + if (objects.length > 0) { + this.objectList = List.of(objects); + } + this.messageID = UUID.randomUUID().toString(); + } + + public static ICRequest releaseParamList(String objectName) { + return new ICRequest(RELEASE_PARAM_LIST, new RequestObject(objectName)); + } + + public String getCommand() { + return command; + } + + public String getCondition() { + return condition; + } + + public List getObjectList() { + return objectList; + } + + public String getMessageID() { + return messageID; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICResponse.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICResponse.java new file mode 100644 index 0000000000000..8ef63077c22b3 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ICResponse.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class ICResponse { + + private String messageID = ""; + private String command = ""; + private @Nullable String description; + private String response = ""; + + private @Nullable List objectList; + private @Nullable List answer; + + public ICResponse() { + } + + public String getMessageID() { + return messageID; + } + + public String getCommand() { + return command; + } + + public List getObjectList() { + if (objectList == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(objectList); + } + + @Nullable + public String getDescription() { + return description; + } + + public List getAnswer() { + if (answer == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(answer); + } + + /** + * HTTP response code for the response. + * + * @return + */ + public String getResponse() { + return response; + } + + @Override + public String toString() { + return ICProtocol.GSON.toJson(this); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/NotifyListListener.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/NotifyListListener.java new file mode 100644 index 0000000000000..0433f4071c56f --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/NotifyListListener.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * Listener for receiving responses from a IntelliCenter subscription. + * + * @see org.openhab.binding.intellicenter2.internal.protocol.Command + * + * @author Valdis Rigdon - initial contribution + */ +public interface NotifyListListener { + + /** + * The response to an IntelliCenter subscription. + * + * @param response the decoded response + */ + void onNotifyList(@NonNull ResponseObject response); +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/RequestObject.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/RequestObject.java new file mode 100644 index 0000000000000..b14bfbf19f63d --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/RequestObject.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import static java.util.Objects.requireNonNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class RequestObject { + + @SerializedName("objnam") + private final String objectName; + @SuppressWarnings("unused") + @Nullable + private final Collection keys; + @SuppressWarnings("unused") + @Nullable + private final Map params; + + public RequestObject(final String objname, final Collection keys) { + this.objectName = requireNonNull(objname); + this.keys = Collections.unmodifiableCollection(keys); + this.params = null; + } + + public RequestObject(final String objname, final Attribute... keys) { + this(objname, List.of(keys)); + } + + public RequestObject(final String objectName, Map params) { + this.objectName = objectName; + this.params = new HashMap<>(params); + this.keys = null; + } + + @SerializedName("objnam") + public String getObjectName() { + return objectName; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ResponseObject.java b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ResponseObject.java new file mode 100644 index 0000000000000..9ae4e2bb469c5 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/java/org/openhab/binding/intellicenter2/internal/protocol/ResponseObject.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class ResponseObject { + + @SerializedName("objnam") + private String objectName = ""; + private Map params = Map.of(); + + protected ResponseObject() { + } + + private ResponseObject(Map m) { + this.objectName = requireNonNull((String) m.get("objnam")); + @SuppressWarnings("unchecked") + var inputParams = (Map) m.get("params"); + this.params = new HashMap<>(); + if (inputParams != null) { + inputParams.forEach((k, v) -> { + try { + params.put(Attribute.valueOf(k), v); + } catch (IllegalArgumentException e) { + } + }); + } + } + + public ResponseObject(@Nullable ResponseObject response) { + if (response != null) { + this.objectName = response.getObjectName(); + this.params = response.getParams(); + } + } + + public String getObjectName() { + return objectName; + } + + public Map getParams() { + return params; + } + + public String getValueAsString(Attribute key) { + return (String) Objects.requireNonNull(params.get(key)); + } + + public Optional getOptionalValueAsString(Attribute key) { + return Optional.ofNullable((String) params.get(key)); + } + + public int getValueAsInt(Attribute key) { + return Integer.parseInt(getValueAsString(key)); + } + + public boolean getValueAsBoolean(Attribute key) { + return "ON".equals(getValueAsString(key)); + } + + public List getValueAsResponseObjects(Attribute key) { + @SuppressWarnings("unchecked") + final List> list = (List>) params.get(key); + if (list != null) { + final var responses = new ArrayList(list.size()); + list.forEach(m -> { + responses.add(new ResponseObject(m)); + }); + return responses; + } + return Collections.emptyList(); + } + + public <@NonNull T extends Enum> T getValueAsEnum(Attribute a, Class enumClass) { + final Object value = requireNonNull(params.get(a)); + return Objects.requireNonNull(ICProtocol.GSON.fromJson(value.toString(), enumClass)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{objectName=" + objectName + "}"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + ResponseObject that = (ResponseObject) o; + return this.objectName.equals(that.getObjectName()); + } + + @Override + public int hashCode() { + return objectName.hashCode(); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..2550718354f62 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + + binding + Intellicenter2 + Binding for the Pentair IntelliCenter2 + hybrid + + diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..03a32ee560cb2 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + IntelliCenter2 Binding + This is the binding for IntelliCenter2. + + diff --git a/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..e7743626664aa --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,188 @@ + + + + + + Pentair IntelliCenter 2 Bridge + + Pentair + + + + + network-address + + Hostname or IP address of the device + + + + Port of the device, defaults to 6681 + + + + + + + + + + + Pool for IntelliCenter2 Binding + + + + + The current pool temperature. + + + + The target pool temperature. + + + + On if the heater is running, off otherwise. + + + + SNAME + + + + + + + + + + Feature for IntelliCenter2 Binding + + + + + Feature On/Off + + + + OBJNAM + + + + + + + + + + Light for IntelliCenter2 Binding + + + + + + + OBJNAM + + + + + + + + + + Pump for IntelliCenter2 Binding + + + + + + + + OBJNAM + + + + + + + + + + Sensor for IntelliCenter2 Binding + + + + + + + + OBJNAM + + + + + Number:Temperature + + The temperature value. + + + + + Number:Temperature + + The current temperature value. + + + + + Number:Temperature + + The target temperature value. + + + + + Switch + + ON if the pool heater is running. + + + + + Switch + + Feature On/Off + + + + Number + + Pump Flow Rate (GPM) + + + + + Number + + Pump Speed (RPM) + + + + + Number:Power + + Pump Power Usage in Watts + + + + + Number + + Temperature calibration + + + + diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryServiceTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryServiceTest.java new file mode 100644 index 0000000000000..7edac1add9bfe --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/IntelliCenter2DiscoveryServiceTest.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.discovery; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.openhab.binding.intellicenter2.internal.IntelliCenter2Configuration; +import org.openhab.binding.intellicenter2.internal.handler.IntelliCenter2BridgeHandler; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.internal.BridgeImpl; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +@Disabled +public class IntelliCenter2DiscoveryServiceTest { + + @Test + @Timeout(30) + public void testDiscovery() throws Exception { + var bridge = new BridgeImpl(new ThingTypeUID("intellicenter2:bridge"), getClass().getSimpleName()); + var config = new IntelliCenter2Configuration(); + config.hostname = "192.168.1.148"; + var bridgeHandler = new IntelliCenter2BridgeHandler(bridge, config); + bridgeHandler.initialize(); + + var listener = new SimpleDiscoveryListener(); + var discovery = new IntelliCenter2DiscoveryService(); + discovery.setThingHandler(bridgeHandler); + discovery.addDiscoveryListener(listener); + // wait for the protocol to be ready + bridgeHandler.getProtocol(); + discovery.startScan(); + + // 2 pools and 4 features, 1 light, 2 pumps, 3 sensors (that are the same) + assertEquals(12, listener.discoveredResults.size()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/SimpleDiscoveryListener.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/SimpleDiscoveryListener.java new file mode 100644 index 0000000000000..3c6fbce4c206b --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/discovery/SimpleDiscoveryListener.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.discovery; + +import java.util.Collection; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.discovery.DiscoveryListener; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class SimpleDiscoveryListener implements DiscoveryListener { + + public Queue discoveredResults = new ConcurrentLinkedQueue<>(); + + @Override + public void thingRemoved(DiscoveryService source, ThingUID thingUID) { + } + + @Override + public void thingDiscovered(DiscoveryService source, DiscoveryResult result) { + discoveredResults.add(result); + } + + @Override + public @Nullable Collection removeOlderResults(DiscoveryService source, long timestamp, + @Nullable Collection thingTypeUIDs, @Nullable ThingUID bridgeUID) { + return null; + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/FeatureTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/FeatureTest.java new file mode 100644 index 0000000000000..294019bb66767 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/FeatureTest.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.openhab.binding.intellicenter2.internal.protocol.ICProtocol.GSON; + +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class FeatureTest { + + public FeatureTest() { + } + + @Test + public void testFeatureDeserialization() { + + String jsonResponse = "{\"command\":\"SendParamList\",\"messageID\":\"ffc6dca0-1d94-454d-84ba-decc5e1ec067\",\"response\":\"200\",\"objectList\":[{\"objnam\":\"FTR01\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"CIRCGRP\":\"CIRCGRP\",\"FEATR\":\"ON\",\"STATUS\":\"OFF\",\"SNAME\":\"Jets\",\"LISTORD\":\"1\"}},{\"objnam\":\"C0004\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"INTELLI\",\"CIRCGRP\":\"CIRCGRP\",\"FEATR\":\"ON\",\"STATUS\":\"OFF\",\"SNAME\":\"Spa Light\",\"LISTORD\":\"4\"}},{\"objnam\":\"C0002\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"CIRCGRP\":\"CIRCGRP\",\"FEATR\":\"ON\",\"STATUS\":\"OFF\",\"SNAME\":\"Air Blower\",\"LISTORD\":\"2\"}},{\"objnam\":\"C0003\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"CIRCGRP\":\"CIRCGRP\",\"FEATR\":\"ON\",\"STATUS\":\"OFF\",\"SNAME\":\"Ultraviolet\",\"LISTORD\":\"3\"}}]}\n"; + + var response = GSON.fromJson(jsonResponse, ICResponse.class); + SortedSet features = response.getObjectList().stream().map(Circuit::new) + .collect(Collectors.toCollection(TreeSet::new)); + + assertEquals(4, features.size()); + + var array = features.toArray(new Circuit[0]); + assertEquals("Jets", array[0].getSname()); + assertEquals("Air Blower", array[1].getSname()); + assertEquals("Ultraviolet", array[2].getSname()); + assertEquals("Spa Light", array[3].getSname()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinitionTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinitionTest.java new file mode 100644 index 0000000000000..96441eb56d762 --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/GetHardwareDefinitionTest.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openhab.binding.intellicenter2.internal.protocol.ICProtocol.GSON; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class GetHardwareDefinitionTest { + + private static final String GET_HARDWARE_DEFINITION_RESPONSE_NO_ARGS = "{\"command\":\"SendQuery\",\"messageID\":\"88a4331a-ccdb-4ab3-b940-0566244c465f\",\"queryName\":\"GetHardwareDefinition\",\"description\":\"Created on: 2000-01-20 00:14:31\",\"response\":\"200\",\"answer\":[{\"objnam\":\"PNL01\",\"params\":{\"OBJTYP\":\"PANEL\",\"SUBTYP\":\"OCP\",\"HNAME\":\"PNL01\",\"SNAME\":\"Panel 1\",\"PANID\":\"SHARE\",\"LISTORD\":\"1\",\"VER\":\"VER\",\"OBJLIST\":[{\"objnam\":\"M0101\",\"params\":{\"OBJTYP\":\"MODULE\",\"SUBTYP\":\"I5P\",\"SNAME\":\"M0101\",\"LISTORD\":\"LISTORD\",\"PARENT\":\"PNL01\",\"PORT\":\"1\",\"VER\":\"10.001\",\"BADGE\":\"BADGE\",\"CIRCUITS\":[{\"objnam\":\"B1101\",\"params\":{\"OBJTYP\":\"BODY\",\"SUBTYP\":\"POOL\",\"SNAME\":\"Spa\",\"LISTORD\":\"1\",\"HITMP\":\"100\",\"LOTMP\":\"80\",\"HTSRC\":\"H0001\",\"SHARE\":\"B1202\",\"PRIM\":\"65535\",\"SEC\":\"65535\",\"ACT1\":\"65535\",\"ACT2\":\"65535\",\"ACT3\":\"65535\",\"ACT4\":\"65535\",\"VOL\":\"3000\",\"MANHT\":\"00000\",\"MODE\":\"2\"}},{\"objnam\":\"GRP01\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"CIRCGRP\",\"HNAME\":\"GRP01\",\"SNAME\":\"UV Group\",\"COVER\":\"OFF\",\"LISTORD\":\"1\",\"PARENT\":\"00000\",\"BODY\":\"00000\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}}]}},{\"objnam\":\"M0102\",\"params\":{\"OBJTYP\":\"MODULE\",\"SUBTYP\":\"I5PS\",\"SNAME\":\"M0102\",\"LISTORD\":\"LISTORD\",\"PARENT\":\"PNL01\",\"PORT\":\"1\",\"VER\":\"10.001\",\"BADGE\":\"BADGE\",\"CIRCUITS\":[{\"objnam\":\"B1202\",\"params\":{\"OBJTYP\":\"BODY\",\"SUBTYP\":\"SPA\",\"SNAME\":\"Pool\",\"LISTORD\":\"2\",\"HITMP\":\"103\",\"LOTMP\":\"100\",\"HTSRC\":\"00000\",\"SHARE\":\"B1101\",\"PRIM\":\"65535\",\"SEC\":\"65535\",\"ACT1\":\"65535\",\"ACT2\":\"65535\",\"ACT3\":\"65535\",\"ACT4\":\"65535\",\"VOL\":\"1000\",\"MANHT\":\"00000\",\"MODE\":\"1\"}},{\"objnam\":\"GRP01\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"CIRCGRP\",\"HNAME\":\"GRP01\",\"SNAME\":\"UV Group\",\"COVER\":\"OFF\",\"LISTORD\":\"1\",\"PARENT\":\"00000\",\"BODY\":\"00000\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}}]}}]}}]}"; + private static final String GET_HARDWARE_DEFINITION_RESPONSE_CIRCUITS = "{\"command\":\"SendQuery\",\"messageID\":\"276A3DAE-126D-4714-A793-1759633222E1\",\"queryName\":\"GetHardwareDefinition\",\"description\":\"Created on: 2000-02-01 05:50:04\",\"response\":\"200\",\"answer\":[{\"objnam\":\"PNL01\",\"params\":{\"OBJTYP\":\"PANEL\",\"SUBTYP\":\"OCP\",\"HNAME\":\"PNL01\",\"SNAME\":\"Panel 1\",\"PANID\":\"SHARE\",\"LISTORD\":\"1\",\"VER\":\"VER\",\"OBJLIST\":[{\"objnam\":\"M0101\",\"params\":{\"OBJTYP\":\"MODULE\",\"SUBTYP\":\"I5P\",\"SNAME\":\"M0101\",\"LISTORD\":\"LISTORD\",\"PARENT\":\"PNL01\",\"PORT\":\"1\",\"VER\":\"10.001\",\"BADGE\":\"BADGE\",\"CIRCUITS\":[{\"objnam\":\"B1101\",\"params\":{\"OBJTYP\":\"BODY\",\"SUBTYP\":\"POOL\",\"SNAME\":\"Spa\",\"LISTORD\":\"1\",\"HITMP\":\"100\",\"LOTMP\":\"80\",\"HTSRC\":\"H0001\",\"SHARE\":\"B1202\",\"PRIM\":\"65535\",\"SEC\":\"65535\",\"ACT1\":\"65535\",\"ACT2\":\"65535\",\"ACT3\":\"65535\",\"ACT4\":\"65535\",\"VOL\":\"3000\",\"MANHT\":\"00000\",\"MODE\":\"2\"}},{\"objnam\":\"C0002\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"HNAME\":\"AUX 1\",\"SNAME\":\"Air Blower\",\"LISTORD\":\"2\",\"PARENT\":\"M0101\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"ON\"}},{\"objnam\":\"C0003\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"HNAME\":\"AUX 2\",\"SNAME\":\"Ultraviolet\",\"LISTORD\":\"3\",\"PARENT\":\"M0101\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"ON\"}},{\"objnam\":\"C0004\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"INTELLI\",\"HNAME\":\"AUX 3\",\"SNAME\":\"Spa Light\",\"LISTORD\":\"4\",\"PARENT\":\"M0101\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"ON\"}},{\"objnam\":\"C0005\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"GENERIC\",\"HNAME\":\"AUX 4\",\"SNAME\":\"AUX 4\",\"LISTORD\":\"5\",\"PARENT\":\"M0101\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}},{\"objnam\":\"C0006\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"POOL\",\"HNAME\":\"Pool\",\"SNAME\":\"Spa\",\"LISTORD\":\"6\",\"PARENT\":\"M0101\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}},{\"objnam\":\"GRP01\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"CIRCGRP\",\"HNAME\":\"GRP01\",\"SNAME\":\"UV Group\",\"COVER\":\"OFF\",\"LISTORD\":\"1\",\"PARENT\":\"00000\",\"BODY\":\"00000\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}}]}},{\"objnam\":\"M0102\",\"params\":{\"OBJTYP\":\"MODULE\",\"SUBTYP\":\"I5PS\",\"SNAME\":\"M0102\",\"LISTORD\":\"LISTORD\",\"PARENT\":\"PNL01\",\"PORT\":\"1\",\"VER\":\"10.001\",\"BADGE\":\"BADGE\",\"CIRCUITS\":[{\"objnam\":\"B1202\",\"params\":{\"OBJTYP\":\"BODY\",\"SUBTYP\":\"SPA\",\"SNAME\":\"Pool\",\"LISTORD\":\"2\",\"HITMP\":\"103\",\"LOTMP\":\"100\",\"HTSRC\":\"00000\",\"SHARE\":\"B1101\",\"PRIM\":\"65535\",\"SEC\":\"65535\",\"ACT1\":\"65535\",\"ACT2\":\"65535\",\"ACT3\":\"65535\",\"ACT4\":\"65535\",\"VOL\":\"1000\",\"MANHT\":\"00000\",\"MODE\":\"1\"}},{\"objnam\":\"C0001\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"SPA\",\"HNAME\":\"Spa\",\"SNAME\":\"Pool\",\"LISTORD\":\"1\",\"PARENT\":\"M0102\",\"BODY\":\"BODY\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}},{\"objnam\":\"GRP01\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"CIRCGRP\",\"HNAME\":\"GRP01\",\"SNAME\":\"UV Group\",\"COVER\":\"OFF\",\"LISTORD\":\"1\",\"PARENT\":\"00000\",\"BODY\":\"00000\",\"FREEZE\":\"OFF\",\"VER\":\"VER\",\"TIMZON\":\"TIMZON\",\"TIME\":\"720\",\"RLY\":\"RLY\",\"DNTSTP\":\"OFF\",\"FEATR\":\"OFF\"}}]}}]}}]}\n"; + + public GetHardwareDefinitionTest() { + } + + @Test + public void testGetHardwareDefinitionNoArguments() throws Exception { + var response = GSON.fromJson(GET_HARDWARE_DEFINITION_RESPONSE_NO_ARGS, ICResponse.class); + assertNotNull(response); + var defn = new GetHardwareDefinition(response); + + var bodies = defn.getPanels().get(0).getBodies(); + assertEquals(2, bodies.size()); + + var names = bodies.stream().map(Body::getObjectName).collect(toSet()); + + assertTrue(names.contains("B1101")); + assertTrue(names.contains("B1202")); + } + + @Test + public void testGetHardwareDefinitionCircuits() throws Exception { + var response = GSON.fromJson(GET_HARDWARE_DEFINITION_RESPONSE_CIRCUITS, ICResponse.class); + assertNotNull(response); + var defn = new GetHardwareDefinition(response); + + var circuits = defn.getPanels().get(0).getCircuits(); + assertEquals(8, circuits.size()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/IntelliBriteTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/IntelliBriteTest.java new file mode 100644 index 0000000000000..8728646e2d3ed --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/IntelliBriteTest.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.openhab.binding.intellicenter2.internal.protocol.ICProtocol.GSON; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class IntelliBriteTest { + + @Test + public void testResponseRed() { + String jsonResponse = "{\"command\":\"SendParamList\",\"messageID\":\"ffc6dca0-1d94-454d-84ba-decc5e1ec067\",\"response\":\"200\",\"objectList\":[{\"objnam\":\"C0004\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"INTELLI\",\"STATUS\":\"ON\",\"SNAME\":\"Spa Light\",\"ACT\":\"65535\",\"USE\":\"REDR\"}}]}\n"; + var response = GSON.fromJson(jsonResponse, ICResponse.class); + var light = new IntelliBrite(response.getObjectList().get(0)); + + assertEquals(light.getColor(), IntelliBrite.Color.RED); + } + + @Test + public void testResponseMagenta() { + String jsonResponse = "{\"command\":\"SendParamList\",\"messageID\":\"ffc6dca0-1d94-454d-84ba-decc5e1ec067\",\"response\":\"200\",\"objectList\":[{\"objnam\":\"C0004\",\"params\":{\"OBJTYP\":\"CIRCUIT\",\"SUBTYP\":\"INTELLI\",\"STATUS\":\"ON\",\"SNAME\":\"Spa Light\",\"ACT\":\"65535\",\"USE\":\"MAGNTAR\"}}]}\n"; + var response = GSON.fromJson(jsonResponse, ICResponse.class); + var light = new IntelliBrite(response.getObjectList().get(0)); + + assertEquals(light.getColor(), IntelliBrite.Color.MAGENTA); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/SystemInfoTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/SystemInfoTest.java new file mode 100644 index 0000000000000..4ff5964a3ff6e --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/model/SystemInfoTest.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.openhab.binding.intellicenter2.internal.protocol.ICProtocol.GSON; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.intellicenter2.internal.protocol.ICResponse; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +public class SystemInfoTest { + + @Test + public void testResponse() { + String jsonResponse = "{\"command\":\"SendParamList\",\"messageID\":\"0c0a089b-41df-47da-a950-09aa72b264fe\",\"response\":\"200\",\"objectList\":[{\"objnam\":\"_5451\",\"params\":{\"MODE\":\"ENGLISH\",\"VER\":\"IC: 1.064 , ICWEB:2021-10-19 1.007\",\"PROPNAME\":\"my property\",\"SNAME\":\"(TV_+ubDl;}>llXT>pd<*V<+twKK<|$5oEW@b$%E!U3mH6;tKjpBR..fv/.s}&_^\"}}]}\n"; + + var response = GSON.fromJson(jsonResponse, ICResponse.class); + var info = new SystemInfo(response.getObjectList().get(0)); + + assertEquals("1.064", info.getIntellicenterVersion()); + assertEquals("my property", info.getPropertyName()); + assertEquals("ENGLISH", info.getMode()); + } +} diff --git a/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocolTest.java b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocolTest.java new file mode 100644 index 0000000000000..70e161143993e --- /dev/null +++ b/bundles/org.openhab.binding.intellicenter2/src/test/java/org/openhab/binding/intellicenter2/internal/protocol/ICProtocolTest.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.intellicenter2.internal.protocol; + +import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openhab.binding.intellicenter2.internal.IntelliCenter2Configuration; +import org.openhab.binding.intellicenter2.internal.model.Body; +import org.openhab.binding.intellicenter2.internal.model.GetHardwareDefinition; + +/** + * @author Valdis Rigdon - Initial contribution + */ +@NonNullByDefault +@Disabled +public class ICProtocolTest { + + @Nullable + private static ICProtocol protocol; + + @BeforeAll + public static void setup() throws Exception { + var config = new IntelliCenter2Configuration(); + config.hostname = "192.168.1.148"; + protocol = new ICProtocol(config); + } + + @Test + public void testGetHardwareDefinition() throws Exception { + var response = protocol.submit(GetHardwareDefinition.Argument.DEFAULT.getRequest()).get(); + var defn = new GetHardwareDefinition(response); + + var bodies = defn.getPanels().get(0).getBodies(); + assertEquals(2, bodies.size()); + + var names = bodies.stream().map(Body::getObjectName).collect(toSet()); + + assertTrue(names.contains("B1101")); + assertTrue(names.contains("B1202")); + } + + @Test + @Disabled + public void testNotifyListener() throws Exception { + final List responses = new ArrayList<>(); + final var listener = new NotifyListListener() { + @Override + public void onNotifyList(ResponseObject response) { + System.err.println(response); + responses.add(response); + } + }; + try { + protocol.subscribe(listener, new RequestObject("C0003", Attribute.STATUS, Attribute.MODE)); + protocol.subscribe(listener, new RequestObject("C0004", Attribute.STATUS, Attribute.ACT)); + + Thread.sleep(10000); + + } finally { + protocol.unsubscribe(listener); + } + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index f8e41e838d9a3..004278a37056e 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -204,6 +204,7 @@ org.openhab.binding.icloud org.openhab.binding.ihc org.openhab.binding.insteon + org.openhab.binding.intellicenter2 org.openhab.binding.ipcamera org.openhab.binding.ipobserver org.openhab.binding.intesis