All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ogema.channels.Channel Maven / Gradle / Ivy

/**
 * Copyright 2011-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ogema.channels;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

import org.ogema.channels.Configuration.Type;
import org.ogema.core.application.AppID;
import org.ogema.core.channelmanager.ChannelAccessException;
import org.ogema.core.channelmanager.ChannelConfiguration;
import org.ogema.core.channelmanager.EventType;
import org.ogema.core.channelmanager.driverspi.ChannelLocator;
import org.ogema.core.channelmanager.driverspi.SampledValueContainer;
import org.ogema.core.channelmanager.driverspi.ValueContainer;
import org.ogema.core.channelmanager.measurements.Quality;
import org.ogema.core.channelmanager.measurements.SampledValue;
import org.ogema.core.channelmanager.measurements.Value;
import org.slf4j.LoggerFactory;

class Channel {

	private boolean closed;
	
	private final Driver driver;
	
	private final ChannelLocator channelLocator;
	
	private final Map configurations = new ConcurrentHashMap();
	
	private AtomicReference value = new AtomicReference();

	private ReaderThread readerThread;
	private boolean isListening;

	/** Re-usable List for wrapping SampledValue for one-time channel read */
	private List readList;

	/** Re-usable List for wrapping Value for channel write */
	private List writeList;
	
	Channel(Driver driver, ChannelLocator channelLocator) {
		
		// constructor has no global side-effects
		
		this.driver = driver;
		this.channelLocator = channelLocator;
		
		// set default channel value
		value.set(new SampledValue(null, System.currentTimeMillis(), Quality.BAD));
		
		readList = new ArrayList(1);
		readList.add(new SampledValueContainer(channelLocator));
		
		writeList = new ArrayList(1);
		writeList.add(new ValueContainer(channelLocator, null));
	}

	synchronized void close() {
		closed = true;
		
		configurations.clear();
		
		if (readerThread != null) {
			readerThread.remove(this);
			readerThread = null;
		}
		
		if (isListening) {
			driver.removeListenChannel(this);
			isListening = false;
		}
		
		driver.channelClosed(this);
	}
	
	SampledValue getStoredValue() {
		return value.get();
	}
	
	ChannelLocator getChannelLocator() {
		return channelLocator;
	}

	synchronized ChannelConfiguration addConfiguration(ChannelConfigurationImpl channelConfiguration)
			throws ChannelAccessException {
		try {

			if (closed)
				throw new IllegalStateException("Channel '" + channelLocator + "' has been closed");
			
			// first added configuration
			if (configurations.isEmpty()) {
				driver.channelAdded(channelLocator);
			}
			
			Configuration configuration = configurations.get(channelConfiguration);

			if (configuration == null) {
				configuration = new Configuration(this, channelConfiguration);

				// previously no readerThread
				if (configuration.getType() == Type.POLLED && readerThread == null) {
					readerThread = driver.getReaderThread(this);
				}

				// is this channel already added to the list of listen channels?
				else if (configuration.getType() == Type.LISTEN && !isListening) {
					driver.addListenChannel(this);
					isListening = true;
				}

				configurations.put(channelConfiguration, configuration);

				// update the reader thread waittime after adding the new
				// configuration,
				// because otherwise it might check the wait times before the
				// configuration was added
				if (configuration.getType() == Type.POLLED) {
					readerThread.update();
				}
			}

			return configuration.getConfiguration();
		} finally {
			if (configurations.isEmpty()) {
				try {
					close();
				} catch (Exception e) {
					LoggerFactory.getLogger(getClass()).warn("Failed to close channel after unsuccessful add operation",e);
				}
			}
		}
	}

	synchronized boolean removeConfiguration(ChannelConfiguration configuration) {

		boolean success = false;

		if (closed)
			return false;

		try {
			if (configurations.remove(configuration) != null)
				success = true;

			// check if we still have polled configurations
			if (readerThread != null) {
				boolean needsReaderThread = false;

				for (Configuration c : configurations.values()) {
					if (c.getType() == Type.POLLED) {
						needsReaderThread = true;
						break;
					}
				}

				// don't need to update the reader thread here.
				// If we removed the configuration with the shortest waittime
				// the readerThread now wakes up too early, finds nothing to do
				// and goes to sleep again.

				if (!needsReaderThread) {
					readerThread.remove(this);
					readerThread = null;
				}
			}

			// check if we still need to listen
			if (isListening) {
				boolean needsListening = false;

				for (Configuration c : configurations.values()) {
					if (c.getType() == Type.LISTEN) {
						needsListening = true;
						break;
					}
				}

				if (!needsListening) {
					driver.removeListenChannel(this);
					isListening = false;
				}
			}
		} finally {
			if (configurations.isEmpty()) {
				close();
			}
		}
		
		return success;
	}

	/**
	 * One-time single read of channel.
	 * 
	 * @throws ChannelAccessException
	 */
	void readChannel() throws ChannelAccessException {
		synchronized (readList) {
			readList.get(0).setSampledValue(null);
			
			try {
				driver.readChannels(readList);
			} catch (Exception e) {
				throw new ChannelAccessException("read failed: " + channelLocator, e);
			}
			
			update(readList.get(0));
		}
	}

	/**
	 * One-time single write of channel.
	 * 
	 * @param value
	 * @throws ChannelAccessException
	 */
	void writeChannel(Value value) throws ChannelAccessException {
		synchronized (writeList) {
			writeList.get(0).setValue(value);

			try {	
				driver.writeChannels(writeList);
			} catch (Exception e) {
				throw new ChannelAccessException("write failed: " + channelLocator, e);
			}
		}
	}

	Configuration getConfiguration(ChannelConfiguration configuration) {
		return configurations.get(configuration);
	}
	
	void update(SampledValueContainer svc) {
		EventType type;
		SampledValue newValue;
		SampledValue oldValue;
		
		// no input -> keep old value
		if (svc == null)
			return;
		
		newValue = svc.getSampledValue();
		
		// if there is no new value -> just keep old value
		if (newValue == null)
			return;

		// set new value
		oldValue = value.getAndSet(newValue);
		
		// if there wasn't an old value -> VALUE_CHANGED
		if (oldValue == null) {
			type = EventType.VALUE_CHANGED;
		// an exception for the default startup value -> VALUE_CHANGED
		} else if (oldValue.getValue() == null 
				&& newValue.getValue() != null 
				&& newValue.getQuality() == Quality.GOOD) {
			type = EventType.VALUE_CHANGED;
		// if the quality changed -> QUALITY_CHANGED
		} else if (oldValue.getQuality() == null ? newValue.getQuality() != null : 
				!oldValue.getQuality().equals(newValue.getQuality())) {
			type = EventType.QUALITY_CHANGED;
			
		// if the value changed -> VALUE_CHANGED
		} else if (oldValue.getValue() == null ? newValue.getValue() != null : 
				!oldValue.getValue().equals(newValue.getValue())) {
			type = EventType.VALUE_CHANGED;
		
		// if only the timestamp changed -> UPDATED
		} else {
			type = EventType.UPDATED;
		}
		
		// call listeners
		for (Configuration configuration : configurations.values()) {
			configuration.update(svc, type); // TODO check for null values!
		}
	}
	
	long updateSamplingTime(long currentTime, List containers) {
		
		long result = Long.MAX_VALUE;
		long waittime;
		
		for(Configuration configuration : configurations.values()) {
			waittime = configuration.updateSamplingTime(currentTime, containers);
			
			if (waittime < result)
				result = waittime;
		}
		
		return result;
	}

	synchronized void removeAppID(AppID appID) {
		List list = new ArrayList();
		
		// collect all matching configurations into a list
		for (Configuration current : configurations.values()) {
			if (current.getAppID().equals(appID)) {
				list.add(current.getConfiguration());
			}
		}
		
		// remove all matching configurations
		for (ChannelConfiguration configuration : list) {
			removeConfiguration(configuration);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy