/* ==================================================================
* MockIdTagScanner.java - 11/06/2015 7:52:29 pm
*
* Copyright 2007-2015 SolarNetwork.net Dev Team
*
* 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., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.node.ocpp.mock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import net.solarnetwork.node.ocpp.ChargeSession;
import net.solarnetwork.node.ocpp.ChargeSessionManager;
import net.solarnetwork.node.ocpp.OCPPException;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicToggleSettingSpecifier;
import net.solarnetwork.util.FilterableService;
import net.solarnetwork.util.OptionalService;
import net.solarnetwork.util.StringUtils;
import ocpp.v15.cs.AuthorizationStatus;
/**
* A mock service that listens for
* {@link ChargeSessionManager#EVENT_TOPIC_SOCKET_ACTIVATED} and
* {@link ChargeSessionManager#EVENT_TOPIC_SOCKET_DEACTIVATED} messages to
* simulate a device that provides an OCPP <em>ID tag</em> and initiates or
* completes a charging session. This setup is purely for development testing.
* In the real world some other bundle would be listening for an RFID scan or
* something to provide ID tag values.
*
* @author matt
* @version 1.0
*/
public class MockIdTagScanner implements SettingSpecifierProvider {
private ChargeSessionManager chargeSessionManager;
private OptionalService<EventAdmin> eventAdmin;
private MessageSource messageSource;
private final ExecutorService executor = Executors.newSingleThreadExecutor(); // to kick off the handleEvent() thread
private String idTag = "MockIdTag";
private boolean active = false;
private String socketId = "/socket/mock";
private final Logger log = LoggerFactory.getLogger(getClass());
private final ConcurrentMap<String, String> socketSessionMap = new ConcurrentHashMap<String, String>(
4);
private void handleChargingStatusChange(final boolean pluggedIn) {
EventAdmin ea = (eventAdmin != null ? eventAdmin.service() : null);
if ( ea == null ) {
return;
}
final String socketId = this.socketId;
Map<String, Object> props = Collections
.singletonMap(ChargeSessionManager.EVENT_PROPERTY_SOCKET_ID, (Object) socketId);
ea.postEvent(new Event(pluggedIn ? ChargeSessionManager.EVENT_TOPIC_SOCKET_ACTIVATED
: ChargeSessionManager.EVENT_TOPIC_SOCKET_DEACTIVATED, props));
if ( pluggedIn ) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
String sessionId = chargeSessionManager.initiateChargeSession(idTag, socketId,
null);
socketSessionMap.put(socketId, sessionId);
} catch ( OCPPException e ) {
if ( AuthorizationStatus.CONCURRENT_TX.equals(e.getStatus()) ) {
ChargeSession session = chargeSessionManager.activeChargeSession(socketId);
if ( session != null ) {
socketSessionMap.put(socketId, session.getSessionId());
}
}
} catch ( RuntimeException e ) {
log.error("Error initiating a charge session on socket {}", socketId, e);
}
}
});
} else {
final String sessionId = endSessionOnSocket(socketId);
if ( sessionId == null ) {
log.debug("Unknown session ID for socket {}", socketId);
return;
}
executor.submit(new Runnable() {
@Override
public void run() {
try {
chargeSessionManager.completeChargeSession(idTag, sessionId);
} catch ( RuntimeException e ) {
log.error("Error completing a charge session on socket {}", socketId, e);
}
}
});
}
}
private String endSessionOnSocket(String socketId) {
String sessionId = socketSessionMap.remove(socketId);
if ( sessionId == null ) {
ChargeSession session = chargeSessionManager.activeChargeSession(socketId);
if ( session != null ) {
sessionId = session.getSessionId();
}
}
return sessionId;
}
@Override
public String getSettingUID() {
return "net.solarnetwork.node.ocpp.mock.rfid";
}
@Override
public String getDisplayName() {
return getClass().getSimpleName();
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
MockIdTagScanner defaults = new MockIdTagScanner();
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(8);
results.add(new BasicTitleSettingSpecifier("info", getInfoMessage(Locale.getDefault()), true));
results.add(new BasicToggleSettingSpecifier("active", defaults.active));
results.add(new BasicTextFieldSettingSpecifier("idTag", defaults.idTag));
results.add(new BasicTextFieldSettingSpecifier("socketId", defaults.socketId));
results.add(new BasicTextFieldSettingSpecifier(
"filterableChargeSessionManager.propertyFilters['UID']", "OCPP Central System"));
return results;
}
private String getInfoMessage(Locale locale) {
String sessions = StringUtils.delimitedStringFromMap(socketSessionMap);
return "Active sessions: " + (sessions != null && sessions.length() > 0 ? sessions : "none");
}
public void setChargeSessionManager(ChargeSessionManager chargeSessionManager) {
this.chargeSessionManager = chargeSessionManager;
}
public FilterableService getFilterableChargeSessionManager() {
ChargeSessionManager mgr = chargeSessionManager;
if ( mgr instanceof FilterableService ) {
return (FilterableService) mgr;
}
return null;
}
public void setIdTag(String idTag) {
this.idTag = idTag;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public void setEventAdmin(OptionalService<EventAdmin> eventAdmin) {
this.eventAdmin = eventAdmin;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
if ( active != this.active ) {
this.active = active;
handleChargingStatusChange(active);
}
}
public String getIdTag() {
return idTag;
}
public String getSocketId() {
return socketId;
}
public void setSocketId(String socketId) {
this.socketId = socketId;
}
}