/* * Copyright (C) 2012, Katy Hilgenberg. * Special acknowledgments to: Knowledge & Data Engineering Group, University of Kassel (http://www.kde.cs.uni-kassel.de). * Contact: sdcf@cs.uni-kassel.de * * This file is part of the SDCFramework (Sensor Data Collection Framework) project. * * The SDCFramework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The SDCFramework 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SDCFramework. If not, see <http://www.gnu.org/licenses/>. */ package de.unikassel.android.sdcframework.transmission; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import android.content.Context; import android.os.SystemClock; import de.unikassel.android.sdcframework.preferences.facade.TransmissionConfiguration; import de.unikassel.android.sdcframework.transmission.facade.SampleRateChangeResponder; import de.unikassel.android.sdcframework.transmission.facade.UpdatableTransmissionComponent; import de.unikassel.android.sdcframework.util.Logger; /** * A class to realize a controller for the task to select samples from the database for transfer. <br/> * <br/> * It does control the available sample count in database and calculates the * necessary wait time to reach the required minimum sample count. <br/> * The available count of samples for is limited by a configurable minimum and * maximum. <br/> * The wait time calculation does depend on the current sample rate and the * required minimum of samples. * * @author Katy Hilgenberg * */ public class SampleGatheringController implements UpdatableTransmissionComponent<TransmissionConfiguration>, SampleRateChangeResponder { /** * The amplification factor */ private static final float AF = 1.01F; /** * The upper limit for wait times in milliseconds */ private static final long MAX_FREQUENCY = 36000000L; /** * The lower limit for wait times in milliseconds */ private static final long MIN_FREQUENCY = 1000L; /** * The delay for sample rate calculation in case of a rate change */ private static final long SAMPLE_RATE_DELAY = 60000L; /** * The internal time stamp of last wait time calculation */ private final AtomicLong lastTimeStamp; /** * The minimum sample count to transfer */ private final AtomicInteger minSampleCount; /** * The maximum sample count to transfer */ private final AtomicInteger maxSampleCount; /** * The available sample count in database */ private final AtomicLong availableSampleCount; /** * The last record count in database */ private long lastRecordCount; /** * Flag indicating a sample rate change */ private boolean sampleRateHasChanged; /** * Constructor */ public SampleGatheringController() { this.lastTimeStamp = new AtomicLong(); this.minSampleCount = new AtomicInteger(); this.maxSampleCount = new AtomicInteger(); this.availableSampleCount = new AtomicLong(); } /** * Does reset all values * * @param currentRecordCount * the current record count in database */ public synchronized final void reset( long currentRecordCount ) { sampleRateHasChanged = false; updateFields( currentRecordCount, SystemClock.elapsedRealtime() ); } /** * Getter for the minimum sample count to transfer * * @return the minimum sample count to transfer */ public final int getMinSampleCount() { return minSampleCount.get(); } /** * Getter for the maximum sample count to transfer * * @return the maximum sample count to transfer */ public final int getMaxSampleCount() { return maxSampleCount.get(); } /** * Getter for the count of available samples * * @return the count of available samples */ public final long getAvailableSampleCount() { return availableSampleCount.get(); } /** * Method to consume available samples */ public final void consumAvailableSamples() { availableSampleCount.set( 0L ); } /* * (non-Javadoc) * * @see de.unikassel.android.sdcframework.transmission.facade. * UpdatableTransmissionComponent#updateConfiguration(android.content.Context, * de * .unikassel.android.sdcframework.preferences.facade.TransmissionConfiguration * ) */ @Override public final void updateConfiguration( Context context, TransmissionConfiguration config ) { int minSampleCount = config.getMinSampleTransferCount(); this.minSampleCount.set( minSampleCount ); int maxSampleCount = Math.max( minSampleCount, config.getMaxSampleTransferCount() ); this.maxSampleCount.set( maxSampleCount ); } /** * Does calculate the necessary wait time depending on the current record * count in database * * @param currentRecordCount * the current record count in database * @return the current wait time */ public synchronized final long calculatetWaitTime( long currentRecordCount ) { // first determine current sample rate long timeStamp = SystemClock.elapsedRealtime(); long timeInterval = timeStamp - lastTimeStamp.get(); long sampleIncrement = currentRecordCount - ( lastRecordCount + availableSampleCount.get() ); // now update internal fields updateFields( currentRecordCount, timeStamp ); // calculate the required wait time for a minimum of available samples long waitTime = calculateNewWaitTime( sampleIncrement, timeInterval ); // clear rate changed flag sampleRateHasChanged = false; float sampleRate = sampleIncrement; sampleRate /= timeInterval; Logger.getInstance().debug( this, "available sampels = " + availableSampleCount.get() + ", sample rate = " + ( sampleRate * 1000 ) + "/s, current wait time = " + waitTime + " ms" ); return waitTime; } /** * Method to calculate an updated time to wait for samples * * @param sampleIncrement * the current sample increment * @param timeInterval * the current time interval the current error * @return the new calculated time in milliseconds */ private final long calculateNewWaitTime( long sampleIncrement, long timeInterval ) { long currentWaitTime = 0L; long missingSampleCount = getMissingSampleCount(); if ( sampleRateHasChanged ) { // after a sample rate change we need a new cycle for a better sample rate // estimation currentWaitTime = SAMPLE_RATE_DELAY; // clear missing sample count to avoid error correction in next turn missingSampleCount = 0L; } else { if ( missingSampleCount > 0L ) { // avoid division by zero and allow a growing wait time in case of missing increment sampleIncrement = Math.max( sampleIncrement, 1 ); // calculate new wait time and clip to range long waitTime = (long) ( ( missingSampleCount * AF * timeInterval ) / sampleIncrement ); currentWaitTime = Math.max( Math.min( waitTime, MAX_FREQUENCY ), MIN_FREQUENCY ); } } return currentWaitTime; } /** * Method to determine the current missing sample count * * @return the current missing sample count */ public final long getMissingSampleCount() { return Math.max( 0L, minSampleCount.get() - availableSampleCount.get() ); } /** * Method to calculate the available sample count for collecting task * * @param currentRecordCount * the current record count in database * @param timeStamp * the current time stamp */ private final void updateFields( long currentRecordCount, long timeStamp ) { // update available sample count long cnt = Math.max( 0L, Math.min( maxSampleCount.get(), currentRecordCount ) ); availableSampleCount.set( cnt ); // store the record count reduced by the available samples, which // can be consumed by the controlled transfer thread lastRecordCount = currentRecordCount - availableSampleCount.get(); // update time stamp lastTimeStamp.set( timeStamp ); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.transmission.facade.SampleRateChangeResponder * #onSampleRateChanged() */ @Override public synchronized final void onSampleRateChanged() { sampleRateHasChanged= true; } }