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}