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:  http://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2015 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.List;
043import java.util.Map;
044import java.util.concurrent.ConcurrentHashMap;
045
046/**
047 * Abstract base implementation of {@link com.pi4j.io.gpio.GpioProvider}.
048 *
049 * @author Robert Savage (<a
050 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
051 */
052@SuppressWarnings("unused")
053public abstract class GpioProviderBase implements GpioProvider {
054
055    public abstract String getName();
056
057    protected final Map<Pin, List<PinListener>> listeners = new ConcurrentHashMap<>();
058    protected final Map<Pin, GpioProviderPinCache> cache = new ConcurrentHashMap<>();
059    protected boolean isshutdown = false;
060    
061    @Override
062    public boolean hasPin(Pin pin) {
063        return (pin.getProvider().equals(getName()));
064    }
065    
066    protected GpioProviderPinCache getPinCache(Pin pin) {
067        if (!cache.containsKey(pin)) {
068            cache.put(pin, new GpioProviderPinCache(pin));
069        }
070        return cache.get(pin);
071    }
072
073    @Override
074    public void export(Pin pin, PinMode mode, PinState defaultState) {
075        // export the pin and set it's mode
076        export(pin, mode);
077
078        // apply default state if one was provided and only if this pin is a digital output
079        if(defaultState != null && mode == PinMode.DIGITAL_OUTPUT) {
080            setState(pin, defaultState);
081        }
082    }
083
084    @Override
085    public void export(Pin pin, PinMode mode) {
086        if (!hasPin(pin)) {
087            throw new InvalidPinException(pin);
088        }
089
090        if (!pin.getSupportedPinModes().contains(mode)) {
091            throw new UnsupportedPinModeException(pin, mode);
092        }
093
094        // cache exported state
095        getPinCache(pin).setExported(true);
096
097        // cache mode
098        getPinCache(pin).setMode(mode);
099    }
100    
101    @Override
102    public boolean isExported(Pin pin) {
103        if (!hasPin(pin)) {
104            throw new InvalidPinException(pin);
105        }
106        
107        // return cached exported state
108        return getPinCache(pin).isExported();
109    }
110
111    @Override
112    public void unexport(Pin pin) {
113        if (!hasPin(pin)) {
114            throw new InvalidPinException(pin);
115        }
116        
117        // cache exported state
118        getPinCache(pin).setExported(false);
119    }
120
121    @Override
122    public void setMode(Pin pin, PinMode mode) {
123        if (!pin.getSupportedPinModes().contains(mode)) {
124            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; pin [" + pin.getName() + "] does not support this mode.");
125        }
126
127        if (!pin.getSupportedPinModes().contains(mode)) {
128            throw new UnsupportedPinModeException(pin, mode);
129        }
130        
131        // cache mode
132        getPinCache(pin).setMode(mode);
133    }
134
135    @Override
136    public PinMode getMode(Pin pin) {
137        if (!hasPin(pin)) {
138            throw new InvalidPinException(pin);
139        }
140
141        // return cached mode value
142        return getPinCache(pin).getMode();
143    }
144    
145    
146    @Override
147    public void setPullResistance(Pin pin, PinPullResistance resistance) {
148        if (!hasPin(pin)) {
149            throw new InvalidPinException(pin);
150        }
151        
152        if (!pin.getSupportedPinPullResistance().contains(resistance)) {
153            throw new UnsupportedPinPullResistanceException(pin, resistance);
154        }
155        
156        // cache resistance
157        getPinCache(pin).setResistance(resistance);
158    }
159
160    @Override
161    public PinPullResistance getPullResistance(Pin pin) {
162        if (!hasPin(pin)) {
163            throw new InvalidPinException(pin);
164        }
165        
166        // return cached resistance
167        return getPinCache(pin).getResistance();
168    }
169    
170    @Override
171    public void setState(Pin pin, PinState state) {
172        if (!hasPin(pin)) {
173            throw new InvalidPinException(pin);
174        }
175
176        PinMode mode = getMode(pin);
177        
178        // only permit invocation on pins set to DIGITAL_OUTPUT modes 
179        if (mode != PinMode.DIGITAL_OUTPUT) {
180            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setState() when pin mode is [" + mode.getName() + "]");
181        }
182
183        // for digital output pins, we will echo the event feedback
184        dispatchPinDigitalStateChangeEvent(pin, state);
185
186        // cache pin state
187        getPinCache(pin).setState(state);
188    }
189
190    @Override
191    public PinState getState(Pin pin) {
192        if (!hasPin(pin)) {
193            throw new InvalidPinException(pin);
194        }
195        
196        PinMode mode = getMode(pin);
197
198        // only permit invocation on pins set to DIGITAL modes 
199        if (!PinMode.allDigital().contains(mode)) {
200            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot getState() when pin mode is [" + mode.getName() + "]");
201        }
202
203        // return cached pin state
204        return getPinCache(pin).getState();           
205    }
206
207    @Override
208    public void setValue(Pin pin, double value) {
209        if (!hasPin(pin)) {
210            throw new InvalidPinException(pin);
211        }
212        
213        PinMode mode = getMode(pin);
214
215        // only permit invocation on pins set to OUTPUT modes 
216        if (!PinMode.allOutput().contains(mode)) {
217            throw new InvalidPinModeException(pin, "Invalid pin mode on pin [" + pin.getName() + "]; cannot setValue(" + value + ") when pin mode is [" + mode.getName() + "]");
218        }
219
220        // for digital analog pins, we will echo the event feedback
221        dispatchPinAnalogValueChangeEvent(pin, value);
222
223        // cache pin analog value
224        getPinCache(pin).setAnalogValue(value);                        
225    }
226
227    @Override
228    public double getValue(Pin pin) {
229        if (!hasPin(pin)) {
230            throw new InvalidPinException(pin);
231        }
232
233        PinMode mode = getMode(pin);
234        
235        if (mode == PinMode.DIGITAL_OUTPUT) {
236            return getState(pin).getValue(); 
237        }
238        
239        // return cached pin analog value
240        return getPinCache(pin).getAnalogValue();                             
241    }
242    
243    @Override
244    public void setPwm(Pin pin, int value) {
245        if (!hasPin(pin)) {
246            throw new InvalidPinException(pin);        
247        }
248        
249        PinMode mode = getMode(pin);
250
251        if (mode != PinMode.PWM_OUTPUT) {
252            throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; unable to setPwm(" + value + ")");
253        }
254        
255        // cache pin PWM value
256        getPinCache(pin).setPwmValue(value);                       
257    }
258    
259    
260    @Override
261    public int getPwm(Pin pin) {
262        if (!hasPin(pin)) {
263            throw new InvalidPinException(pin);
264        }
265
266        // return cached pin PWM value
267        return getPinCache(pin).getPwmValue();                           
268    }
269
270    @Override
271    public void addListener(Pin pin, PinListener listener) {
272        synchronized (listeners) {
273            // create new pin listener entry if one does not already exist
274            if (!listeners.containsKey(pin)) {
275                listeners.put(pin, new ArrayList<PinListener>());
276            }
277
278            // add the listener instance to the listeners map entry
279            List<PinListener> lsnrs = listeners.get(pin);
280            if (!lsnrs.contains(listener)) {
281                lsnrs.add(listener);
282            }
283        }
284    }
285    
286    @Override
287    public void removeListener(Pin pin, PinListener listener) {
288        synchronized (listeners) {
289            // lookup to pin entry in the listeners map
290            if (listeners.containsKey(pin)) {
291                // remote the listener instance from the listeners map entry if found
292                List<PinListener> lsnrs = listeners.get(pin);
293                if (lsnrs.contains(listener)) {
294                    lsnrs.remove(listener);
295                }
296
297                // if the listener list is empty, then remove the listener pin from the map
298                if (lsnrs.isEmpty()) {
299                    listeners.remove(pin);
300                }
301            }
302        }
303    }    
304    
305    @Override
306    public void removeAllListeners() {
307        synchronized (listeners) {
308            // iterate over all listener pins in the map
309            List<Pin> pins_copy = new ArrayList<>(listeners.keySet());
310            for (Pin pin : pins_copy) {
311                if(listeners.containsKey(pin)) {
312                    // iterate over all listener handler in the map entry
313                    // and remove each listener handler instance
314                    List<PinListener> lsnrs = listeners.get(pin);
315                    if (!lsnrs.isEmpty()) {
316                        List<PinListener> lsnrs_copy = new ArrayList<>(lsnrs);
317                        for (int index = lsnrs_copy.size() - 1; index >= 0; index--) {
318                            PinListener listener = lsnrs_copy.get(index);
319                            removeListener(pin, listener);
320                        }
321                    }
322                }
323            }
324        }
325    }
326    
327    protected void dispatchPinDigitalStateChangeEvent(Pin pin, PinState state) {
328        // if the pin listeners map contains this pin, then dispatch event
329        if (listeners.containsKey(pin)) {
330            // dispatch this event to all listener handlers
331            for (PinListener listener : listeners.get(pin)) {
332                listener.handlePinEvent(new PinDigitalStateChangeEvent(this, pin, state));
333            }            
334        }
335    }
336    
337    protected void dispatchPinAnalogValueChangeEvent(Pin pin, double value) {
338        // if the pin listeners map contains this pin, then dispatch event
339        if (listeners.containsKey(pin)) {
340            // dispatch this event to all listener handlers
341            for (PinListener listener : listeners.get(pin)) {
342                listener.handlePinEvent(new PinAnalogValueChangeEvent(this, pin, value));
343            }            
344        }
345    }
346    
347    @Override
348    public void shutdown() {
349        
350        // prevent reentrant invocation
351        if(isShutdown())
352            return;
353
354        // remove all listeners
355        removeAllListeners();
356        
357        // set shutdown tracking state variable
358        isshutdown = true;
359    }     
360        
361    /**
362     * This method returns TRUE if the GPIO provider has been shutdown.
363     * 
364     * @return shutdown state
365     */
366    @Override
367    public boolean isShutdown(){
368        return isshutdown;
369    }    
370}