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