/* ==================================================================
* ChargePointService_v15.java - 13/06/2015 5:17:45 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.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.jws.WebService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import net.solarnetwork.node.SystemService;
import net.solarnetwork.node.ocpp.ChargeConfiguration;
import net.solarnetwork.node.ocpp.ChargeConfigurationDao;
import net.solarnetwork.node.ocpp.ChargeSession;
import net.solarnetwork.node.ocpp.ChargeSessionManager;
import net.solarnetwork.node.ocpp.support.SimpleChargeConfiguration;
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.util.FilterableService;
import net.solarnetwork.util.OptionalService;
import ocpp.v15.cp.AvailabilityStatus;
import ocpp.v15.cp.AvailabilityType;
import ocpp.v15.cp.CancelReservationRequest;
import ocpp.v15.cp.CancelReservationResponse;
import ocpp.v15.cp.ChangeAvailabilityRequest;
import ocpp.v15.cp.ChangeAvailabilityResponse;
import ocpp.v15.cp.ChangeConfigurationRequest;
import ocpp.v15.cp.ChangeConfigurationResponse;
import ocpp.v15.cp.ChargePointService;
import ocpp.v15.cp.ClearCacheRequest;
import ocpp.v15.cp.ClearCacheResponse;
import ocpp.v15.cp.ConfigurationStatus;
import ocpp.v15.cp.DataTransferRequest;
import ocpp.v15.cp.DataTransferResponse;
import ocpp.v15.cp.GetConfigurationRequest;
import ocpp.v15.cp.GetConfigurationResponse;
import ocpp.v15.cp.GetDiagnosticsRequest;
import ocpp.v15.cp.GetDiagnosticsResponse;
import ocpp.v15.cp.GetLocalListVersionRequest;
import ocpp.v15.cp.GetLocalListVersionResponse;
import ocpp.v15.cp.KeyValue;
import ocpp.v15.cp.RemoteStartStopStatus;
import ocpp.v15.cp.RemoteStartTransactionRequest;
import ocpp.v15.cp.RemoteStartTransactionResponse;
import ocpp.v15.cp.RemoteStopTransactionRequest;
import ocpp.v15.cp.RemoteStopTransactionResponse;
import ocpp.v15.cp.ReserveNowRequest;
import ocpp.v15.cp.ReserveNowResponse;
import ocpp.v15.cp.ResetRequest;
import ocpp.v15.cp.ResetResponse;
import ocpp.v15.cp.ResetStatus;
import ocpp.v15.cp.ResetType;
import ocpp.v15.cp.SendLocalListRequest;
import ocpp.v15.cp.SendLocalListResponse;
import ocpp.v15.cp.UnlockConnectorRequest;
import ocpp.v15.cp.UnlockConnectorResponse;
import ocpp.v15.cp.UnlockStatus;
import ocpp.v15.cp.UpdateFirmwareRequest;
import ocpp.v15.cp.UpdateFirmwareResponse;
import ocpp.v15.support.ConfigurationKeys;
/**
* SolarNode implementation of {@link ChargePointService}
*
* @author matt
* @version 1.1
*/
@WebService(serviceName = "ChargePointService", targetNamespace = "urn://Ocpp/Cp/2012/06/")
public class ChargePointService_v15 implements ChargePointService, SettingSpecifierProvider {
private ChargeSessionManager chargeSessionManager;
private ChargeConfigurationDao chargeConfigurationDao;
private OptionalService<SystemService> systemService;
private MessageSource messageSource;
private final Logger log = LoggerFactory.getLogger(getClass());
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public UnlockConnectorResponse unlockConnector(UnlockConnectorRequest parameters,
String chargeBoxIdentity) {
final Integer connId = parameters.getConnectorId();
final String socketId = chargeSessionManager.socketIdForConnectorId(connId);
// if there is an active session on this socket, complete it now
final ChargeSession session = chargeSessionManager.activeChargeSession(socketId);
if ( session != null ) {
chargeSessionManager.completeChargeSession(session.getIdTag(), session.getSessionId());
}
final UnlockConnectorResponse resp = new UnlockConnectorResponse();
if ( socketId == null ) {
resp.setStatus(UnlockStatus.REJECTED);
} else {
chargeSessionManager.configureSocketEnabledState(Collections.singleton(socketId), true);
resp.setStatus(UnlockStatus.ACCEPTED);
}
return resp;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public ResetResponse reset(ResetRequest parameters, String chargeBoxIdentity) {
for ( String socketId : chargeSessionManager.availableSocketIds() ) {
ChargeSession session = chargeSessionManager.activeChargeSession(socketId);
if ( session != null ) {
chargeSessionManager.completeChargeSession(session.getIdTag(), session.getSessionId());
}
}
if ( parameters.getType() == ResetType.HARD ) {
// also restart (via new thread)
SystemService sysService = (systemService != null ? systemService.service() : null);
if ( sysService != null ) {
sysService.reboot();
}
}
ResetResponse resp = new ResetResponse();
resp.setStatus(ResetStatus.ACCEPTED);
return resp;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public ChangeAvailabilityResponse changeAvailability(ChangeAvailabilityRequest parameters,
String chargeBoxIdentity) {
final Integer connId = parameters.getConnectorId();
final Collection<String> socketIds;
final ChangeAvailabilityResponse resp = new ChangeAvailabilityResponse();
if ( Integer.valueOf(0).equals(connId) ) {
// this means ALL connectors
socketIds = chargeSessionManager.availableSocketIds();
} else {
String socketId = chargeSessionManager.socketIdForConnectorId(connId);
if ( socketId == null ) {
socketIds = Collections.emptySet();
} else {
socketIds = Collections.singleton(socketId);
}
}
if ( socketIds.isEmpty() ) {
resp.setStatus(AvailabilityStatus.REJECTED);
} else {
chargeSessionManager.configureSocketEnabledState(socketIds,
AvailabilityType.OPERATIVE.equals(parameters.getType()));
resp.setStatus(AvailabilityStatus.ACCEPTED);
}
return resp;
}
@Override
public GetDiagnosticsResponse getDiagnostics(GetDiagnosticsRequest parameters,
String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public ClearCacheResponse clearCache(ClearCacheRequest parameters, String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public UpdateFirmwareResponse updateFirmware(UpdateFirmwareRequest parameters,
String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public ChangeConfigurationResponse changeConfiguration(ChangeConfigurationRequest parameters,
String chargeBoxIdentity) {
ChangeConfigurationResponse resp = new ChangeConfigurationResponse();
resp.setStatus(ConfigurationStatus.ACCEPTED);
ChargeConfiguration config = chargeConfigurationDao.getChargeConfiguration();
SimpleChargeConfiguration newConfig = new SimpleChargeConfiguration(config);
try {
ConfigurationKeys key = ConfigurationKeys.forKey(parameters.getKey());
switch (key) {
case HeartBeatInterval:
newConfig.setHeartBeatInterval(Integer.parseInt(parameters.getValue()));
break;
case MeterValueSampleInterval:
newConfig.setMeterValueSampleInterval(Integer.parseInt(parameters.getValue()));
break;
default:
resp.setStatus(ConfigurationStatus.NOT_SUPPORTED);
}
} catch ( NumberFormatException e ) {
resp.setStatus(ConfigurationStatus.REJECTED);
} catch ( IllegalArgumentException e ) {
resp.setStatus(ConfigurationStatus.NOT_SUPPORTED);
}
if ( newConfig.differsFrom(config) ) {
chargeConfigurationDao.storeChargeConfiguration(newConfig);
}
return resp;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public RemoteStartTransactionResponse remoteStartTransaction(
final RemoteStartTransactionRequest parameters, final String chargeBoxIdentity) {
final Integer connId = parameters.getConnectorId();
final String socketId;
final RemoteStartTransactionResponse resp = new RemoteStartTransactionResponse();
if ( connId != null ) {
socketId = chargeSessionManager.socketIdForConnectorId(connId);
} else {
Collection<String> socketIds = chargeSessionManager.availableSocketIds();
if ( socketIds.isEmpty() ) {
socketId = null;
} else {
socketId = socketIds.iterator().next();
}
}
if ( socketId == null ) {
resp.setStatus(RemoteStartStopStatus.REJECTED);
} else {
String sessionId = chargeSessionManager.initiateChargeSession(parameters.getIdTag(),
socketId, null);
log.debug("Initiated remote charge session {} for IdTag {} on socket {}", sessionId,
parameters.getIdTag(), socketId);
resp.setStatus(RemoteStartStopStatus.ACCEPTED);
}
return resp;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public RemoteStopTransactionResponse remoteStopTransaction(RemoteStopTransactionRequest parameters,
String chargeBoxIdentity) {
final int txId = parameters.getTransactionId();
final RemoteStopTransactionResponse resp = new RemoteStopTransactionResponse();
final ChargeSession session = chargeSessionManager.activeChargeSession(txId);
if ( session == null ) {
resp.setStatus(RemoteStartStopStatus.REJECTED);
} else {
chargeSessionManager.completeChargeSession(session.getIdTag(), session.getSessionId());
log.debug("Completed remote charge session {} for IdTag {} on socket {}",
session.getSessionId(), session.getIdTag(), session.getSocketId());
resp.setStatus(RemoteStartStopStatus.ACCEPTED);
}
return resp;
}
@Override
public CancelReservationResponse cancelReservation(CancelReservationRequest parameters,
String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public DataTransferResponse dataTransfer(DataTransferRequest parameters, String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
private void addKeyValue(String key, String value, boolean readonly, List<KeyValue> list) {
KeyValue kv = new KeyValue();
kv.setKey(key);
kv.setValue(value);
kv.setReadonly(readonly);
list.add(kv);
}
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
@Override
public GetConfigurationResponse getConfiguration(GetConfigurationRequest parameters,
String chargeBoxIdentity) {
final ChargeConfiguration config = chargeConfigurationDao.getChargeConfiguration();
final GetConfigurationResponse resp = new GetConfigurationResponse();
List<String> keys = parameters.getKey();
if ( keys == null || keys.isEmpty() ) {
keys = Arrays.asList(ConfigurationKeys.HeartBeatInterval.getKey(),
ConfigurationKeys.MeterValueSampleInterval.getKey());
}
for ( String key : keys ) {
try {
ConfigurationKeys confKey = ConfigurationKeys.forKey(key);
switch (confKey) {
case HeartBeatInterval:
addKeyValue(key, String.valueOf(config.getHeartBeatInterval()), false,
resp.getConfigurationKey());
break;
case MeterValueSampleInterval:
addKeyValue(key, String.valueOf(config.getMeterValueSampleInterval()), false,
resp.getConfigurationKey());
break;
default:
resp.getUnknownKey().add(key);
}
} catch ( IllegalArgumentException e ) {
resp.getUnknownKey().add(key);
}
}
return resp;
}
@Override
public GetLocalListVersionResponse getLocalListVersion(GetLocalListVersionRequest parameters,
String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public ReserveNowResponse reserveNow(ReserveNowRequest parameters, String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public SendLocalListResponse sendLocalList(SendLocalListRequest parameters,
String chargeBoxIdentity) {
throw new UnsupportedOperationException();
}
@Override
public String getSettingUID() {
return "net.solarnetwork.node.ocpp.web.chargepoint";
}
@Override
public String getDisplayName() {
return getClass().getSimpleName();
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
//ChargePointService_v15 defaults = new ChargePointService_v15();
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(4);
results.add(new BasicTitleSettingSpecifier("info", getInfoMessage(Locale.getDefault()), true));
results.add(new BasicTextFieldSettingSpecifier(
"filterableChargeSessionManager.propertyFilters['UID']", "OCPP Central System"));
return results;
}
private String getInfoMessage(Locale locale) {
ChargeSessionManager mgr = chargeSessionManager;
String managerUID = null;
try {
managerUID = (mgr != null ? mgr.getUID() : null);
} catch ( RuntimeException e ) {
log.warn("ChargeSessionManager UID unavailable: {}", e.getMessage());
}
if ( managerUID != null ) {
return messageSource.getMessage("status", new Object[] { managerUID }, locale);
}
return messageSource.getMessage("status.noManager", null, locale);
}
public ChargeSessionManager getChargeSessionManager() {
return chargeSessionManager;
}
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 setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* Set the {@link ChargeConfigurationDao} to use.
*
* @param chargeConfigurationDao
* The DAO to use.
* @since 1.1
*/
public void setChargeConfigurationDao(ChargeConfigurationDao chargeConfigurationDao) {
this.chargeConfigurationDao = chargeConfigurationDao;
}
/**
* Set the {@link SystemService} to use. This is required to support the
* {@link #reset(ResetRequest, String)} method.
*
* @param systemService
* the systemService to set
* @since 1.1
*/
public void setSystemService(OptionalService<SystemService> systemService) {
this.systemService = systemService;
}
}