/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2015 RomRaider.com
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.romraider.logger.ecu.comms.manager;
import static com.romraider.logger.ecu.comms.io.connection.LoggerConnectionFactory.getConnection;
import static com.romraider.logger.ecu.definition.EcuDataType.EXTERNAL;
import static com.romraider.util.ParamChecker.checkNotNull;
import static com.romraider.util.ThreadUtil.runAsDaemon;
import static com.romraider.util.ThreadUtil.sleep;
import static java.lang.System.currentTimeMillis;
import static java.util.Collections.synchronizedList;
import static java.util.Collections.synchronizedMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import com.romraider.Settings;
import com.romraider.logger.ecu.comms.io.connection.LoggerConnection;
import com.romraider.logger.ecu.comms.query.EcuInitCallback;
import com.romraider.logger.ecu.comms.query.EcuQuery;
import com.romraider.logger.ecu.comms.query.EcuQueryImpl;
import com.romraider.logger.ecu.comms.query.ExternalQuery;
import com.romraider.logger.ecu.comms.query.ExternalQueryImpl;
import com.romraider.logger.ecu.comms.query.Query;
import com.romraider.logger.ecu.comms.query.Response;
import com.romraider.logger.ecu.comms.query.ResponseImpl;
import com.romraider.logger.ecu.definition.EcuData;
import com.romraider.logger.ecu.definition.ExternalData;
import com.romraider.logger.ecu.definition.LoggerData;
import com.romraider.logger.ecu.definition.Module;
import com.romraider.logger.ecu.ui.MessageListener;
import com.romraider.logger.ecu.ui.StatusChangeListener;
import com.romraider.logger.ecu.ui.handler.DataUpdateHandler;
import com.romraider.logger.ecu.ui.handler.file.FileLoggerControllerSwitchMonitor;
import com.romraider.util.SettingsManager;
public final class QueryManagerImpl implements QueryManager {
private static final Logger LOGGER = Logger.getLogger(QueryManagerImpl.class);
private final List<StatusChangeListener> listeners =
synchronizedList(new ArrayList<StatusChangeListener>());
private final Map<String, Query> queryMap =
synchronizedMap(new HashMap<String, Query>());
private final Map<String, Query> addList = new HashMap<String, Query>();
private final List<String> removeList = new ArrayList<String>();
private static final PollingState pollState = new PollingStateImpl();
private static final Settings settings = SettingsManager.getSettings();
private static final String EXT = "Externals";
private final EcuInitCallback ecuInitCallback;
private final MessageListener messageListener;
private final DataUpdateHandler[] dataUpdateHandlers;
private FileLoggerControllerSwitchMonitor monitor;
private EcuQuery fileLoggerQuery;
private Thread queryManagerThread;
private static boolean started;
private static boolean stop;
public QueryManagerImpl(EcuInitCallback ecuInitCallback,
MessageListener messageListener,
DataUpdateHandler... dataUpdateHandlers) {
checkNotNull(ecuInitCallback,
messageListener,
dataUpdateHandlers);
this.ecuInitCallback = ecuInitCallback;
this.messageListener = messageListener;
this.dataUpdateHandlers = dataUpdateHandlers;
stop = true;
}
@Override
public synchronized void addListener(StatusChangeListener listener) {
checkNotNull(listener, "listener");
listeners.add(listener);
}
@Override
public void setFileLoggerSwitchMonitor(FileLoggerControllerSwitchMonitor monitor) {
checkNotNull(monitor);
this.monitor = monitor;
fileLoggerQuery = new EcuQueryImpl(monitor.getEcuSwitch());
}
@Override
public synchronized void addQuery(String callerId, LoggerData loggerData) {
checkNotNull(callerId, loggerData);
//FIXME: This is a hack!!
String queryId = buildQueryId(callerId, loggerData);
if (loggerData.getDataType() == EXTERNAL) {
addList.put(queryId, new ExternalQueryImpl((ExternalData) loggerData));
} else {
addList.put(queryId, new EcuQueryImpl((EcuData) loggerData));
pollState.setLastQuery(false);
pollState.setNewQuery(true);
}
}
@Override
public synchronized void removeQuery(String callerId, LoggerData loggerData) {
checkNotNull(callerId, loggerData);
removeList.add(buildQueryId(callerId, loggerData));
if (loggerData.getDataType() != EXTERNAL) {
pollState.setNewQuery(true);
}
}
@Override
public Thread getThread() {
return queryManagerThread;
}
@Override
public boolean isRunning() {
return started && !stop;
}
@Override
public void run() {
started = true;
queryManagerThread = Thread.currentThread();
LOGGER.debug("QueryManager started.");
try {
stop = false;
while (!stop) {
notifyConnecting();
if (!settings.isLogExternalsOnly() &&
doEcuInit(settings.getDestinationTarget())) {
notifyReading();
runLogger(settings.getDestinationTarget());
} else if (settings.isLogExternalsOnly()) {
notifyReading();
runLogger(null);
} else {
sleep(1000L);
}
}
} catch (Exception e) {
messageListener.reportError(e);
} finally {
notifyStopped();
messageListener.reportMessage("Disconnected.");
LOGGER.debug("QueryManager stopped.");
}
}
private boolean doEcuInit(Module module) {
try {
LoggerConnection connection =
getConnection(settings.getLoggerProtocol(),
settings.getLoggerPort(),
settings.getLoggerConnectionProperties());
try {
messageListener.reportMessage("Sending " + module.getName() + " Init...");
connection.ecuInit(ecuInitCallback, module);
messageListener.reportMessage("Sending " + module.getName() + " Init...done.");
return true;
} finally {
connection.close();
}
} catch (Exception e) {
messageListener.reportMessage("Unable to send " + module.getName() +
" init - check cable is connected and ignition is on.");
logError(e);
return false;
}
}
private void logError(Exception e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Error sending init", e);
} else {
LOGGER.info("Error sending init: " + e.getMessage());
}
}
private void runLogger(Module module) {
String moduleName = null;
if (module == null){
moduleName = EXT;
}
else {
moduleName = module.getName();
}
TransmissionManager txManager = new TransmissionManagerImpl();
long start = currentTimeMillis();
long end = currentTimeMillis();
int count = 0;
try {
txManager.start();
boolean lastPollState = settings.isFastPoll();
while (!stop) {
pollState.setFastPoll(settings.isFastPoll());
updateQueryList();
if (queryMap.isEmpty()) {
if (pollState.isLastQuery() &&
pollState.getCurrentState() == PollingState.State.STATE_0) {
endEcuQueries(txManager);
pollState.setLastState(PollingState.State.STATE_0);
}
start = System.currentTimeMillis();
count = 0;
messageListener.reportMessage("Select parameters to be logged...");
sleep(1000L);
} else {
end = currentTimeMillis() + 1L; // update once every 1msec
final List<EcuQuery> ecuQueries =
filterEcuQueries(queryMap.values());
if (!settings.isLogExternalsOnly()) {
if (!ecuQueries.isEmpty()) {
sendEcuQueries(txManager);
if (!pollState.isFastPoll() && lastPollState) {
endEcuQueries(txManager);
}
if (pollState.isFastPoll()) {
if (pollState.getCurrentState() == PollingState.State.STATE_0 &&
pollState.isNewQuery()) {
pollState.setCurrentState(PollingState.State.STATE_1);
pollState.setNewQuery(false);
}
if (pollState.getCurrentState() == PollingState.State.STATE_0 &&
!pollState.isNewQuery()) {
pollState.setCurrentState(PollingState.State.STATE_1);
}
if (pollState.getCurrentState() == PollingState.State.STATE_1 &&
pollState.isNewQuery()) {
pollState.setCurrentState(PollingState.State.STATE_0);
pollState.setLastState(PollingState.State.STATE_1);
pollState.setNewQuery(false);
}
if (pollState.getCurrentState() == PollingState.State.STATE_1 &&
!pollState.isNewQuery()) {
pollState.setLastState(PollingState.State.STATE_1);
}
pollState.setLastQuery(true);
}
else {
pollState.setCurrentState(PollingState.State.STATE_0);
pollState.setLastState(PollingState.State.STATE_0);
pollState.setNewQuery(false);
}
lastPollState = pollState.isFastPoll();
}
else {
if (pollState.isLastQuery() &&
pollState.getLastState() == PollingState.State.STATE_1) {
endEcuQueries(txManager);
pollState.setLastState(PollingState.State.STATE_0);
pollState.setCurrentState(PollingState.State.STATE_0);
pollState.setNewQuery(true);
}
}
}
sendExternalQueries();
// waiting until at least 1msec has passed since last query set
while (currentTimeMillis() < end) {
sleep(1L);
}
handleQueryResponse();
count++;
messageListener.reportMessage("Querying " + moduleName + "...");
messageListener.reportStats(buildStatsMessage(start, count));
}
}
} catch (Exception e) {
messageListener.reportError(e);
} finally {
txManager.stop();
pollState.setCurrentState(PollingState.State.STATE_0);
pollState.setNewQuery(true);
}
}
private void sendEcuQueries(TransmissionManager txManager) {
final List<EcuQuery> ecuQueries = filterEcuQueries(queryMap.values());
if (fileLoggerQuery != null
&& settings.isFileLoggingControllerSwitchActive())
ecuQueries.add(fileLoggerQuery);
txManager.sendQueries(ecuQueries, pollState);
}
private void sendExternalQueries() {
final List<ExternalQuery> externalQueries =
filterExternalQueries(queryMap.values());
for (ExternalQuery externalQuery : externalQueries) {
//FIXME: This is a hack!!
externalQuery.setResponse(
externalQuery.getLoggerData().getSelectedConvertor().convert(null));
}
}
private void endEcuQueries(TransmissionManager txManager) {
txManager.endQueries();
pollState.setLastQuery(false);
}
private void handleQueryResponse() {
if (settings.isFileLoggingControllerSwitchActive())
monitor.monitorFileLoggerSwitch(fileLoggerQuery.getResponse());
final Response response = buildResponse(queryMap.values());
for (final DataUpdateHandler dataUpdateHandler : dataUpdateHandlers) {
runAsDaemon(new Runnable() {
@Override
public void run() {
dataUpdateHandler.handleDataUpdate(response);
}
});
}
}
private Response buildResponse(Collection<Query> queries) {
final Response response = new ResponseImpl();
for (final Query query : queries) {
response.setDataValue(query.getLoggerData(), query.getResponse());
}
return response;
}
//FIXME: This is a hack!!
private List<EcuQuery> filterEcuQueries(Collection<Query> queries) {
List<EcuQuery> filtered = new ArrayList<EcuQuery>();
for (Query query : queries) {
if (EcuQuery.class.isAssignableFrom(query.getClass())) {
filtered.add((EcuQuery) query);
}
}
return filtered;
}
//FIXME: This is a hack!!
private List<ExternalQuery> filterExternalQueries(Collection<Query> queries) {
List<ExternalQuery> filtered = new ArrayList<ExternalQuery>();
for (Query query : queries) {
if (ExternalQuery.class.isAssignableFrom(query.getClass())) {
filtered.add((ExternalQuery) query);
}
}
return filtered;
}
@Override
public void stop() {
stop = true;
}
private String buildQueryId(String callerId, LoggerData loggerData) {
return callerId + "_" + loggerData.getName();
}
private synchronized void updateQueryList() {
addQueries();
removeQueries();
}
private void addQueries() {
for (String queryId : addList.keySet()) {
queryMap.put(queryId, addList.get(queryId));
}
addList.clear();
}
private void removeQueries() {
for (String queryId : removeList) {
queryMap.remove(queryId);
}
removeList.clear();
}
private String buildStatsMessage(long start, int count) {
String state = String.format("%s Slow-K:",settings.getLoggerProtocol());
if (pollState.isFastPoll()) {
state = String.format("%s Fast-K:",settings.getLoggerProtocol());
}
if (settings.getTransportProtocol().equals("ISO15765")) {
state = String.format("%s CAN bus:",settings.getLoggerProtocol());
}
if (settings.isLogExternalsOnly()) {
state = "Externals:";
}
double duration = (System.currentTimeMillis() - start) / 1000.0;
String result = String.format(
"%s[ %.2f queries/sec, %.2f sec/query ]",
state,
(count) / duration,
duration / (count)
);
return result;
}
private void notifyConnecting() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for (StatusChangeListener listener : listeners) {
listener.connecting();
}
}
});
}
private void notifyReading() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for (StatusChangeListener listener : listeners) {
listener.readingData();
}
}
});
}
private void notifyStopped() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for (StatusChangeListener listener : listeners) {
listener.stopped();
}
}
});
}
}