001package com.pi4j.io.gpio;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  OdroidGpioProvider.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
032import com.pi4j.io.gpio.event.PinListener;
033import com.pi4j.io.gpio.exception.InvalidPinModeException;
034import com.pi4j.io.gpio.exception.UnsupportedPinModeException;
035import com.pi4j.jni.AnalogInputEvent;
036import com.pi4j.jni.AnalogInputListener;
037import com.pi4j.platform.Platform;
038import com.pi4j.wiringpi.Gpio;
039import com.pi4j.wiringpi.GpioInterruptEvent;
040import com.pi4j.wiringpi.GpioInterruptListener;
041import com.pi4j.wiringpi.GpioUtil;
042
043/**
044 * Odroid-C1/C1+/XU4 {@link GpioProvider} implementation.
045 *
046 * @author Robert Savage (<a
047 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
048 */
049@SuppressWarnings("unused")
050public class OdroidGpioProvider extends WiringPiGpioProviderBase implements GpioProvider, GpioInterruptListener, AnalogInputListener {
051
052    public static final String NAME = "Odroid GPIO Provider";
053
054    // analog input pin addresses are assigned in a virtual range starting above the
055    // maximum number of physical pins supported by WiringPi
056    public static final int AIN_ADDRESS_OFFSET = Gpio.NUM_PINS + 1;
057
058    public static final int DEFAULT_ANALOG_INPUT_POLLING_RATE = 50; // milliseconds
059    public static final double DEFAULT_ANALOG_INPUT_LISTENER_CHANGE_THRESHOLD = 0.0f;
060
061    protected static int analogInputPollingRate = DEFAULT_ANALOG_INPUT_POLLING_RATE;
062    protected static double analogInputListenerChangeThreshold = DEFAULT_ANALOG_INPUT_LISTENER_CHANGE_THRESHOLD;
063
064    /**
065     * Get the analog input monitor polling rate in milliseconds.
066     * This is the rate at which the internal analog input monitoring thread will poll for analog input value changes
067     * and dispatch analog input value change event for subscribed analog input listeners.
068     *
069     * (The DEFAULT polling rate is 50 milliseconds)
070     *
071     * @return polling rate in milliseconds
072     */
073    public static int getAnalogInputPollingRate(){
074        return analogInputPollingRate;
075    }
076
077    /**
078     * Get the analog input listener change value threshold.
079     * This is the threshold delta value that the internal analog input monitoring thread must cross before
080     * dispatching a new analog input value change event.  The analog input value must change in excess of this
081     * defined value from the last event dispatched before dispatching a new analog input value change event.
082     *
083     * NOTE: This threshold value is a valuable tool to filter/limit the analog input value change events that
084     * your program may receive.
085     *
086     * (The DEFAULT change threshold value is 0)
087     *
088     * @return change threshold value (delta)
089     */
090    public static double getAnalogInputListenerChangeThreshold(){
091        return analogInputListenerChangeThreshold;
092    }
093
094    /**
095     * Set the analog input monitor polling rate in milliseconds.
096     * This is the rate at which the internal analog input monitoring thread will poll for analog input value changes
097     * and dispatch analog input value change event for subscribed analog input listeners.
098     *
099     * NOTE:  Be aware that lower polling rates can impact/increase the CPU usage for your application.
100     *
101     * (The DEFAULT polling rate is 50 milliseconds)
102     *
103     * @param milliseconds polling rate in milliseconds; this value must be a positive number
104     *                     else a default polling rate is used
105     */
106    public static void setAnalogInputPollingRate(int milliseconds){
107        if(milliseconds > 0) analogInputPollingRate = milliseconds;
108    }
109
110    /**
111     * Set the analog input listener change value threshold.
112     * This is the threshold delta value that the internal analog input monitoring thread must cross before
113     * dispatching a new analog input value change event.  The analog input value must change in excess of this
114     * defined value from the last event dispatched before dispatching a new analog input value change event.
115     *
116     * NOTE: This threshold value is a valuable tool to filter/limit the analog input value change events that
117     * your program may receive.
118     *
119     * (The DEFAULT change threshold value is 0)
120     *
121     * @param threshold change threshold value; this value must be zero or greater.  If the threshold value is set
122     *                  to zero, then any change in value will dispatch a new analog input value event.
123     */
124    public static void setAnalogInputListenerChangeThreshold(double threshold){
125        if(threshold > 0) analogInputListenerChangeThreshold = threshold;
126    }
127
128    /**
129     * Default Constructor
130     */
131    public OdroidGpioProvider() {
132
133        // configure the Pi4J platform to use the "odroid" implementation
134        System.setProperty("pi4j.platform", Platform.ODROID.id());
135
136        // set wiringPi interface for internal use
137        // we will use the WiringPi pin number scheme with the wiringPi library
138        com.pi4j.wiringpi.Gpio.wiringPiSetup();
139    }
140
141    @Override
142    public String getName() {
143        return NAME;
144    }
145
146    @Override
147    public void export(Pin pin, PinMode mode, PinState defaultState) {
148        // no need to export an Odroid AIN pin
149        if (mode == PinMode.ANALOG_INPUT) {
150
151            // set the pin input/output mode
152            setMode(pin, mode);
153
154            return;
155        }
156
157        super.export(pin, mode, defaultState);
158    }
159
160
161    @Override
162    public boolean isExported(Pin pin) {
163        // Odroid AIN pins are not exported
164        if (getMode(pin) == PinMode.ANALOG_INPUT) {
165            return false;
166        }
167
168        return super.isExported(pin);
169    }
170
171    @Override
172    public void unexport(Pin pin) {
173        // no need to unexport an Odroid AIN pin
174        if (pin.getSupportedPinModes().contains(PinMode.ANALOG_INPUT)) {
175            return;
176        }
177
178        super.unexport(pin);
179    }
180
181    @Override
182    public void setMode(Pin pin, PinMode mode) {
183
184        // no need to export an Odroid AIN pin
185        if (mode == PinMode.ANALOG_INPUT) {
186
187            if (!pin.getSupportedPinModes().contains(mode)) {
188                throw new InvalidPinModeException(pin, "Invalid pin mode [" + mode.getName() + "]; pin [" + pin.getName() + "] does not support this mode.");
189            }
190
191            // local pin mode cache
192            pinModeCache[pin.getAddress()] = mode;
193
194            // cache mode
195            getPinCache(pin).setMode(mode);
196
197            return;
198        }
199
200        super.setMode(pin, mode);
201    }
202
203    @Override
204    public double getValue(Pin pin) {
205
206        // the getMode() will validate the pin exists with the hasPin() function
207        PinMode mode = getMode(pin);
208
209        // handle analog input reading for Odroid boards
210        if (mode == PinMode.ANALOG_INPUT) {
211            // read latest analog input value from WiringPi
212            // we need to re-address the pin for Odroid boards (analog_address = assigned_pin_address - AIN_ADDRESS_OFFSET)
213            double value = com.pi4j.wiringpi.Gpio.analogRead(pin.getAddress() - AIN_ADDRESS_OFFSET);
214
215            // cache latest analog input value
216            getPinCache(pin).setAnalogValue(value);
217
218            // return latest analog input value
219            return value;
220        }
221
222        return super.getValue(pin);
223    }
224
225    @Override
226    public void pinValueChange(AnalogInputEvent event){
227
228        // we need to re-address the pin for Odroid boards
229        int analogPinAddress = event.getPin() + AIN_ADDRESS_OFFSET;
230
231        // iterate over the pin listeners map
232        for (Pin pin : listeners.keySet()) {
233            // dispatch this event to the listener
234            // if a matching pin address is found
235            if (pin.getAddress() == analogPinAddress) {
236                dispatchPinAnalogValueChangeEvent(pin, event.getValue());
237            }
238        }
239    }
240
241    // internal
242    @Override
243    protected void updateInterruptListener(Pin pin) {
244
245        // enable or disable single static listener with the native impl
246        if (listeners.size() > 0) {
247
248            // ------------------------
249            // DIGITAL INPUT PINS
250            // ------------------------
251
252            // setup interrupt listener native thread and enable callbacks
253            if (!com.pi4j.wiringpi.GpioInterrupt.hasListener(this)) {
254                com.pi4j.wiringpi.GpioInterrupt.addListener(this);
255            }
256
257            // only configure WiringPi interrupts for digital input pins
258            if(pinModeCache[pin.getAddress()] == PinMode.DIGITAL_INPUT) {
259
260                // enable or disable the individual pin listener
261                if(listeners.containsKey(pin) && listeners.get(pin).size() > 0) {
262                    // enable interrupt listener for this pin
263                    com.pi4j.wiringpi.GpioInterrupt.enablePinStateChangeCallback(pin.getAddress());
264                }
265                else {
266                    // disable interrupt listener for this pin
267                    com.pi4j.wiringpi.GpioInterrupt.disablePinStateChangeCallback(pin.getAddress());
268                }
269            }
270
271            // ------------------------
272            // ANALOG INPUT PINS
273            // ------------------------
274
275            // setup analog input monitor/listener native thread and enable callbacks
276            if (!com.pi4j.jni.AnalogInputMonitor.hasListener(this)) {
277                com.pi4j.jni.AnalogInputMonitor.addListener(this);
278            }
279
280            // configure analog monitor for analog input pins
281            if(pinModeCache[pin.getAddress()] == PinMode.ANALOG_INPUT) {
282                // we need to re-address the pin for Odroid boards (analog_address = assigned_pin_address - AIN_ADDRESS_OFFSET)
283                int analogPinAddress = pin.getAddress() - AIN_ADDRESS_OFFSET;
284
285                // enable or disable the individual pin listener
286                if(listeners.containsKey(pin) && listeners.get(pin).size() > 0) {
287                    // enable interrupt listener for this pin
288                    com.pi4j.jni.AnalogInputMonitor.enablePinValueChangeCallback(analogPinAddress,
289                                                                                 analogInputPollingRate,
290                                                                                 analogInputListenerChangeThreshold);
291                }
292                else {
293                    // disable interrupt listener for this pin
294                    com.pi4j.jni.AnalogInputMonitor.disablePinValueChangeCallback(analogPinAddress);
295                }
296            }
297        }
298        else {
299            // ------------------------
300            // DIGITAL INPUT PINS
301            // ------------------------
302
303            // disable interrupt listener for this pins
304            com.pi4j.wiringpi.GpioInterrupt.disablePinStateChangeCallback(pin.getAddress());
305
306            // remove interrupt listener, disable native thread and callbacks
307            if (com.pi4j.wiringpi.GpioInterrupt.hasListener(this)) {
308                com.pi4j.wiringpi.GpioInterrupt.removeListener(this);
309            }
310
311            // ------------------------
312            // ANALOG INPUT PINS
313            // ------------------------
314
315            // we need to re-address the pin for Odroid boards (analog_address = assigned_pin_address - AIN_ADDRESS_OFFSET)
316            int analogPinAddress = pin.getAddress() - AIN_ADDRESS_OFFSET;
317
318            // disable analog input monitor/listener for this pins
319            com.pi4j.jni.AnalogInputMonitor.disablePinValueChangeCallback(analogPinAddress);
320
321            // remove analog input monitor/listener, disable native thread and callbacks
322            if (com.pi4j.jni.AnalogInputMonitor.hasListener(this)) {
323                com.pi4j.jni.AnalogInputMonitor.removeListener(this);
324            }
325        }
326    }
327}