]> git.basschouten.com Git - openhab-addons.git/blob
cad17fabde85167e6e903a9e60248ffbf3607751
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mcp23017.internal.handler;
14
15 import static org.openhab.binding.mcp23017.internal.Mcp23017BindingConstants.*;
16
17 import java.io.IOException;
18 import java.util.Objects;
19
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;
33
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;
44
45 /**
46  * The {@link Mcp23017Handler} is base class for MCP23017 chip support
47  *
48  * @author Anatol Ogorek - Initial contribution
49  */
50 public class Mcp23017Handler extends BaseThingHandler implements GpioPinListenerDigital {
51
52     private final Logger logger = LoggerFactory.getLogger(getClass());
53
54     private MCP23017GpioProvider mcpProvider;
55     private Integer address;
56     private Integer busNumber;
57     private Mcp23017PinStateHolder pinStateHolder;
58     /**
59      * the polling interval mcp23071 check interrupt register (optional, defaults to 50ms)
60      */
61     private static final int POLLING_INTERVAL = 50;
62
63     public Mcp23017Handler(Thing thing) {
64         super(thing);
65     }
66
67     @Override
68     public void handleCommand(ChannelUID channelUID, Command command) {
69         logger.debug("Received command: {} on channelGroup {} on channel {}", command.toFullString(),
70                 channelUID.getGroupId(), channelUID.getIdWithoutGroup());
71
72         if (!verifyChannel(channelUID)) {
73             return;
74         }
75
76         String channelGroup = channelUID.getGroupId();
77
78         switch (channelGroup) {
79             case CHANNEL_GROUP_INPUT:
80                 handleInputCommand(channelUID, command);
81                 break;
82             case CHANNEL_GROUP_OUTPUT:
83                 handleOutputCommand(channelUID, command);
84             default:
85                 break;
86         }
87     }
88
89     @Override
90     public void initialize() {
91         try {
92             checkConfiguration();
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());
99         }
100     }
101
102     private boolean verifyChannel(ChannelUID channelUID) {
103         if (!isChannelGroupValid(channelUID) || !isChannelValid(channelUID)) {
104             logger.warn("Channel group or channel is invalid. Probably configuration problem");
105             return false;
106         }
107         return true;
108     }
109
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();
114
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);
122         }
123     }
124
125     private void handleInputCommand(ChannelUID channelUID, Command command) {
126         logger.debug("Nothing to be done in handleCommand for contact.");
127     }
128
129     private boolean isChannelGroupValid(ChannelUID channelUID) {
130         if (!channelUID.isInGroup()) {
131             logger.debug("Defined channel not in group: {}", channelUID.getAsString());
132             return false;
133         }
134         boolean channelGroupValid = SUPPORTED_CHANNEL_GROUPS.contains(channelUID.getGroupId());
135         logger.debug("Defined channel in group: {}. Valid: {}", channelUID.getGroupId(), channelGroupValid);
136
137         return channelGroupValid;
138     }
139
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);
143         return channelValid;
144     }
145
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());
150     }
151
152     private MCP23017GpioProvider initializeMcpProvider() {
153         MCP23017GpioProvider mcp = null;
154         logger.debug("initializing mcp provider for busNumber {} and address {}", busNumber, address);
155         try {
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());
161         }
162         logger.debug("got mcpProvider {}", mcp);
163         return mcp;
164     }
165
166     private GpioPinDigitalInput initializeInputPin(ChannelUID channel) {
167         logger.debug("initializing input pin for channel {}", channel.getAsString());
168         Pin pin = PinMapper.get(channel.getIdWithoutGroup());
169
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))
174                     : DEFAULT_PULL_MODE;
175         }
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(),
181                 pullMode);
182         return input;
183     }
184
185     @Override
186     public void dispose() {
187         final Mcp23017PinStateHolder holder = pinStateHolder;
188
189         if (holder != null) {
190             holder.unBindGpioPins();
191         }
192
193         super.dispose();
194     }
195
196     @Override
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;
202         }
203         ChannelUID channelForPin = pinStateHolder.getChannelForInputPin((GpioPinDigitalInput) pin);
204         logger.debug("updating channel {} with state {}", channelForPin, state);
205         updateState(channelForPin, state);
206     }
207
208     @Override
209     public void channelLinked(ChannelUID channelUID) {
210         synchronized (this) {
211             logger.debug("channel linked {}", channelUID.getAsString());
212             if (!verifyChannel(channelUID)) {
213                 return;
214             }
215             String channelGroup = channelUID.getGroupId();
216
217             if (channelGroup != null && channelGroup.equals(CHANNEL_GROUP_INPUT)) {
218                 if (pinStateHolder.getInputPin(channelUID) != null) {
219                     return;
220                 }
221                 GpioPinDigitalInput inputPin = initializeInputPin(channelUID);
222                 pinStateHolder.addInputPin(inputPin, channelUID);
223
224             }
225             super.channelLinked(channelUID);
226         }
227     }
228 }