001package com.pi4j.io.gpio.impl; 002 003/* 004 * #%L 005 * ********************************************************************** 006 * ORGANIZATION : Pi4J 007 * PROJECT : Pi4J :: Java Library (Core) 008 * FILENAME : GpioScheduledExecutorImpl.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 - 2021 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.GpioFactory; 034import com.pi4j.io.gpio.GpioPinDigitalOutput; 035import com.pi4j.io.gpio.PinState; 036import com.pi4j.io.gpio.tasks.impl.GpioBlinkStopTaskImpl; 037import com.pi4j.io.gpio.tasks.impl.GpioBlinkTaskImpl; 038import com.pi4j.io.gpio.tasks.impl.GpioPulseTaskImpl; 039 040import java.util.ArrayList; 041import java.util.Map.Entry; 042import java.util.concurrent.*; 043 044public class GpioScheduledExecutorImpl { 045 046 private static final ConcurrentHashMap<GpioPinDigitalOutput, CopyOnWriteArrayList<ScheduledFuture<?>>> pinTaskQueue = new ConcurrentHashMap<>(); 047 private static ScheduledExecutorService scheduledExecutorService; 048 049 private synchronized static void init(GpioPinDigitalOutput pin) { 050 if (scheduledExecutorService == null) { 051 scheduledExecutorService = GpioFactory.getExecutorServiceFactory().getScheduledExecutorService(); 052 } 053 054 // determine if any existing future tasks are already scheduled for this pin 055 if (pinTaskQueue.containsKey(pin)) { 056 // if a task is found, then cancel all pending tasks immediately and remove them 057 CopyOnWriteArrayList<ScheduledFuture<?>> tasks = pinTaskQueue.get(pin); 058 if (tasks != null && !tasks.isEmpty()) { 059 // iterate tasks and cancel any 060 for (int index = (tasks.size() - 1); index >= 0; index--) { 061 ScheduledFuture<?> task = tasks.get(index); 062 task.cancel(true); 063 } 064 // clear the tasks queue for this pin 065 tasks.clear(); 066 } 067 068 // if no remaining future tasks are remaining, then remove this pin from the tasks queue 069 if (tasks != null && tasks.isEmpty()) { 070 pinTaskQueue.remove(pin); 071 } 072 } 073 } 074 075 private synchronized static ScheduledFuture<?> createCleanupTask(long delay, TimeUnit timeUnit) { 076 // create future task to clean up completed tasks 077 078 @SuppressWarnings({"rawtypes", "unchecked"}) 079 ScheduledFuture<?> cleanupFutureTask = scheduledExecutorService.schedule(new Callable() { 080 public Object call() throws Exception { 081 for (Entry<GpioPinDigitalOutput, CopyOnWriteArrayList<ScheduledFuture<?>>> item : pinTaskQueue.entrySet()) { 082 CopyOnWriteArrayList<ScheduledFuture<?>> remainingTasks = item.getValue(); 083 ArrayList<ScheduledFuture<?>> purgeTasks = new ArrayList<>(); 084 085 // if there are pending tasks in queue, then lets look for any that can be removed 086 if (remainingTasks != null && !remainingTasks.isEmpty()) { 087 // build up a purge list of tasks that are canceled or already complete 088 for (int index = (remainingTasks.size() - 1); index >= 0; index--) { 089 ScheduledFuture<?> remainingTask = remainingTasks.get(index); 090 if (remainingTask.isCancelled() || remainingTask.isDone()) { 091 purgeTasks.add(remainingTask); 092 } 093 } 094 // purge the completed and canceled tasks on the purge list now 095 for(ScheduledFuture<?> task : purgeTasks){ 096 if(remainingTasks.contains(task)) remainingTasks.remove(task); 097 } 098 099 // if no remaining future tasks are remaining, then remove this pin from the tasks queue 100 if (remainingTasks.isEmpty()) { 101 pinTaskQueue.remove(item.getKey()); 102 } 103 } 104 } 105 return null; 106 } 107 }, delay, timeUnit); 108 109 return cleanupFutureTask; 110 } 111 112 113 public synchronized static Future<?> pulse(GpioPinDigitalOutput pin, long duration, PinState pulseState, TimeUnit unit) { 114 return pulse(pin, duration, pulseState, null, unit); 115 } 116 117 public synchronized static Future<?> pulse(GpioPinDigitalOutput pin, long duration, PinState pulseState, Callable<?> callback, TimeUnit timeUnit) { 118 119 // create future return object 120 ScheduledFuture<?> scheduledFuture = null; 121 122 // perform the initial startup and cleanup for this pin 123 init(pin); 124 125 // we only pulse for requests with a valid duration in milliseconds 126 if (duration > 0) { 127 // set the active state 128 pin.setState(pulseState); 129 130 // create future job to return the pin to the low state 131 scheduledFuture = scheduledExecutorService 132 .schedule(new GpioPulseTaskImpl(pin, PinState.getInverseState(pulseState), callback), duration, timeUnit); 133 134 // get pending tasks for the current pin 135 CopyOnWriteArrayList<ScheduledFuture<?>> tasks; 136 if (!pinTaskQueue.containsKey(pin)) { 137 pinTaskQueue.put(pin, new CopyOnWriteArrayList<>()); 138 } 139 tasks = pinTaskQueue.get(pin); 140 141 // add the new scheduled task to the tasks collection 142 tasks.add(scheduledFuture); 143 144 // create future task to clean up completed tasks 145 createCleanupTask(duration + 500, timeUnit); 146 } 147 148 // return future task 149 return scheduledFuture; 150 } 151 152 public synchronized static Future<?> blink(GpioPinDigitalOutput pin, long delay, long duration, PinState blinkState, TimeUnit timeUnit) { 153 154 // perform the initial startup and cleanup for this pin 155 init(pin); 156 157 // we only blink for requests with a valid delay in milliseconds 158 if (delay > 0) { 159 // make sure pin starts in active state 160 pin.setState(blinkState); 161 162 // create future job to toggle the pin state 163 ScheduledFuture<?> scheduledFutureBlinkTask = scheduledExecutorService 164 .scheduleAtFixedRate(new GpioBlinkTaskImpl(pin), delay, delay, timeUnit); 165 166 // get pending tasks for the current pin 167 CopyOnWriteArrayList<ScheduledFuture<?>> tasks; 168 if (!pinTaskQueue.containsKey(pin)) { 169 pinTaskQueue.put(pin, new CopyOnWriteArrayList<>()); 170 } 171 tasks = pinTaskQueue.get(pin); 172 173 // add the new scheduled task to the tasks collection 174 tasks.add(scheduledFutureBlinkTask); 175 176 // if a duration was defined, then schedule a future task to kill the blinker task 177 if (duration > 0) { 178 // create future job to stop blinking 179 ScheduledFuture<?> scheduledFutureBlinkStopTask = scheduledExecutorService 180 .schedule(new GpioBlinkStopTaskImpl(pin,PinState.getInverseState(blinkState), scheduledFutureBlinkTask), duration, timeUnit); 181 182 // add the new scheduled stop task to the tasks collection 183 tasks.add(scheduledFutureBlinkStopTask); 184 185 // create future task to clean up completed tasks 186 createCleanupTask(duration + 500, timeUnit); 187 } 188 189 // return future task 190 return scheduledFutureBlinkTask; 191 } 192 193 // no future task when a delay time has not been specified 194 return null; 195 } 196}