/* * Copyright (c) 2013, Will Szumski * Copyright (c) 2013, Doug Szumski * * This file is part of Cyclismo. * * Cyclismo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cyclismo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cyclismo. If not, see <http://www.gnu.org/licenses/>. */ package org.cowboycoders.turbotrainers.bushido.headunit; import org.cowboycoders.turbotrainers.TurboCommunicationException; import org.cowboycoders.turbotrainers.bushido.headunit.BushidoButtonPressDescriptor.Duration; import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Only dispatches a button press when duration is known * * @author will */ public class ButtonPressDispatcher { // I have left a 500 ms window to catch the repeated transmissions - may need tuning for // reliability / response time /** * Maximum time to listen for a new button press */ private static long TIMEOUT_NANO_SECONDS = TimeUnit.MILLISECONDS.toNanos(525); /** * If an another button press is received within this period, it is deem to be independent * and thus triggers another event (channel period is 8hz). * * Note : this may no be needed as you should not get too @code {Duration.Short}'s in a row. */ private static long INDEPENDENT_MESSAGE_CUTOFF_NANO_SECONDS = TimeUnit.MILLISECONDS.toNanos(200); private BushidoInternalListener listener; private Lock lock = new ReentrantLock(); private Condition newPressCondition = lock.newCondition(); private BushidoButtonPressDescriptor currentDescriptor; private BushidoButtonPressDescriptor lastDescriptor; private LinkedBlockingQueue<BushidoButtonPressDescriptor> oldDescriptors = new LinkedBlockingQueue<BushidoButtonPressDescriptor>(); private ArrayList<BushidoButtonPressDescriptor> descriptorDrain = new ArrayList<BushidoButtonPressDescriptor>(); private boolean newSubmit = false; public ButtonPressDispatcher(BushidoInternalListener listener) { this.listener = listener; } public void submitButtonPress(BushidoButtonPressDescriptor descriptor) { try { lock.lock(); if (currentDescriptor == null) { currentDescriptor = descriptor; startAwaitSubmissionThread(); return; } ; // notify a button press is active listener.onButtonPressActive(descriptor); lastDescriptor = currentDescriptor; oldDescriptors.add(lastDescriptor); currentDescriptor = descriptor; newSubmit = true; newPressCondition.signalAll(); } finally { lock.unlock(); } } private void processOldDescriptors() { int numberDrained = oldDescriptors.drainTo(descriptorDrain); if (numberDrained < 1) { return; } Iterator<BushidoButtonPressDescriptor> iterator = descriptorDrain.iterator(); BushidoButtonPressDescriptor last = null; while (iterator.hasNext()) { BushidoButtonPressDescriptor current = iterator.next(); iterator.remove(); if (last == null) { last = current; continue; } //check duration is progressing in time if (current.getDuration().ordinal() < last.getDuration().ordinal()) { listener.onButtonPressFinished(last); } else if (current.getDuration() == Duration.SHORT && last.getDuration() == Duration.SHORT) //&& current.getRecievedTimestamp() - last.getRecievedTimestamp() < // INDEPENDENT_MESSAGE_CUTOFF_NANO_SECONDS) { listener.onButtonPressFinished(last); } last = current; } //check very last value if (last != null && currentDescriptor.getDuration().ordinal() < last.getDuration().ordinal()) { listener.onButtonPressFinished(last); } else if (currentDescriptor.getDuration() == Duration.SHORT && last.getDuration() == Duration.SHORT) //&& currentDescriptor.getRecievedTimestamp() - last.getRecievedTimestamp() < // INDEPENDENT_MESSAGE_CUTOFF_NANO_SECONDS) { listener.onButtonPressFinished(last); } } /** * Submits if no other button presses received before timeout * * @return true if submitted, otherwise false */ private boolean awaitNewSubmit() throws InterruptedException { long startTimeStamp = System.nanoTime(); while (!newSubmit) { long currentTimeStamp = System.nanoTime(); long timeLeft = TIMEOUT_NANO_SECONDS - (currentTimeStamp - startTimeStamp); boolean status = newPressCondition.await(timeLeft, TimeUnit.NANOSECONDS); if (!status) { listener.onButtonPressFinished(currentDescriptor); return true; } } processOldDescriptors(); newSubmit = false; return false; } private void startAwaitSubmissionThread() { new Thread() { @Override public void run() { try { lock.lock(); while (!awaitNewSubmit()) ; reset(); } catch (InterruptedException e) { throw new TurboCommunicationException(e); } finally { lock.unlock(); } } }.start(); } private void reset() { currentDescriptor = null; lastDescriptor = null; newSubmit = false; } }