/*
* Copyright 2011-16 Fraunhofer ISE
*
* This file is part of OpenMUC.
* For more information visit http://www.openmuc.org
*
* OpenMUC 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.
*
* OpenMUC 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 OpenMUC. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openmuc.framework.core.datamanager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import org.openmuc.framework.config.ChannelConfig;
import org.openmuc.framework.data.BooleanValue;
import org.openmuc.framework.data.ByteArrayValue;
import org.openmuc.framework.data.ByteValue;
import org.openmuc.framework.data.DoubleValue;
import org.openmuc.framework.data.Flag;
import org.openmuc.framework.data.FloatValue;
import org.openmuc.framework.data.IntValue;
import org.openmuc.framework.data.LongValue;
import org.openmuc.framework.data.Record;
import org.openmuc.framework.data.ShortValue;
import org.openmuc.framework.data.StringValue;
import org.openmuc.framework.data.TypeConversionException;
import org.openmuc.framework.data.Value;
import org.openmuc.framework.data.ValueType;
import org.openmuc.framework.dataaccess.Channel;
import org.openmuc.framework.dataaccess.ChannelState;
import org.openmuc.framework.dataaccess.DataLoggerNotAvailableException;
import org.openmuc.framework.dataaccess.DeviceState;
import org.openmuc.framework.dataaccess.ReadRecordContainer;
import org.openmuc.framework.dataaccess.RecordListener;
import org.openmuc.framework.dataaccess.WriteValueContainer;
import org.openmuc.framework.datalogger.spi.LogChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ChannelImpl implements Channel {
private final static Logger logger = LoggerFactory.getLogger(ChannelImpl.class);
volatile Record latestRecord;
ChannelRecordContainerImpl driverChannel;
volatile ChannelConfigImpl config;
ChannelCollection samplingCollection;
ChannelCollection loggingCollection;
private final Set<RecordListener> listeners = new LinkedHashSet<>();
private final DataManager dataManager;
volatile Object handle;
private Timer timer = null;
private List<Record> futureValues;
public ChannelImpl(DataManager dataManager, ChannelConfigImpl config, ChannelState initState, Flag initFlag,
long currentTime, List<LogChannel> logChannels) {
this.dataManager = dataManager;
this.config = config;
this.futureValues = new ArrayList<>();
if (config.disabled) {
config.state = ChannelState.DISABLED;
latestRecord = new Record(Flag.DISABLED);
}
else if (!config.isListening() && config.samplingInterval < 0) {
config.state = initState;
latestRecord = new Record(Flag.SAMPLING_AND_LISTENING_DISABLED);
}
else {
config.state = initState;
latestRecord = new Record(null, null, initFlag);
}
if (config.loggingInterval != ChannelConfig.LOGGING_INTERVAL_DEFAULT) {
dataManager.addToLoggingCollections(this, currentTime);
logChannels.add(config);
}
}
@Override
public String getId() {
return config.id;
}
@Override
public String getChannelAddress() {
return config.channelAddress;
}
@Override
public String getDescription() {
return config.description;
}
@Override
public String getUnit() {
return config.unit;
}
@Override
public ValueType getValueType() {
return config.valueType;
}
@Override
public double getScalingFactor() {
if (config.scalingFactor == null) {
return 1;
}
return config.scalingFactor;
}
@Override
public int getSamplingInterval() {
return config.samplingInterval;
}
@Override
public int getSamplingTimeOffset() {
return config.samplingTimeOffset;
}
@Override
public int getLoggingInterval() {
return config.loggingInterval;
}
@Override
public int getLoggingTimeOffset() {
return config.loggingTimeOffset;
}
@Override
public String getDriverName() {
return config.deviceParent.driverParent.id;
}
@Override
public String getDeviceAddress() {
return config.deviceParent.deviceAddress;
}
@Override
public String getDeviceName() {
return config.deviceParent.id;
}
@Override
public String getDeviceDescription() {
return config.deviceParent.description;
}
@Override
public ChannelState getChannelState() {
return config.state;
}
@Override
public DeviceState getDeviceState() {
return config.deviceParent.device.getState();
}
@Override
public void addListener(RecordListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
@Override
public void removeListener(RecordListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
@Override
public Record getLatestRecord() {
return latestRecord;
}
@Override
public void setLatestRecord(Record record) {
setNewRecord(record);
}
@Override
public Record getLoggedRecord(long timestamp) throws DataLoggerNotAvailableException, IOException {
List<Record> records = dataManager.getDataLogger().getRecords(config.id, timestamp, timestamp);
if (records.size() > 0) {
return records.get(0);
}
else {
return null;
}
}
@Override
public List<Record> getLoggedRecords(long startTime) throws DataLoggerNotAvailableException, IOException {
return dataManager.getDataLogger().getRecords(config.id, startTime, System.currentTimeMillis());
}
@Override
public List<Record> getLoggedRecords(long startTime, long endTime)
throws DataLoggerNotAvailableException, IOException {
Long currentTime = System.currentTimeMillis();
List<Record> toReturn = dataManager.getDataLogger().getRecords(config.id, startTime, endTime);
for (Record record : futureValues) {
if (record.getTimestamp() >= currentTime) {
if (record.getTimestamp() <= endTime) {
toReturn.add(record);
}
else {
break;
}
}
}
return toReturn;
}
Record setNewRecord(Record record) {
Record convertedRecord = null;
if (record.getFlag() == Flag.VALID) {
Double scalingFactor = config.scalingFactor;
Double scalingOffset = config.valueOffset;
if (scalingFactor != null) {
try {
record = new Record(new DoubleValue(record.getValue().asDouble() * scalingFactor),
record.getTimestamp(), record.getFlag());
} catch (TypeConversionException e) {
logger.error("Unable to apply scaling factor to channel " + config.id
+ " because a TypeConversionError occured.", e);
}
}
if (scalingOffset != null) {
try {
record = new Record(new DoubleValue(record.getValue().asDouble() + scalingOffset),
record.getTimestamp(), record.getFlag());
} catch (TypeConversionException e) {
logger.error("Unable to apply scaling offset to channel " + config.id
+ " because a TypeConversionError occured.", e);
}
}
try {
switch (config.valueType) {
case BOOLEAN:
convertedRecord = new Record(new BooleanValue(record.getValue().asBoolean()), record.getTimestamp(),
record.getFlag());
break;
case BYTE:
convertedRecord = new Record(new ByteValue(record.getValue().asByte()), record.getTimestamp(),
record.getFlag());
break;
case SHORT:
convertedRecord = new Record(new ShortValue(record.getValue().asShort()), record.getTimestamp(),
record.getFlag());
break;
case INTEGER:
convertedRecord = new Record(new IntValue(record.getValue().asInt()), record.getTimestamp(),
record.getFlag());
break;
case LONG:
convertedRecord = new Record(new LongValue(record.getValue().asLong()), record.getTimestamp(),
record.getFlag());
break;
case FLOAT:
convertedRecord = new Record(new FloatValue(record.getValue().asFloat()), record.getTimestamp(),
record.getFlag());
break;
case DOUBLE:
convertedRecord = new Record(new DoubleValue(record.getValue().asDouble()), record.getTimestamp(),
record.getFlag());
break;
case BYTE_ARRAY:
convertedRecord = new Record(new ByteArrayValue(record.getValue().asByteArray()),
record.getTimestamp(), record.getFlag());
break;
case STRING:
convertedRecord = new Record(new StringValue(record.getValue().toString()), record.getTimestamp(),
record.getFlag());
break;
}
} catch (TypeConversionException e) {
logger.error("Unable to convert value to configured value type because a TypeConversionError occured.",
e);
convertedRecord = record;
}
}
else {
convertedRecord = new Record(latestRecord.getValue(), latestRecord.getTimestamp(), record.getFlag());
}
latestRecord = convertedRecord;
notifyListeners();
return convertedRecord;
}
private void notifyListeners() {
if (listeners.size() != 0) {
synchronized (listeners) {
for (RecordListener listener : listeners) {
config.deviceParent.device.dataManager.executor
.execute(new ListenerNotifier(listener, latestRecord));
}
}
}
}
ChannelRecordContainerImpl createChannelRecordContainer() {
return new ChannelRecordContainerImpl(this);
}
void setFlag(Flag flag) {
if (flag != latestRecord.getFlag()) {
latestRecord = new Record(latestRecord.getValue(), latestRecord.getTimestamp(), flag);
notifyListeners();
}
}
public void setNewDeviceState(ChannelState state, Flag flag) {
if (config.disabled) {
config.state = ChannelState.DISABLED;
setFlag(Flag.DISABLED);
}
else if (!config.isListening() && config.samplingInterval < 0) {
config.state = state;
setFlag(Flag.SAMPLING_AND_LISTENING_DISABLED);
}
else {
config.state = state;
setFlag(flag);
}
}
@Override
public Flag write(Value value) {
if (config.deviceParent.driverParent.getId().equals("virtual")) {
setLatestRecord(new Record(value, System.currentTimeMillis()));
return Flag.VALID;
}
CountDownLatch writeTaskFinishedSignal = new CountDownLatch(1);
WriteValueContainerImpl writeValueContainer = new WriteValueContainerImpl(this);
Value adjustedValue = value;
Double valueOffset = config.valueOffset;
Double scalingFactor = config.scalingFactor;
if (valueOffset != null) {
adjustedValue = new DoubleValue(adjustedValue.asDouble() - valueOffset);
}
if (scalingFactor != null) {
adjustedValue = new DoubleValue(adjustedValue.asDouble() / scalingFactor);
}
writeValueContainer.setValue(adjustedValue);
List<WriteValueContainerImpl> writeValueContainerList = new ArrayList<>(1);
writeValueContainerList.add(writeValueContainer);
WriteTask writeTask = new WriteTask(dataManager, config.deviceParent.device, writeValueContainerList,
writeTaskFinishedSignal);
synchronized (dataManager.newWriteTasks) {
dataManager.newWriteTasks.add(writeTask);
}
dataManager.interrupt();
try {
writeTaskFinishedSignal.await();
} catch (InterruptedException e) {
}
latestRecord = new Record(value, System.currentTimeMillis(), writeValueContainer.getFlag());
notifyListeners();
return writeValueContainer.getFlag();
}
@Override
public synchronized void write(List<Record> values) {
this.futureValues = values;
Collections.sort(values, new Comparator<Record>() {
@Override
public int compare(Record o1, Record o2) {
return o1.getTimestamp().compareTo(o2.getTimestamp());
}
});
if (timer != null) {
timer.cancel();
}
timer = new Timer("Timer ChannelImpl " + config.getId());
long currentTimestamp = System.currentTimeMillis();
for (final Record value : futureValues) {
if ((currentTimestamp - value.getTimestamp()) < 1000l) {
timer.schedule(new TimerTask() {
@Override
public void run() {
write(value.getValue());
}
}, new Date(value.getTimestamp()));
}
}
}
@Override
public Record read() {
CountDownLatch readTaskFinishedSignal = new CountDownLatch(1);
ChannelRecordContainerImpl readValueContainer = new ChannelRecordContainerImpl(this);
List<ChannelRecordContainerImpl> readValueContainerList = new ArrayList<>(1);
readValueContainerList.add(readValueContainer);
ReadTask readTask = new ReadTask(dataManager, config.deviceParent.device, readValueContainerList,
readTaskFinishedSignal);
synchronized (dataManager.newReadTasks) {
dataManager.newReadTasks.add(readTask);
}
dataManager.interrupt();
try {
readTaskFinishedSignal.await();
} catch (InterruptedException e) {
}
return setNewRecord(readValueContainer.record);
}
@Override
public boolean isConnected() {
if (config.state == ChannelState.CONNECTED || config.state == ChannelState.SAMPLING
|| config.state == ChannelState.LISTENING) {
return true;
}
return false;
}
@Override
public WriteValueContainer getWriteContainer() {
return new WriteValueContainerImpl(this);
}
@Override
public ReadRecordContainer getReadContainer() {
return new ChannelRecordContainerImpl(this);
}
}