001package com.pi4j.io.gpio;
002
003import com.pi4j.io.gpio.event.PinListener;
004import com.pi4j.io.gpio.exception.InvalidPinException;
005import com.pi4j.io.gpio.exception.InvalidPinModeException;
006import com.pi4j.io.gpio.exception.UnsupportedPinModeException;
007import com.pi4j.wiringpi.Gpio;
008import com.pi4j.wiringpi.GpioInterruptEvent;
009import com.pi4j.wiringpi.GpioInterruptListener;
010import com.pi4j.wiringpi.GpioUtil;
011
012/*
013 * #%L
014 * **********************************************************************
015 * ORGANIZATION  :  Pi4J
016 * PROJECT       :  Pi4J :: Java Library (Core)
017 * FILENAME      :  WiringPiGpioProviderBase.java
018 *
019 * This file is part of the Pi4J project. More information about
020 * this project can be found here:  https://www.pi4j.com/
021 * **********************************************************************
022 * %%
023 * Copyright (C) 2012 - 2021 Pi4J
024 * %%
025 * This program is free software: you can redistribute it and/or modify
026 * it under the terms of the GNU Lesser General Public License as
027 * published by the Free Software Foundation, either version 3 of the
028 * License, or (at your option) any later version.
029 *
030 * This program is distributed in the hope that it will be useful,
031 * but WITHOUT ANY WARRANTY; without even the implied warranty of
032 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
033 * GNU General Lesser Public License for more details.
034 *
035 * You should have received a copy of the GNU General Lesser Public
036 * License along with this program.  If not, see
037 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
038 * #L%
039 */
040
041/**
042 * BananaPro {@link GpioProvider} implementation.
043 *
044 * @author Robert Savage (<a
045 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
046 */
047@SuppressWarnings("unused")
048public abstract class WiringPiGpioProviderBase extends GpioProviderBase implements GpioProvider, GpioInterruptListener {
049
050    // the pin cache should support the maximum number of pins supported by wiringPi plus some
051    // additional overhead for virtual analog input pins used by providers like Odroid C1/C1+/C2/XU4
052    protected static short MAX_PIN_CACHE = Gpio.NUM_PINS + 5;
053
054    // need enough space in array for maximum number of pins.
055    // Currently the Computer module supports the highest number of pins.
056    protected static short pinSupportedCache[] = new short[MAX_PIN_CACHE];
057    protected static PinMode pinModeCache[] = new PinMode[MAX_PIN_CACHE];
058
059    public abstract String getName();
060
061
062    @Override
063    public boolean hasPin(Pin pin) {
064        if(pinSupportedCache[pin.getAddress()] == 1) {
065            return true;
066        }
067        else if(pinSupportedCache[pin.getAddress()] == -1) {
068            return false;
069        }
070        else{
071            // add pin support to cache
072            if(GpioUtil.isPinSupported(pin.getAddress()) > 0) {
073                pinSupportedCache[pin.getAddress()] = 1;
074                return true;
075            }
076            else{
077                pinSupportedCache[pin.getAddress()] = -1;
078                return false;
079            }
080        }
081    }
082
083    @Override
084    public void export(Pin pin, PinMode mode) {
085        export(pin, mode, null);
086    }
087
088    @Override
089    public void export(Pin pin, PinMode mode, PinState defaultState) {
090        super.export(pin, mode);
091
092        //System.out.println("-- EXPORTING PIN [" + pin.getAddress() + "] to mode [" + mode.getName() + "]");
093
094        // get mode configured direction value
095        int direction = mode.getDirection().getValue();
096
097        // if a default state was provided and the direction is OUT, then override the
098        // pin direction to include initial state value
099        if(defaultState != null && mode.getDirection() == PinDirection.OUT){
100            if(defaultState == PinState.LOW)
101                direction = GpioUtil.DIRECTION_LOW;
102            else if(defaultState == PinState.HIGH)
103                direction = GpioUtil.DIRECTION_HIGH;
104        }
105
106        // if not already exported, export the pin and set the pin direction
107        if(!GpioUtil.isExported(pin.getAddress())){
108            GpioUtil.export(pin.getAddress(), direction);
109        }
110        // if the pin is already exported, then check its current configured direction
111        // if the direction does not match, then set the new direction for the pin
112        else if(GpioUtil.getDirection(pin.getAddress()) != mode.getDirection().getValue()){
113            GpioUtil.setDirection(pin.getAddress(), direction);
114        }
115
116        // set the pin input/output mode
117        setMode(pin, mode);
118    }
119
120    @Override
121    public boolean isExported(Pin pin) {
122        super.isExported(pin);
123
124        // return the pin exported state
125        return GpioUtil.isExported(pin.getAddress());
126    }
127
128    @Override
129    public void unexport(Pin pin) {
130        super.unexport(pin);
131
132        // unexport the pins
133        GpioUtil.unexport(pin.getAddress());
134    }
135
136    @Override
137    public void setMode(Pin pin, PinMode mode) {
138        super.setMode(pin, mode);
139
140        // local pin mode cache
141        pinModeCache[pin.getAddress()] = mode;
142
143        if (!pin.getSupportedPinModes().contains(mode)) {
144            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; pin [" + pin.getName() + "] does not support this mode.");
145        }
146
147        if (!pin.getSupportedPinModes().contains(mode)) {
148            throw new UnsupportedPinModeException(pin, mode);
149        }
150
151        // cache mode
152        getPinCache(pin).setMode(mode);
153
154        // set pin mode on hardware
155        com.pi4j.wiringpi.Gpio.pinMode(pin.getAddress(), mode.getValue());
156    }
157
158    @Override
159    public PinMode getMode(Pin pin) {
160        return pinModeCache[pin.getAddress()];
161    }
162
163    @Override
164    public void setPullResistance(Pin pin, PinPullResistance resistance) {
165        super.setPullResistance(pin, resistance);
166
167        com.pi4j.wiringpi.Gpio.pullUpDnControl(pin.getAddress(), resistance.getValue());
168    }
169
170//    @Override
171//    public PinPullResistance getPullResistance(Pin pin) {
172//        // TODO : get actual pin pull resistance from native impl
173//        return super.getPullResistance(pin);
174//    }
175
176    @Override
177    public void setState(Pin pin, PinState state) {
178
179        // validate pin
180        if (!hasPin(pin)) {
181            throw new InvalidPinException(pin);
182        }
183
184        // only permit invocation on pins set to DIGITAL_OUTPUT modes
185        if (pinModeCache[pin.getAddress()] != PinMode.DIGITAL_OUTPUT) {
186            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setState() when pin mode is [" + pinModeCache[pin.getAddress()].getName() + "]");
187        }
188
189        // control GPIO pin
190        com.pi4j.wiringpi.Gpio.digitalWrite(pin.getAddress(), state.getValue());
191
192        // for digital output pins, we will echo the event feedback
193        dispatchPinDigitalStateChangeEvent(pin, state);
194
195        // for the Raspberry pi, we will not cache pin state since we never use the cache to get state.
196    }
197
198    @Override
199    public PinState getState(Pin pin) {
200        super.getState(pin);
201
202        // return pin state
203        PinState state = null;
204        int ret = com.pi4j.wiringpi.Gpio.digitalRead(pin.getAddress());
205        if (ret >= 0) {
206            state = PinState.getState(ret);
207        }
208        return state;
209    }
210
211    @Override
212    public void setValue(Pin pin, double value) {
213        super.setValue(pin, value);
214        throw new RuntimeException("This GPIO provider does not support analog pins.");
215    }
216
217    @Override
218    public double getValue(Pin pin) {
219        super.getValue(pin);
220        throw new RuntimeException("This GPIO provider does not support analog pins.");
221    }
222
223    @Override
224    public void setPwm(Pin pin, int value) {
225        // validate pin
226        if (!hasPin(pin)) {
227            throw new InvalidPinException(pin);
228        }
229
230        // get pin configured mode
231        PinMode mode = getMode(pin);
232
233        // validate mode; set PWM value based on pin mode
234        if (mode == PinMode.PWM_OUTPUT) {
235            // set pin hardware PWM value
236            com.pi4j.wiringpi.Gpio.pwmWrite(pin.getAddress(), value);
237        }
238        else if(mode == PinMode.SOFT_PWM_OUTPUT) {
239            // set pin software emulated PWM value
240            com.pi4j.wiringpi.SoftPwm.softPwmWrite(pin.getAddress(), value);
241        }
242        else {
243            // unsupported pin mode
244            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; unable to setPwm(" + value + ")");
245        }
246
247        // cache updated pin PWM value
248        getPinCache(pin).setPwmValue(value);
249    }
250
251    @Override
252    public void setPwmRange(Pin pin, int range){
253        // validate pin
254        if (!hasPin(pin)) {
255            throw new InvalidPinException(pin);
256        }
257
258        // get pin configured mode
259        PinMode mode = getMode(pin);
260
261        // validate mode; set PWM value based on pin mode
262        if (mode == PinMode.PWM_OUTPUT) {
263            // set pin hardware PWM value
264            com.pi4j.wiringpi.Gpio.pwmSetRange(range);
265        }
266        else if(mode == PinMode.SOFT_PWM_OUTPUT) {
267            // first, stop the software emulated PWM driver for this pin
268            com.pi4j.wiringpi.SoftPwm.softPwmStop(pin.getAddress());
269            // update the PWM range for this pin
270            com.pi4j.wiringpi.SoftPwm.softPwmCreate(pin.getAddress(), 0, range);
271        }
272        else {
273            // unsupported operation
274            throw new UnsupportedOperationException();
275        }
276    }
277
278    @Override
279    public int getPwm(Pin pin) {
280        return super.getPwm(pin);
281    }
282
283    @Override
284    public void pinStateChange(GpioInterruptEvent event) {
285        // iterate over the pin listeners map
286        for (Pin pin : listeners.keySet()) {
287            // dispatch this event to the listener
288            // if a matching pin address is found
289            if (pin.getAddress() == event.getPin()) {
290                dispatchPinDigitalStateChangeEvent(pin, PinState.getState(event.getState()));
291            }
292        }
293    }
294
295    @Override
296    public void addListener(Pin pin, PinListener listener) {
297        super.addListener(pin, listener);
298
299        // update the native interrupt listener thread for callbacks
300        updateInterruptListener(pin);
301    }
302
303    @Override
304    public void removeListener(Pin pin, PinListener listener) {
305        super.removeListener(pin, listener);
306
307        // update the native interrupt listener thread for callbacks
308        updateInterruptListener(pin);
309    }
310
311    // internal
312    protected void updateInterruptListener(Pin pin) {
313        // enable or disable single static listener with the native impl
314        if (listeners.size() > 0) {
315            // setup interrupt listener native thread and enable callbacks
316            if (!com.pi4j.wiringpi.GpioInterrupt.hasListener(this)) {
317                com.pi4j.wiringpi.GpioInterrupt.addListener(this);
318            }
319
320            // only configure WiringPi interrupts for digital input pins
321            if(pinModeCache[pin.getAddress()] == PinMode.DIGITAL_INPUT) {
322                // enable or disable the individual pin listener
323                if(listeners.containsKey(pin) && listeners.get(pin).size() > 0) {
324                    // enable interrupt listener for this pin
325                    com.pi4j.wiringpi.GpioInterrupt.enablePinStateChangeCallback(pin.getAddress());
326                }
327                else {
328                    // disable interrupt listener for this pin
329                    com.pi4j.wiringpi.GpioInterrupt.disablePinStateChangeCallback(pin.getAddress());
330                }
331            }
332        }
333        else {
334            // disable interrupt listener for this pins
335            com.pi4j.wiringpi.GpioInterrupt.disablePinStateChangeCallback(pin.getAddress());
336
337            // remove interrupt listener, disable native thread and callbacks
338            if (com.pi4j.wiringpi.GpioInterrupt.hasListener(this)) {
339                com.pi4j.wiringpi.GpioInterrupt.removeListener(this);
340            }
341        }
342    }
343}