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}