/* ================================================================== * CloseCompletedChargeSessionsJob.java - 24/03/2017 10:13:38 AM * * Copyright 2007-2017 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.charge; import java.util.List; import java.util.ListIterator; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.PersistJobDataAfterExecution; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import net.solarnetwork.node.job.AbstractJob; import net.solarnetwork.node.ocpp.ChargeSession; import net.solarnetwork.node.ocpp.ChargeSessionManager; import net.solarnetwork.node.ocpp.ChargeSessionMeterReading; import ocpp.v15.cs.Measurand; /** * Job to periodically look for active charge sessions that appear to have * finished because of a lack of power being drawn on the associated socket. * * @author matt * @version 1.0 */ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class CloseCompletedChargeSessionsJob extends AbstractJob { private ChargeSessionManager service; private TransactionTemplate transactionTemplate; private long maxAgeLastReading = (15 * 60 * 1000L); private int readingEnergyCount = 5; private long maxEnergy = 5; @Override protected void executeInternal(JobExecutionContext jobContext) throws Exception { if ( service == null ) { log.warn( "No ChargeSessionManager available, cannot close active charge sessions that appear to be completed"); return; } if ( transactionTemplate != null ) { transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { closeCompletedChargeSessions(); return null; } }); } else { closeCompletedChargeSessions(); } } private void closeCompletedChargeSessions() { log.debug("Looking for OCPP active charge sessions that appear to be completed"); for ( String socketId : service.availableSocketIds() ) { ChargeSession session = service.activeChargeSession(socketId); if ( session != null ) { List<ChargeSessionMeterReading> readings = service .meterReadingsForChargeSession(session.getSessionId()); boolean close = false; if ( (readings == null || readings.isEmpty()) && session.getCreated() != null && (session.getCreated().getTime() + maxAgeLastReading) < System .currentTimeMillis() ) { log.info( "OCCP charge session {} on socket {} has not recorded any readings since {}; closing session", session.getSessionId(), socketId, session.getCreated()); close = true; } else if ( readings != null && !readings.isEmpty() ) { ChargeSessionMeterReading reading = readings.get(readings.size() - 1); if ( reading != null && reading.getTs() != null && reading.getTs().getTime() + maxAgeLastReading < System.currentTimeMillis() ) { log.info( "OCCP charge session {} on socket {} has not recorded any readings since {}; closing session", session.getSessionId(), socketId, reading.getTs()); close = true; } else if ( readingEnergyCount > 0 && readings.size() >= readingEnergyCount ) { // look to see if the energy drawn over the last few readings is about 0, meaning the battery is charged // we assume readings are taken at regular intervals ListIterator<ChargeSessionMeterReading> itr = readings .listIterator(readings.size()); int count = 0; long whEnd = 0; long whStart = 0; while ( itr.hasPrevious() && count < readingEnergyCount ) { reading = itr.previous(); if ( reading.getMeasurand() == Measurand.ENERGY_ACTIVE_IMPORT_REGISTER ) { count += 1; if ( count == 1 ) { whEnd = Long.valueOf(reading.getValue()); } else if ( count == readingEnergyCount ) { whStart = Long.valueOf(reading.getValue()); break; } } } if ( count == readingEnergyCount ) { long wh = whEnd - whStart; if ( wh < maxEnergy ) { log.info( "OCCP charge session {} on socket {} has only drawn {} Wh since {}; closing session", session.getSessionId(), socketId, wh, reading.getTs()); close = true; } } } } if ( close ) { service.completeChargeSession(session.getIdTag(), session.getSessionId()); } } } } /** * Set the charge session manager to use. * * @param service * The service to use. */ public void setService(ChargeSessionManager service) { this.service = service; } /** * A transaction template to use. * * @param transactionTemplate * The template to use. */ public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } /** * Set the number of meter readings to consider when calculating the * effective energy drawn on the socket. * * @param readingEnergyCount * The reading average count. */ public void setReadingEnergyCount(int readingEnergyCount) { this.readingEnergyCount = readingEnergyCount; } /** * The maximum energy, in Wh, a charge session can draw over * {@code readingEnergyCount} readings to be considered for closing. If the * energy drawn is higher than this, the session will not be closed. * * @param maxEnergy * The maximum energy to consider for closing a session, in Wh. */ public void setMaxEnergy(long maxEnergy) { this.maxEnergy = maxEnergy; } /** * Set the maximum age in milliseconds from the last meter reading captured * for that session (or the date the session started, if no readings are * available). If this threshold is passed then the session will be closed. * * @param maxAgeLastReading * The maximum time lapse allowed between meter readings, in * milliseconds. */ public void setMaxAgeLastReading(long maxAgeLastReading) { this.maxAgeLastReading = maxAgeLastReading; } }