2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mcp23017.internal.handler;
15 import static org.openhab.binding.mcp23017.internal.Mcp23017BindingConstants.*;
17 import java.io.IOException;
18 import java.util.Objects;
20 import org.openhab.binding.mcp23017.internal.GPIODataHolder;
21 import org.openhab.binding.mcp23017.internal.PinMapper;
22 import org.openhab.core.config.core.Configuration;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.library.types.OpenClosedType;
25 import org.openhab.core.thing.ChannelUID;
26 import org.openhab.core.thing.Thing;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.openhab.core.thing.binding.BaseThingHandler;
30 import org.openhab.core.types.Command;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.pi4j.gpio.extension.mcp.MCP23017GpioProvider;
35 import com.pi4j.io.gpio.GpioPin;
36 import com.pi4j.io.gpio.GpioPinDigitalInput;
37 import com.pi4j.io.gpio.GpioPinDigitalOutput;
38 import com.pi4j.io.gpio.Pin;
39 import com.pi4j.io.gpio.PinPullResistance;
40 import com.pi4j.io.gpio.PinState;
41 import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
42 import com.pi4j.io.gpio.event.GpioPinListenerDigital;
43 import com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException;
46 * The {@link Mcp23017Handler} is base class for MCP23017 chip support
48 * @author Anatol Ogorek - Initial contribution
50 public class Mcp23017Handler extends BaseThingHandler implements GpioPinListenerDigital {
52 private final Logger logger = LoggerFactory.getLogger(getClass());
54 private MCP23017GpioProvider mcpProvider;
55 private Integer address;
56 private Integer busNumber;
57 private Mcp23017PinStateHolder pinStateHolder;
59 * the polling interval mcp23071 check interrupt register (optional, defaults to 50ms)
61 private static final int POLLING_INTERVAL = 50;
63 public Mcp23017Handler(Thing thing) {
68 public void handleCommand(ChannelUID channelUID, Command command) {
69 logger.debug("Received command: {} on channelGroup {} on channel {}", command.toFullString(),
70 channelUID.getGroupId(), channelUID.getIdWithoutGroup());
72 if (!verifyChannel(channelUID)) {
76 String channelGroup = channelUID.getGroupId();
78 switch (channelGroup) {
79 case CHANNEL_GROUP_INPUT:
80 handleInputCommand(channelUID, command);
82 case CHANNEL_GROUP_OUTPUT:
83 handleOutputCommand(channelUID, command);
90 public void initialize() {
93 mcpProvider = initializeMcpProvider();
94 pinStateHolder = new Mcp23017PinStateHolder(mcpProvider, this.thing);
95 updateStatus(ThingStatus.ONLINE);
96 } catch (IllegalArgumentException | SecurityException e) {
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
98 "An exception occurred while adding pin. Check pin configuration. Exception: " + e.getMessage());
102 private boolean verifyChannel(ChannelUID channelUID) {
103 if (!isChannelGroupValid(channelUID) || !isChannelValid(channelUID)) {
104 logger.warn("Channel group or channel is invalid. Probably configuration problem");
110 private void handleOutputCommand(ChannelUID channelUID, Command command) {
111 if (command instanceof OnOffType) {
112 GpioPinDigitalOutput outputPin = pinStateHolder.getOutputPin(channelUID);
113 Configuration configuration = this.getThing().getChannel(channelUID.getId()).getConfiguration();
115 // invertLogic is null if not configured
116 String activeLowStr = Objects.toString(configuration.get(ACTIVE_LOW), null);
117 boolean activeLowFlag = ACTIVE_LOW_ENABLED.equalsIgnoreCase(activeLowStr);
118 PinState pinState = command == OnOffType.ON ^ activeLowFlag ? PinState.HIGH : PinState.LOW;
119 logger.debug("got output pin {} for channel {} and command {} [active_low={}, new_state={}]", outputPin,
120 channelUID, command, activeLowFlag, pinState);
121 GPIODataHolder.GPIO.setState(pinState, outputPin);
125 private void handleInputCommand(ChannelUID channelUID, Command command) {
126 logger.debug("Nothing to be done in handleCommand for contact.");
129 private boolean isChannelGroupValid(ChannelUID channelUID) {
130 if (!channelUID.isInGroup()) {
131 logger.debug("Defined channel not in group: {}", channelUID.getAsString());
134 boolean channelGroupValid = SUPPORTED_CHANNEL_GROUPS.contains(channelUID.getGroupId());
135 logger.debug("Defined channel in group: {}. Valid: {}", channelUID.getGroupId(), channelGroupValid);
137 return channelGroupValid;
140 private boolean isChannelValid(ChannelUID channelUID) {
141 boolean channelValid = SUPPORTED_CHANNELS.contains(channelUID.getIdWithoutGroup());
142 logger.debug("Is channel {} in supported channels: {}", channelUID.getIdWithoutGroup(), channelValid);
146 protected void checkConfiguration() {
147 Configuration configuration = getConfig();
148 address = Integer.parseInt((configuration.get(ADDRESS)).toString(), 16);
149 busNumber = Integer.parseInt((configuration.get(BUS_NUMBER)).toString());
152 private MCP23017GpioProvider initializeMcpProvider() {
153 MCP23017GpioProvider mcp = null;
154 logger.debug("initializing mcp provider for busNumber {} and address {}", busNumber, address);
156 mcp = new MCP23017GpioProvider(busNumber, address);
157 mcp.setPollingTime(POLLING_INTERVAL);
158 } catch (UnsupportedBusNumberException | IOException ex) {
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
160 "Tried to access not available I2C bus: " + ex.getMessage());
162 logger.debug("got mcpProvider {}", mcp);
166 private GpioPinDigitalInput initializeInputPin(ChannelUID channel) {
167 logger.debug("initializing input pin for channel {}", channel.getAsString());
168 Pin pin = PinMapper.get(channel.getIdWithoutGroup());
170 String pullMode = DEFAULT_PULL_MODE;
171 if (thing.getChannel(channel.getId()) != null) {
172 Configuration configuration = thing.getChannel(channel.getId()).getConfiguration();
173 pullMode = ((String) configuration.get(PULL_MODE)) != null ? ((String) configuration.get(PULL_MODE))
176 logger.debug("initializing pin {}, pullMode {}, mcpProvider {}", pin, pullMode, mcpProvider);
177 GpioPinDigitalInput input = GPIODataHolder.GPIO.provisionDigitalInputPin(mcpProvider, pin,
178 channel.getIdWithoutGroup(), PinPullResistance.valueOf(pullMode));
179 input.addListener(this);
180 logger.debug("Bound digital input for PIN: {}, ItemName: {}, pullMode: {}", pin, channel.getAsString(),
186 public void dispose() {
187 final Mcp23017PinStateHolder holder = pinStateHolder;
189 if (holder != null) {
190 holder.unBindGpioPins();
197 public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
198 GpioPin pin = event.getPin();
199 OpenClosedType state = OpenClosedType.CLOSED;
200 if (event.getState() == PinState.LOW) {
201 state = OpenClosedType.OPEN;
203 ChannelUID channelForPin = pinStateHolder.getChannelForInputPin((GpioPinDigitalInput) pin);
204 logger.debug("updating channel {} with state {}", channelForPin, state);
205 updateState(channelForPin, state);
209 public void channelLinked(ChannelUID channelUID) {
210 synchronized (this) {
211 logger.debug("channel linked {}", channelUID.getAsString());
212 if (!verifyChannel(channelUID)) {
215 String channelGroup = channelUID.getGroupId();
217 if (channelGroup != null && channelGroup.equals(CHANNEL_GROUP_INPUT)) {
218 if (pinStateHolder.getInputPin(channelUID) != null) {
221 GpioPinDigitalInput inputPin = initializeInputPin(channelUID);
222 pinStateHolder.addInputPin(inputPin, channelUID);
225 super.channelLinked(channelUID);