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 - 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.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                for (int index =  (tasks.size() - 1); index >= 0; index--) {
060                    ScheduledFuture<?> task = tasks.get(index);
061                    task.cancel(true);
062                    tasks.remove(index);
063                }
064            }
065
066            // if no remaining future tasks are remaining, then remove this pin from the tasks queue
067            if (tasks != null && tasks.isEmpty()) {
068                pinTaskQueue.remove(pin);
069            }
070        }
071    }
072
073    private synchronized static ScheduledFuture<?> createCleanupTask(long delay, TimeUnit timeUnit) {
074        // create future task to clean up completed tasks
075
076        @SuppressWarnings({"rawtypes", "unchecked"})
077        ScheduledFuture<?> cleanupFutureTask = scheduledExecutorService.schedule(new Callable() {
078            public Object call() throws Exception {
079                for (Entry<GpioPinDigitalOutput, CopyOnWriteArrayList<ScheduledFuture<?>>> item : pinTaskQueue.entrySet()) {
080                    CopyOnWriteArrayList<ScheduledFuture<?>> remainingTasks = item.getValue();
081
082                    // if a task is found, then cancel all pending tasks immediately and remove them
083                    if (remainingTasks != null && !remainingTasks.isEmpty()) {
084                        for (int index = (remainingTasks.size() - 1); index >= 0; index--) {
085                            ScheduledFuture<?> remainingTask = remainingTasks.get(index);
086                            if (remainingTask.isCancelled() || remainingTask.isDone()) {
087                                remainingTasks.remove(index);
088                            }
089                        }
090
091                        // if no remaining future tasks are remaining, then remove this pin from the tasks queue
092                        if (remainingTasks.isEmpty()) {
093                            pinTaskQueue.remove(item.getKey());
094                        }
095                    }
096                }
097                return null;
098            }
099        }, delay, timeUnit);
100
101        return cleanupFutureTask;
102    }
103
104
105    public synchronized static Future<?> pulse(GpioPinDigitalOutput pin, long duration, PinState pulseState, TimeUnit unit) {
106        return pulse(pin, duration, pulseState, null, unit);
107    }
108
109    public synchronized static Future<?> pulse(GpioPinDigitalOutput pin, long duration, PinState pulseState, Callable<?> callback, TimeUnit timeUnit) {
110
111        // create future return object
112        ScheduledFuture<?> scheduledFuture = null;
113
114        // perform the initial startup and cleanup for this pin
115        init(pin);
116
117        // we only pulse for requests with a valid duration in milliseconds
118        if (duration > 0) {
119            // set the active state
120            pin.setState(pulseState);
121
122            // create future job to return the pin to the low state
123            scheduledFuture = scheduledExecutorService
124                .schedule(new GpioPulseTaskImpl(pin, PinState.getInverseState(pulseState), callback), duration, timeUnit);
125
126            // get pending tasks for the current pin
127            CopyOnWriteArrayList<ScheduledFuture<?>> tasks;
128            if (!pinTaskQueue.containsKey(pin)) {
129                pinTaskQueue.put(pin, new CopyOnWriteArrayList<>());
130            }
131            tasks = pinTaskQueue.get(pin);
132
133            // add the new scheduled task to the tasks collection
134            tasks.add(scheduledFuture);
135
136            // create future task to clean up completed tasks
137            createCleanupTask(duration + 500, timeUnit);
138        }
139
140        // return future task
141        return scheduledFuture;
142    }
143
144    public synchronized static Future<?> blink(GpioPinDigitalOutput pin, long delay, long duration, PinState blinkState, TimeUnit timeUnit) {
145
146        // perform the initial startup and cleanup for this pin
147        init(pin);
148
149        // we only blink for requests with a valid delay in milliseconds
150        if (delay > 0) {
151            // make sure pin starts in active state
152            pin.setState(blinkState);
153
154            // create future job to toggle the pin state
155            ScheduledFuture<?> scheduledFutureBlinkTask = scheduledExecutorService
156                .scheduleAtFixedRate(new GpioBlinkTaskImpl(pin), delay, delay, timeUnit);
157
158            // get pending tasks for the current pin
159            CopyOnWriteArrayList<ScheduledFuture<?>> tasks;
160            if (!pinTaskQueue.containsKey(pin)) {
161                pinTaskQueue.put(pin, new CopyOnWriteArrayList<>());
162            }
163            tasks = pinTaskQueue.get(pin);
164
165            // add the new scheduled task to the tasks collection
166            tasks.add(scheduledFutureBlinkTask);
167
168            // if a duration was defined, then schedule a future task to kill the blinker task
169            if (duration > 0) {
170                // create future job to stop blinking
171                ScheduledFuture<?> scheduledFutureBlinkStopTask = scheduledExecutorService
172                    .schedule(new GpioBlinkStopTaskImpl(pin,PinState.getInverseState(blinkState), scheduledFutureBlinkTask), duration, timeUnit);
173
174                // add the new scheduled stop task to the tasks collection
175                tasks.add(scheduledFutureBlinkStopTask);
176
177                // create future task to clean up completed tasks
178                createCleanupTask(duration + 500, timeUnit);
179            }
180
181            // return future task
182            return scheduledFutureBlinkTask;
183        }
184
185        // no future task when a delay time has not been specified
186        return null;
187    }
188}