/*
* 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.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import org.openmuc.framework.config.ChannelConfig;
import org.openmuc.framework.data.Flag;
import org.openmuc.framework.dataaccess.ChannelState;
import org.openmuc.framework.dataaccess.DeviceState;
import org.openmuc.framework.datalogger.spi.LogChannel;
import org.openmuc.framework.driver.spi.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Device {
private static final Logger LOGGER = LoggerFactory.getLogger(Device.class);
DeviceConfigImpl deviceConfig;
DataManager dataManager;
private DeviceState state = null;
Connection connection;
private final List<DeviceEvent> eventList;
private final List<DeviceTask> taskList;
DeviceState getState() {
return state;
}
public Device(DataManager dataManager, DeviceConfigImpl deviceConfig, long currentTime,
List<LogChannel> logChannels) {
this.eventList = new LinkedList<>();
this.taskList = new LinkedList<>();
this.dataManager = dataManager;
this.deviceConfig = deviceConfig;
if (deviceConfig.isDisabled()) {
state = DeviceState.DISABLED;
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
channelConfig.channel = new ChannelImpl(dataManager, channelConfig, ChannelState.DISABLED,
Flag.DISABLED, currentTime, logChannels);
}
}
else if (deviceConfig.driverParent.activeDriver == null) {
state = DeviceState.DRIVER_UNAVAILABLE;
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
channelConfig.channel = new ChannelImpl(dataManager, channelConfig, ChannelState.DRIVER_UNAVAILABLE,
Flag.DRIVER_UNAVAILABLE, currentTime, logChannels);
}
}
else {
state = DeviceState.CONNECTING;
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
channelConfig.channel = new ChannelImpl(dataManager, channelConfig, ChannelState.CONNECTING,
Flag.CONNECTING, currentTime, logChannels);
}
}
}
public void configChangedSignal(DeviceConfigImpl newDeviceConfig, long currentTime, List<LogChannel> logChannels) {
DeviceConfigImpl oldDeviceConfig = deviceConfig;
deviceConfig = newDeviceConfig;
newDeviceConfig.device = this;
if (state == DeviceState.DISABLED) {
if (newDeviceConfig.isDisabled()) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DISABLED, ChannelState.DISABLED, Flag.DISABLED,
currentTime, logChannels);
}
else if (deviceConfig.driverParent.activeDriver == null) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DRIVER_UNAVAILABLE, ChannelState.DRIVER_UNAVAILABLE,
Flag.DRIVER_UNAVAILABLE, currentTime, logChannels);
}
else {
setStatesForNewDevice(oldDeviceConfig, DeviceState.CONNECTING, ChannelState.CONNECTING, Flag.CONNECTING,
currentTime, logChannels);
connect();
}
}
else if (state == DeviceState.DRIVER_UNAVAILABLE) {
if (newDeviceConfig.isDisabled()) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DISABLED, ChannelState.DISABLED, Flag.DISABLED,
currentTime, logChannels);
}
else {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DRIVER_UNAVAILABLE, ChannelState.DRIVER_UNAVAILABLE,
Flag.DRIVER_UNAVAILABLE, currentTime, logChannels);
}
}
else if (state == DeviceState.CONNECTING) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.CONNECTING, ChannelState.CONNECTING, Flag.CONNECTING,
currentTime, logChannels);
if (newDeviceConfig.isDisabled()) {
addEvent(DeviceEvent.DISABLED);
}
else if (oldDeviceConfig.isDisabled()) {
eventList.remove(DeviceEvent.DISABLED);
}
}
else if (state == DeviceState.DISCONNECTING) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DISCONNECTING, ChannelState.DISCONNECTING,
Flag.DISCONNECTING, currentTime, logChannels);
if (newDeviceConfig.isDisabled()) {
addEvent(DeviceEvent.DISABLED);
}
else if (oldDeviceConfig.isDisabled()) {
eventList.remove(DeviceEvent.DISABLED);
}
}
else if (state == DeviceState.WAITING_FOR_CONNECTION_RETRY) {
if (newDeviceConfig.isDisabled()) {
setStatesForNewDevice(oldDeviceConfig, DeviceState.DISABLED, ChannelState.DISABLED, Flag.DISABLED,
currentTime, logChannels);
}
else {
setStatesForNewDevice(oldDeviceConfig, DeviceState.WAITING_FOR_CONNECTION_RETRY,
ChannelState.WAITING_FOR_CONNECTION_RETRY, Flag.WAITING_FOR_CONNECTION_RETRY, currentTime,
logChannels);
}
}
else {
if (newDeviceConfig.isDisabled()) {
if (state == DeviceState.CONNECTED) {
eventList.add(DeviceEvent.DISABLED);
// TODO disable all readworkers
setStatesForNewConnectedDevice(oldDeviceConfig, DeviceState.DISCONNECTING,
ChannelState.DISCONNECTING, Flag.DISCONNECTING, currentTime, logChannels);
disconnect();
}
else {
// Adding the disabled event will automatically disconnect the device as soon as the active task is
// finished
eventList.add(DeviceEvent.DISABLED);
// Update channels anyway to update the log channels
updateChannels(oldDeviceConfig, ChannelState.DISCONNECTING, Flag.DISCONNECTING, currentTime,
logChannels);
}
}
else {
updateChannels(oldDeviceConfig, ChannelState.CONNECTED, Flag.NO_VALUE_RECEIVED_YET, currentTime,
logChannels);
}
}
}
private void updateChannels(DeviceConfigImpl oldDeviceConfig, ChannelState channelState, Flag flag,
long currentTime, List<LogChannel> logChannels) {
List<ChannelRecordContainerImpl> listeningChannels = null;
for (Entry<String, ChannelConfigImpl> newChannelConfigEntry : deviceConfig.channelConfigsById.entrySet()) {
ChannelConfigImpl oldChannelConfig = oldDeviceConfig.channelConfigsById.get(newChannelConfigEntry.getKey());
ChannelConfigImpl newChannelConfig = newChannelConfigEntry.getValue();
if (oldChannelConfig == null) {
listeningChannels = initalizeListenChannels(channelState, flag, currentTime, logChannels,
listeningChannels, newChannelConfig);
}
else {
updateConfig(currentTime, logChannels, oldChannelConfig, newChannelConfig);
}
}
if (listeningChannels != null) {
addStartListeningTask(new StartListeningTask(dataManager, this, listeningChannels));
}
}
private void updateConfig(long currentTime, List<LogChannel> logChannels, ChannelConfigImpl oldChannelConfig,
ChannelConfigImpl newChannelConfig) {
newChannelConfig.channel = oldChannelConfig.channel;
newChannelConfig.channel.config = newChannelConfig;
newChannelConfig.channel.setNewDeviceState(oldChannelConfig.state,
newChannelConfig.channel.latestRecord.getFlag());
if (!newChannelConfig.isDisabled() && (newChannelConfig.loggingInterval > 0)) {
dataManager.addToLoggingCollections(newChannelConfig.channel, currentTime);
logChannels.add(newChannelConfig);
}
else if (!oldChannelConfig.disabled && oldChannelConfig.loggingInterval > 0) {
dataManager.removeFromLoggingCollections(newChannelConfig.channel);
}
if (newChannelConfig.isSampling()) {
dataManager.addToSamplingCollections(newChannelConfig.channel, currentTime);
}
else if (oldChannelConfig.isSampling()) {
dataManager.removeFromSamplingCollections(newChannelConfig.channel);
}
if (!newChannelConfig.channelAddress.equals(oldChannelConfig.channelAddress)) {
newChannelConfig.channel.handle = null;
}
}
private List<ChannelRecordContainerImpl> initalizeListenChannels(ChannelState channelState, Flag flag,
long currentTime, List<LogChannel> logChannels, List<ChannelRecordContainerImpl> listeningChannels,
ChannelConfigImpl newChannelConfig) {
if (newChannelConfig.state != ChannelState.DISABLED) {
if (newChannelConfig.listening) {
if (listeningChannels == null) {
listeningChannels = new LinkedList<>();
}
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, ChannelState.LISTENING,
Flag.NO_VALUE_RECEIVED_YET, currentTime, logChannels);
listeningChannels.add(newChannelConfig.channel.createChannelRecordContainer());
}
else if (newChannelConfig.samplingInterval != ChannelConfig.SAMPLING_INTERVAL_DEFAULT) {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, ChannelState.SAMPLING,
Flag.NO_VALUE_RECEIVED_YET, currentTime, logChannels);
dataManager.addToSamplingCollections(newChannelConfig.channel, currentTime);
}
else {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, channelState, flag,
currentTime, logChannels);
}
}
else {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, channelState, flag, currentTime,
logChannels);
}
return listeningChannels;
}
private void addEvent(DeviceEvent event) {
Iterator<DeviceEvent> i = eventList.iterator();
while (i.hasNext()) {
if (i.next() == event) {
return;
}
}
eventList.add(event);
}
private void setStatesForNewDevice(DeviceConfigImpl oldDeviceConfig, DeviceState DeviceState,
ChannelState channelState, Flag flag, long currentTime, List<LogChannel> logChannels) {
state = DeviceState;
for (Entry<String, ChannelConfigImpl> newChannelConfigEntry : deviceConfig.channelConfigsById.entrySet()) {
ChannelConfigImpl oldChannelConfig = oldDeviceConfig.channelConfigsById.get(newChannelConfigEntry.getKey());
if (oldChannelConfig == null) {
newChannelConfigEntry.getValue().channel = new ChannelImpl(dataManager,
newChannelConfigEntry.getValue(), channelState, flag, currentTime, logChannels);
}
else {
newChannelConfigEntry.getValue().channel = oldChannelConfig.channel;
newChannelConfigEntry.getValue().channel.config = newChannelConfigEntry.getValue();
newChannelConfigEntry.getValue().channel.setNewDeviceState(channelState, flag);
if (!newChannelConfigEntry.getValue().isDisabled()
&& (newChannelConfigEntry.getValue().loggingInterval > 0)) {
dataManager.addToLoggingCollections(newChannelConfigEntry.getValue().channel, currentTime);
logChannels.add(newChannelConfigEntry.getValue());
}
}
}
}
private void setStatesForNewConnectedDevice(DeviceConfigImpl oldDeviceConfig, DeviceState DeviceState,
ChannelState channelState, Flag flag, long currentTime, List<LogChannel> logChannels) {
state = DeviceState;
List<ChannelRecordContainerImpl> listeningChannels = null;
for (Entry<String, ChannelConfigImpl> newChannelConfigEntry : deviceConfig.channelConfigsById.entrySet()) {
ChannelConfigImpl oldChannelConfig = oldDeviceConfig.channelConfigsById.get(newChannelConfigEntry.getKey());
ChannelConfigImpl newChannelConfig = newChannelConfigEntry.getValue();
if (oldChannelConfig == null) {
if (newChannelConfig.state != ChannelState.DISABLED) {
if (newChannelConfig.listening == true) {
if (listeningChannels == null) {
listeningChannels = new LinkedList<>();
}
listeningChannels.add(newChannelConfig.channel.createChannelRecordContainer());
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig,
ChannelState.LISTENING, Flag.NO_VALUE_RECEIVED_YET, currentTime, logChannels);
}
else if (newChannelConfig.samplingInterval != ChannelConfig.SAMPLING_INTERVAL_DEFAULT) {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, ChannelState.SAMPLING,
Flag.NO_VALUE_RECEIVED_YET, currentTime, logChannels);
dataManager.addToSamplingCollections(newChannelConfig.channel, currentTime);
}
else {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, channelState, flag,
currentTime, logChannels);
}
}
else {
newChannelConfig.channel = new ChannelImpl(dataManager, newChannelConfig, channelState, flag,
currentTime, logChannels);
}
}
else {
newChannelConfig.channel = oldChannelConfig.channel;
newChannelConfig.channel.config = newChannelConfig;
newChannelConfig.channel.setNewDeviceState(channelState, flag);
if (!newChannelConfigEntry.getValue().isDisabled()
&& (newChannelConfigEntry.getValue().loggingInterval > 0)) {
dataManager.addToLoggingCollections(newChannelConfig.channel, currentTime);
logChannels.add(newChannelConfigEntry.getValue());
}
}
}
}
private void setStates(DeviceState DeviceState, ChannelState channelState, Flag flag) {
state = DeviceState;
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
if (channelConfig.state != ChannelState.DISABLED) {
channelConfig.state = channelState;
if (channelConfig.channel.latestRecord.getFlag() != Flag.SAMPLING_AND_LISTENING_DISABLED) {
channelConfig.channel.setFlag(flag);
}
}
}
}
void driverRegisteredSignal() {
if (state == DeviceState.DRIVER_UNAVAILABLE) {
setStates(DeviceState.CONNECTING, ChannelState.CONNECTING, Flag.CONNECTING);
connect();
}
else if (state == DeviceState.DISCONNECTING) {
eventList.add(DeviceEvent.DRIVER_REGISTERED);
}
}
void driverDeregisteredSignal() {
if (state == DeviceState.DISABLED) {
dataManager.activeDeviceCountDown--;
if (dataManager.activeDeviceCountDown == 0) {
dataManager.driverRemovedSignal.countDown();
}
}
else if (state == DeviceState.CONNECTED) {
eventList.add(0, DeviceEvent.DRIVER_DEREGISTERED);
disableSampling();
removeAllTasksOfThisDevice();
setStates(DeviceState.DISCONNECTING, ChannelState.DISCONNECTING, Flag.DISCONNECTING);
disconnect();
}
else if (state == DeviceState.WAITING_FOR_CONNECTION_RETRY) {
disableConnectionRetry();
setStates(DeviceState.DRIVER_UNAVAILABLE, ChannelState.DRIVER_UNAVAILABLE, Flag.DRIVER_UNAVAILABLE);
dataManager.activeDeviceCountDown--;
if (dataManager.activeDeviceCountDown == 0) {
dataManager.driverRemovedSignal.countDown();
}
}
else {
// add driver deregistered event always to the front of the queue
eventList.add(0, DeviceEvent.DRIVER_DEREGISTERED);
}
}
public void deleteSignal() {
if (state == DeviceState.DRIVER_UNAVAILABLE || state == DeviceState.DISABLED) {
setDeleted();
}
else if (state == DeviceState.WAITING_FOR_CONNECTION_RETRY) {
disableConnectionRetry();
setDeleted();
}
else if (state == DeviceState.CONNECTED) {
eventList.add(DeviceEvent.DELETED);
setStates(DeviceState.DISCONNECTING, ChannelState.DISCONNECTING, Flag.DISCONNECTING);
disconnect();
}
else {
eventList.add(DeviceEvent.DELETED);
}
}
void connectedSignal(long currentTime) {
taskList.remove(0);
if (eventList.size() == 0) {
setConnected(currentTime);
executeNextTask();
}
else {
handleEventQueueWhenConnected();
}
}
void connectFailureSignal(long currentTime) {
taskList.remove(0);
if (eventList.size() == 0) {
setStates(DeviceState.WAITING_FOR_CONNECTION_RETRY, ChannelState.WAITING_FOR_CONNECTION_RETRY,
Flag.WAITING_FOR_CONNECTION_RETRY);
dataManager.addReconnectDeviceToActions(this, currentTime + deviceConfig.getConnectRetryInterval());
removeAllTasksOfThisDevice();
}
else {
handleEventQueueWhenDisconnected();
}
}
// TODO is this function thread save?
public void disconnectedSignal() {
// TODO in rare cases where the RecordsReceivedListener causes the disconnectSignal while a SamplingTask is
// still sampling this could cause problems
synchronized (this) {
removeAllTasksOfThisDevice();
if (eventList.isEmpty()) {
setStates(DeviceState.CONNECTING, ChannelState.CONNECTING, Flag.CONNECTING);
connect();
}
else {
handleEventQueueWhenDisconnected();
}
}
}
public void connectRetrySignal() {
setStates(DeviceState.CONNECTING, ChannelState.CONNECTING, Flag.CONNECTING);
connect();
}
private void disableConnectionRetry() {
dataManager.removeFromConnectionRetry(this);
}
private void setDeleted() {
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
channelConfig.state = ChannelState.DELETED;
channelConfig.channel.setFlag(Flag.CHANNEL_DELETED);
channelConfig.channel.handle = null;
}
state = DeviceState.DELETED;
}
private void disableSampling() {
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
if (channelConfig.state != ChannelState.DISABLED) {
if (channelConfig.state == ChannelState.SAMPLING) {
dataManager.removeFromSamplingCollections(channelConfig.channel);
}
}
}
}
private void handleEventQueueWhenConnected() {
removeAllTasksOfThisDevice();
setStates(DeviceState.DISCONNECTING, ChannelState.DISCONNECTING, Flag.DISCONNECTING);
disconnect();
}
private void removeAllTasksOfThisDevice() {
Iterator<DeviceTask> devTaskIter = taskList.iterator();
while (devTaskIter.hasNext()) {
DeviceTask deviceTask = devTaskIter.next();
if (deviceTask.device == this) {
devTaskIter.remove();
}
}
if (!taskList.isEmpty()) {
if (!taskList.get(0).isAlive()) {
taskList.get(0).device.executeNextTask();
}
}
}
private void handleEventQueueWhenDisconnected() {
// DeviceEvent.DRIVER_DEREGISTERED will always be put at position 0
if (eventList.get(0) == DeviceEvent.DRIVER_DEREGISTERED) {
synchronized (dataManager.driverRemovedSignal) {
dataManager.activeDeviceCountDown--;
if (dataManager.activeDeviceCountDown == 0) {
dataManager.driverRemovedSignal.countDown();
}
}
}
DeviceEvent lastEvent = eventList.get(eventList.size() - 1);
if (lastEvent == DeviceEvent.DRIVER_DEREGISTERED) {
setStates(DeviceState.DRIVER_UNAVAILABLE, ChannelState.DRIVER_UNAVAILABLE, Flag.DRIVER_UNAVAILABLE);
}
else if (lastEvent == DeviceEvent.DISABLED) {
setStates(DeviceState.DISABLED, ChannelState.DISABLED, Flag.DISABLED);
}
else if (lastEvent == DeviceEvent.DELETED) {
setDeleted();
}
// TODO handle DeviceEvent.DRIVER_REGISTERED?
eventList.clear();
}
private void setConnected(long currentTime) {
List<ChannelRecordContainerImpl> listeningChannels = null;
for (ChannelConfigImpl channelConfig : deviceConfig.channelConfigsById.values()) {
if (channelConfig.state != ChannelState.DISABLED) {
if (channelConfig.listening == true) {
if (listeningChannels == null) {
listeningChannels = new LinkedList<>();
}
listeningChannels.add(channelConfig.channel.createChannelRecordContainer());
channelConfig.state = ChannelState.LISTENING;
channelConfig.channel.setFlag(Flag.NO_VALUE_RECEIVED_YET);
}
else if (channelConfig.samplingInterval != ChannelConfig.SAMPLING_INTERVAL_DEFAULT) {
dataManager.addToSamplingCollections(channelConfig.channel, currentTime);
channelConfig.state = ChannelState.SAMPLING;
channelConfig.channel.setFlag(Flag.NO_VALUE_RECEIVED_YET);
}
else {
channelConfig.state = ChannelState.CONNECTED;
}
}
}
if (listeningChannels != null) {
taskList.add(new StartListeningTask(dataManager, this, listeningChannels));
state = DeviceState.STARTING_TO_LISTEN;
}
else {
state = DeviceState.CONNECTED;
}
}
private void connect() {
ConnectTask connectTask = new ConnectTask(deviceConfig.driverParent.activeDriver, deviceConfig.device,
dataManager);
taskList.add(connectTask);
if (taskList.size() == 1) {
dataManager.executor.execute(connectTask);
}
}
private void disconnect() {
DisconnectTask disconnectTask = new DisconnectTask(deviceConfig.driverParent.activeDriver, deviceConfig.device,
dataManager);
taskList.add(disconnectTask);
if (taskList.size() == 1) {
dataManager.executor.execute(disconnectTask);
}
}
// only called by main thread
public boolean addSamplingTask(SamplingTask samplingTask, int samplingInterval) {
if (isConnected()) {
// new
// if (deviceConfig.readTimeout == 0 || deviceConfig.readTimeout > samplingInterval) {
// if (taskList.size() > 0) {
// if (taskList.get(0).getType() == DeviceTaskType.READ) {
// ((GenReadTask) taskList.get(0)).startedLate = true;
// }
// }
// }
// new
taskList.add(samplingTask);
if (taskList.size() == 1) {
samplingTask.running = true;
state = DeviceState.READING;
dataManager.executor.execute(samplingTask);
}
return true;
}
else {
samplingTask.deviceNotConnected();
// TODO in the future change this to true
return true;
}
}
public void addReadTask(ReadTask readTask) {
addTask(readTask);
}
private <T extends DeviceTask & ConnectedTask> void addTask(T deviceTask) {
if (isConnected()) {
taskList.add(deviceTask);
if (taskList.size() == 1) {
state = deviceTask.getType().getResultingState();
dataManager.executor.execute(deviceTask);
}
}
else {
deviceTask.deviceNotConnected();
}
}
public void taskFinished() {
taskList.remove(0);
if (eventList.isEmpty()) {
executeNextTask();
}
else {
handleEventQueueWhenConnected();
}
}
private void executeNextTask() {
if (!taskList.isEmpty()) {
if (taskList.get(0).getType() == DeviceTaskType.SAMPLE) {
((SamplingTask) taskList.get(0)).startedLate = true;
}
state = taskList.get(0).getType().getResultingState();
dataManager.executor.execute(taskList.get(0));
}
else {
state = DeviceState.CONNECTED;
}
}
public void removeTask(SamplingTask samplingTask) {
taskList.remove(samplingTask);
}
public void addWriteTask(WriteTask writeTask) {
addTask(writeTask);
}
public void addStartListeningTask(StartListeningTask startListenTask) {
if (isConnected()) {
taskList.add(startListenTask);
if (taskList.size() == 1) {
state = DeviceState.STARTING_TO_LISTEN;
dataManager.executor.execute(startListenTask);
}
}
}
public boolean isConnected() {
return state == DeviceState.CONNECTED || state == DeviceState.READING
|| state == DeviceState.SCANNING_FOR_CHANNELS || state == DeviceState.STARTING_TO_LISTEN
|| state == DeviceState.WRITING;
}
}