001package com.pi4j.io.gpio;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  GpioProviderBase.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  https://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2019 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032
033import com.pi4j.io.gpio.event.PinAnalogValueChangeEvent;
034import com.pi4j.io.gpio.event.PinDigitalStateChangeEvent;
035import com.pi4j.io.gpio.event.PinListener;
036import com.pi4j.io.gpio.exception.InvalidPinException;
037import com.pi4j.io.gpio.exception.InvalidPinModeException;
038import com.pi4j.io.gpio.exception.UnsupportedPinModeException;
039import com.pi4j.io.gpio.exception.UnsupportedPinPullResistanceException;
040
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.List;
044import java.util.Map;
045import java.util.concurrent.ConcurrentHashMap;
046
047/**
048 * Abstract base implementation of {@link com.pi4j.io.gpio.GpioProvider}.
049 *
050 * @author Robert Savage (<a
051 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
052 */
053@SuppressWarnings("unused")
054public abstract class GpioProviderBase implements GpioProvider {
055
056    public static final int DEFAULT_CACHE_SIZE = 100;
057
058    public abstract String getName();
059
060    protected final Map<Pin, List<PinListener>> listeners = new ConcurrentHashMap<>();
061
062    // support up to pin address 100 by default.
063    // (dynamically expand array to accommodate cases where the pin addresses
064    //  may be higher than the default allocation capacity of 100.)
065    protected GpioProviderPinCache[] cache = new GpioProviderPinCache[DEFAULT_CACHE_SIZE];
066
067    protected boolean isshutdown = false;
068
069    @Override
070    public boolean hasPin(Pin pin) {
071        return (pin.getProvider().equals(getName()));
072    }
073
074    protected GpioProviderPinCache getPinCache(Pin pin) {
075
076        int address = pin.getAddress();
077
078        // dynamically resize pin cache storage if needed based on pin address
079        if(address >= cache.length){
080            // create a new array with existing contents
081            // that is 100 elements larger than the requested address
082            // (we add the extra 100 elements to provide additional overhead capacity in
083            //  an attempt to minimize further array expansion)
084            cache = Arrays.copyOf(cache, address + 100);
085        }
086
087        // get the cached pin object from the cache
088        GpioProviderPinCache pc = cache[address];
089
090        // if no pin object is found in the cache, then we need to create one at this address index in the cache array
091        if(pc == null){
092            pc = cache[pin.getAddress()] = new GpioProviderPinCache(pin);
093        }
094        return pc;
095    }
096
097    @Override
098    public void export(Pin pin, PinMode mode, PinState defaultState) {
099        // export the pin and set it's mode
100        export(pin, mode);
101
102        // apply default state if one was provided and only if this pin is a digital output
103        if(defaultState != null && mode == PinMode.DIGITAL_OUTPUT) {
104            setState(pin, defaultState);
105        }
106    }
107
108    @Override
109    public void export(Pin pin, PinMode mode) {
110        if (!hasPin(pin)) {
111            throw new InvalidPinException(pin);
112        }
113
114        if (!pin.getSupportedPinModes().contains(mode)) {
115            throw new UnsupportedPinModeException(pin, mode);
116        }
117
118        // cache exported state
119        getPinCache(pin).setExported(true);
120
121        // cache mode
122        getPinCache(pin).setMode(mode);
123    }
124
125    @Override
126    public boolean isExported(Pin pin) {
127        if (!hasPin(pin)) {
128            throw new InvalidPinException(pin);
129        }
130
131        // return cached exported state
132        return getPinCache(pin).isExported();
133    }
134
135    @Override
136    public void unexport(Pin pin) {
137        if (!hasPin(pin)) {
138            throw new InvalidPinException(pin);
139        }
140
141        // cache exported state
142        getPinCache(pin).setExported(false);
143    }
144
145    @Override
146    public void setMode(Pin pin, PinMode mode) {
147        if (!pin.getSupportedPinModes().contains(mode)) {
148            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; pin [" + pin.getName() + "] does not support this mode.");
149        }
150
151        if (!pin.getSupportedPinModes().contains(mode)) {
152            throw new UnsupportedPinModeException(pin, mode);
153        }
154
155        // cache mode
156        getPinCache(pin).setMode(mode);
157    }
158
159    @Override
160    public PinMode getMode(Pin pin) {
161        if (!hasPin(pin)) {
162            throw new InvalidPinException(pin);
163        }
164
165        // return cached mode value
166        return getPinCache(pin).getMode();
167    }
168
169
170    @Override
171    public void setPullResistance(Pin pin, PinPullResistance resistance) {
172        if (!hasPin(pin)) {
173            throw new InvalidPinException(pin);
174        }
175
176        if (!pin.getSupportedPinPullResistance().contains(resistance)) {
177            throw new UnsupportedPinPullResistanceException(pin, resistance);
178        }
179
180        // cache resistance
181        getPinCache(pin).setResistance(resistance);
182    }
183
184    @Override
185    public PinPullResistance getPullResistance(Pin pin) {
186        if (!hasPin(pin)) {
187            throw new InvalidPinException(pin);
188        }
189
190        // return cached resistance
191        return getPinCache(pin).getResistance();
192    }
193
194    @Override
195    public void setState(Pin pin, PinState state) {
196        if (!hasPin(pin)) {
197            throw new InvalidPinException(pin);
198        }
199
200        GpioProviderPinCache pinCache = getPinCache(pin);
201
202        // only permit invocation on pins set to DIGITAL_OUTPUT modes
203        if (pinCache.getMode() != PinMode.DIGITAL_OUTPUT) {
204            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setState() when pin mode is [" + pinCache.getMode().getName() + "]");
205        }
206
207        // for digital output pins, we will echo the event feedback
208        dispatchPinDigitalStateChangeEvent(pin, state);
209
210        // cache pin state
211        pinCache.setState(state);
212    }
213
214    @Override
215    public PinState getState(Pin pin) {
216        // the getMode() will validate the pin exists with the hasPin() function
217        PinMode mode = getMode(pin);
218
219        // only permit invocation on pins set to DIGITAL modes
220        if (!PinMode.allDigital().contains(mode)) {
221            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot getState() when pin mode is [" + mode.getName() + "]");
222        }
223
224        // return cached pin state
225        return getPinCache(pin).getState();
226    }
227
228    @Override
229    public void setValue(Pin pin, double value) {
230
231        // the getMode() will validate the pin exists with the hasPin() function
232        PinMode mode = getMode(pin);
233
234        // only permit invocation on pins set to OUTPUT modes
235        if (!PinMode.allOutput().contains(mode)) {
236            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setValue(" + value + ") when pin mode is [" + mode.getName() + "]");
237        }
238
239        // for digital analog pins, we will echo the event feedback
240        dispatchPinAnalogValueChangeEvent(pin, value);
241
242        // cache pin analog value
243        getPinCache(pin).setAnalogValue(value);
244    }
245
246    @Override
247    public double getValue(Pin pin) {
248        // the getMode() will validate the pin exists with the hasPin() function
249        PinMode mode = getMode(pin);
250
251        if (mode == PinMode.DIGITAL_OUTPUT) {
252            return getState(pin).getValue();
253        }
254
255        // return cached pin analog value
256        return getPinCache(pin).getAnalogValue();
257    }
258
259    @Override
260    public void setPwm(Pin pin, int value) {
261        if (!hasPin(pin)) {
262            throw new InvalidPinException(pin);
263        }
264
265        PinMode mode = getMode(pin);
266
267        if (mode != PinMode.PWM_OUTPUT || mode != PinMode.SOFT_PWM_OUTPUT) {
268            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; unable to setPwm(" + value + ")");
269        }
270
271        // cache pin PWM value
272        getPinCache(pin).setPwmValue(value);
273    }
274
275    @Override
276    public void setPwmRange(Pin pin, int range){
277        if (!hasPin(pin)) {
278            throw new InvalidPinException(pin);
279        }
280        throw new UnsupportedOperationException();
281    }
282
283    @Override
284    public int getPwm(Pin pin) {
285        if (!hasPin(pin)) {
286            throw new InvalidPinException(pin);
287        }
288
289        // return cached pin PWM value
290        return getPinCache(pin).getPwmValue();
291    }
292
293    @Override
294    public void addListener(Pin pin, PinListener listener) {
295        synchronized (listeners) {
296            // create new pin listener entry if one does not already exist
297            if (!listeners.containsKey(pin)) {
298                listeners.put(pin, new ArrayList<>());
299            }
300
301            // add the listener instance to the listeners map entry
302            List<PinListener> lsnrs = listeners.get(pin);
303            if (!lsnrs.contains(listener)) {
304                lsnrs.add(listener);
305            }
306        }
307    }
308
309    @Override
310    public void removeListener(Pin pin, PinListener listener) {
311        synchronized (listeners) {
312            // lookup to pin entry in the listeners map
313            if (listeners.containsKey(pin)) {
314                // remote the listener instance from the listeners map entry if found
315                List<PinListener> lsnrs = listeners.get(pin);
316                if (lsnrs.contains(listener)) {
317                    lsnrs.remove(listener);
318                }
319
320                // if the listener list is empty, then remove the listener pin from the map
321                if (lsnrs.isEmpty()) {
322                    listeners.remove(pin);
323                }
324            }
325        }
326    }
327
328    @Override
329    public void removeAllListeners() {
330        synchronized (listeners) {
331            // iterate over all listener pins in the map
332            List<Pin> pins_copy = new ArrayList<>(listeners.keySet());
333            for (Pin pin : pins_copy) {
334                if(listeners.containsKey(pin)) {
335                    // iterate over all listener handler in the map entry
336                    // and remove each listener handler instance
337                    List<PinListener> lsnrs = listeners.get(pin);
338                    if (!lsnrs.isEmpty()) {
339                        List<PinListener> lsnrs_copy = new ArrayList<>(lsnrs);
340                        for (int index = lsnrs_copy.size() - 1; index >= 0; index--) {
341                            PinListener listener = lsnrs_copy.get(index);
342                            removeListener(pin, listener);
343                        }
344                    }
345                }
346            }
347        }
348    }
349
350    protected void dispatchPinDigitalStateChangeEvent(Pin pin, PinState state) {
351        // if the pin listeners map contains this pin, then dispatch event
352        if (listeners.containsKey(pin)) {
353            // dispatch this event to all listener handlers
354            for (PinListener listener : listeners.get(pin)) {
355                listener.handlePinEvent(new PinDigitalStateChangeEvent(this, pin, state));
356            }
357        }
358    }
359
360    protected void dispatchPinAnalogValueChangeEvent(Pin pin, double value) {
361        // if the pin listeners map contains this pin, then dispatch event
362        if (listeners.containsKey(pin)) {
363            // dispatch this event to all listener handlers
364            for (PinListener listener : listeners.get(pin)) {
365                listener.handlePinEvent(new PinAnalogValueChangeEvent(this, pin, value));
366            }
367        }
368    }
369
370    @Override
371    public void shutdown() {
372
373        // prevent reentrant invocation
374        if(isShutdown())
375            return;
376
377        // remove all listeners
378        removeAllListeners();
379
380        // set shutdown tracking state variable
381        isshutdown = true;
382    }
383
384    /**
385     * This method returns TRUE if the GPIO provider has been shutdown.
386     *
387     * @return shutdown state
388     */
389    @Override
390    public boolean isShutdown(){
391        return isshutdown;
392    }
393}