diff --git a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/entity/ChargePointTxDefault.kt b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/entity/ChargePointTxDefault.kt new file mode 100644 index 0000000..a267884 --- /dev/null +++ b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/entity/ChargePointTxDefault.kt @@ -0,0 +1,52 @@ +package com.monta.ocpp.emulator.v16.data.entity + +import com.monta.library.ocpp.v16.smartcharge.ChargingProfile +import com.monta.ocpp.emulator.chargepoint.entity.ChargePointDAO +import com.monta.ocpp.emulator.chargepoint.entity.ChargePointTable +import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorDAO +import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorTable +import com.monta.ocpp.emulator.common.util.MontaSerialization +import com.monta.ocpp.emulator.common.util.json +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.LongIdTable + +object TxDefault : LongIdTable("charge_point_default_profile") { + val chargePointId = reference("charge_point_id", ChargePointTable) + val connectorId = reference("connector_id", ChargePointConnectorTable) + + val chargingProfileId = integer("charging_profile_id").nullable() + val stackLeveL = integer("stack_level").nullable() + val txDefaultProfile = json( + name = "tx_default_profile", + objectMapper = MontaSerialization.getDefaultMapper() + ) +} + +// DAO +class TxDefaultDAO( + id: EntityID +) : LongEntity(id) { + companion object : LongEntityClass(TxDefault) { + fun newInstance( + chargePoint: ChargePointDAO, + chargePointConnector: ChargePointConnectorDAO, + chargingProfile: ChargingProfile + ): TxDefaultDAO { + return TxDefaultDAO.new { + this.chargePoint = chargePoint + this.connector = chargePointConnector + this.chargingProfileId = chargingProfile.chargingProfileId + this.stackLevel = chargingProfile.stackLevel + this.txDefaultProfile = chargingProfile + } + } + } + + var chargePoint by ChargePointDAO referencedOn TxDefault.chargePointId + var connector by ChargePointConnectorDAO referencedOn TxDefault.connectorId + var chargingProfileId by TxDefault.chargingProfileId + var stackLevel by TxDefault.stackLeveL + var txDefaultProfile by TxDefault.txDefaultProfile +} diff --git a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/service/TxDefaultService.kt b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/service/TxDefaultService.kt new file mode 100644 index 0000000..292774e --- /dev/null +++ b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/chargepoint/service/TxDefaultService.kt @@ -0,0 +1,41 @@ +package com.monta.ocpp.emulator.v16.data.service + +import com.monta.library.ocpp.v16.smartcharge.ChargingProfile +import com.monta.library.ocpp.v16.smartcharge.ChargingProfilePurposeType +import com.monta.library.ocpp.v16.smartcharge.ClearChargingProfileRequest +import com.monta.ocpp.emulator.chargepoint.entity.ChargePointDAO +import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorDAO +import com.monta.ocpp.emulator.v16.data.entity.TxDefaultDAO +import com.monta.ocpp.emulator.v16.data.repository.TxDefaultRepository +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.core.annotation.Singleton + +@Singleton +class TxDefaultService( + private val txDefaultRepository: TxDefaultRepository +) { + + fun store( + chargePoint: ChargePointDAO, + chargePointConnector: ChargePointConnectorDAO, + txProfile: ChargingProfile + ): TxDefaultDAO { + return transaction { + txDefaultRepository.store(chargePoint, chargePointConnector, txProfile) + } + } + + fun clear( + chargePoint: ChargePointDAO, + connectorDAO: ChargePointConnectorDAO?, + request: ClearChargingProfileRequest + ) { + if (request.chargingProfilePurpose == null || + request.chargingProfilePurpose == ChargingProfilePurposeType.TxDefaultProfile + ) { + return transaction { + txDefaultRepository.delete(chargePoint, connectorDAO, request) + } + } + } +} diff --git a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/common/DatabaseService.kt b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/common/DatabaseService.kt index f9164db..7cec62e 100644 --- a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/common/DatabaseService.kt +++ b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/common/DatabaseService.kt @@ -6,6 +6,7 @@ import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorT import com.monta.ocpp.emulator.chargepointtransaction.entity.ChargePointTransaction import com.monta.ocpp.emulator.configuration.AppConfigTable import com.monta.ocpp.emulator.database.DatabaseInitiator +import com.monta.ocpp.emulator.v16.data.entity.TxDefault import mu.KotlinLogging import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.transactions.transaction @@ -25,6 +26,7 @@ class DatabaseService { ChargePointTable, ChargePointConnectorTable, ChargePointTransaction, + TxDefault, PreviousMessagesTable ) } diff --git a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/data/repository/TxDefaultRepository.kt b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/data/repository/TxDefaultRepository.kt new file mode 100644 index 0000000..f749a9b --- /dev/null +++ b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/data/repository/TxDefaultRepository.kt @@ -0,0 +1,77 @@ +package com.monta.ocpp.emulator.v16.data.repository + +import com.monta.library.ocpp.v16.smartcharge.ChargingProfile +import com.monta.library.ocpp.v16.smartcharge.ChargingProfilePurposeType +import com.monta.library.ocpp.v16.smartcharge.ClearChargingProfileRequest +import com.monta.ocpp.emulator.chargepoint.entity.ChargePointDAO +import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorDAO +import com.monta.ocpp.emulator.v16.data.entity.TxDefault +import com.monta.ocpp.emulator.v16.data.entity.TxDefaultDAO +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteWhere +import org.koin.core.annotation.Singleton + +@Singleton +class TxDefaultRepository { + + fun store( + chargePointDAO: ChargePointDAO, + connectorDAO: ChargePointConnectorDAO, + chargingProfile: ChargingProfile + ): TxDefaultDAO { + require( + chargingProfile.chargingProfilePurpose == ChargingProfilePurposeType.TxDefaultProfile + ) { + "chargingProfile must be a TxDefaultProfile" + } + // v1.6 section 7.8:ChargingProfile.chargingProfileId is required + require(chargingProfile.chargingProfileId != null) { + "chargingProfileId is required" + } + val item = findById(chargePointDAO, connectorDAO, chargingProfile.chargingProfileId!!) + return if (item != null) { + item.txDefaultProfile = chargingProfile + item + } else { + TxDefaultDAO.newInstance(chargePointDAO, connectorDAO, chargingProfile) + } + } + + fun delete( + chargePointDAO: ChargePointDAO, + connectorDAO: ChargePointConnectorDAO?, + request: ClearChargingProfileRequest + ) { + /* + v1.6 section 6.13: + The Central System can use this message to clear (remove) either a specific charging profile (denoted by id) + or + a selection of charging profiles that match with the values of the + optional connectorId, stackLevel and chargingProfilePurpose fields. + + The ClearChargingProfileRequest does not contain stack level. + */ + + val onChargePoint = TxDefault.chargePointId eq chargePointDAO.chargePointId() + val onConnector = connectorDAO?.let { connector -> TxDefault.connectorId eq connector.id } ?: Op.TRUE + val condition = when { + request.id != null -> Op.build { onChargePoint and (TxDefault.chargingProfileId eq request.id) } + else -> onChargePoint and onConnector + } + + TxDefault.deleteWhere { condition } + } + + private fun findById( + chargePointDAO: ChargePointDAO, + connectorDAO: ChargePointConnectorDAO, + chargingProfileId: Int + ): TxDefaultDAO? { + val equalsProfileId = Op.build { TxDefault.chargingProfileId eq chargingProfileId } + val onChargePoint = TxDefault.chargePointId eq chargePointDAO.chargePointId() + val onConnector = TxDefault.connectorId eq connectorDAO.id + return TxDefaultDAO.find { onChargePoint and onConnector and equalsProfileId }.firstOrNull() + } +} diff --git a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/profile/SmartChargingClientHandler.kt b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/profile/SmartChargingClientHandler.kt index 0f82297..159ca27 100644 --- a/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/profile/SmartChargingClientHandler.kt +++ b/v16/src/jvmMain/kotlin/com/monta/ocpp/emulator/v16/profile/SmartChargingClientHandler.kt @@ -17,13 +17,15 @@ import com.monta.ocpp.emulator.chargepoint.service.ChargePointService import com.monta.ocpp.emulator.chargepointconnector.entity.ChargePointConnectorDAO import com.monta.ocpp.emulator.chargepointtransaction.service.ChargePointTransactionService import com.monta.ocpp.emulator.logger.GlobalLogger +import com.monta.ocpp.emulator.v16.data.service.TxDefaultService import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Singleton @Singleton class SmartChargingClientHandler( private val chargePointService: ChargePointService, - private val chargePointTransactionService: ChargePointTransactionService + private val chargePointTransactionService: ChargePointTransactionService, + private val txDefaultService: TxDefaultService ) : SmartChargeClientProfile.Listener { override suspend fun clearChargingProfile( @@ -44,6 +46,12 @@ class SmartChargingClientHandler( } } + // handle TxDefault messages + val chargePoint = chargePointService.getByIdentity( + ocppSessionInfo.identity + ) + txDefaultService.clear(chargePoint, connector, request) + return ClearChargingProfileConfirmation( status = ClearChargingProfileStatus.Accepted ) @@ -66,6 +74,10 @@ class SmartChargingClientHandler( SetChargingProfileConfirmation( status = SetChargingProfileStatus.Accepted ) + } else if (setDefaultChargingProfile(ocppSessionInfo, request)) { + SetChargingProfileConfirmation( + status = SetChargingProfileStatus.Accepted + ) } else { SetChargingProfileConfirmation( status = SetChargingProfileStatus.Rejected @@ -73,10 +85,65 @@ class SmartChargingClientHandler( } } + private suspend fun validateSetDefaultChargingProfile( + connector: ChargePointConnectorDAO, + request: SetChargingProfileRequest + ): Boolean { + if (request.connectorId != 0) { + GlobalLogger.warn(connector, "TxDefault must be on connector 0") + return false + } + val chargingProfile = request.csChargingProfiles + if (chargingProfile.chargingProfilePurpose != ChargingProfilePurposeType.TxDefaultProfile) { + GlobalLogger.warn(connector, "rejected charging profile, chargingProfilePurpose is not TxDefaultProfile") + return false + } + + if (chargingProfile.chargingProfileKind != ChargingProfileKindType.Absolute) { + GlobalLogger.warn(connector, "rejected charging profile, chargingProfileKind is not absolute") + return false + } + + return true + } + + private suspend fun setDefaultChargingProfile( + ocppSessionInfo: OcppSession.Info, + request: SetChargingProfileRequest + ): Boolean { + if (request.csChargingProfiles.chargingProfilePurpose != ChargingProfilePurposeType.TxDefaultProfile) { + return false + } + + val connector = getConnector(ocppSessionInfo = ocppSessionInfo, connectorId = request.connectorId) ?: return false + if (!validateSetDefaultChargingProfile(connector, request)) { + return false + } + + val chargePoint = chargePointService.getByIdentity( + ocppSessionInfo.identity + ) + + GlobalLogger.info(connector, "received TxDefault charging profile :)") + + transaction { + txDefaultService.store(chargePoint, connector, request.csChargingProfiles) + } + + return true + } + private suspend fun setChargingProfileForTransaction( ocppSessionInfo: OcppSession.Info, request: SetChargingProfileRequest ): Boolean { + if (request.csChargingProfiles.chargingProfilePurpose == ChargingProfilePurposeType.TxDefaultProfile) { + return false + } + if (request.connectorId < 1) { + return false + } + val connector = getConnector( ocppSessionInfo = ocppSessionInfo, connectorId = request.connectorId @@ -132,7 +199,7 @@ class SmartChargingClientHandler( ocppSessionInfo: OcppSession.Info, connectorId: Int? ): ChargePointConnectorDAO? { - if (connectorId != null && connectorId > 0) { + if (connectorId != null) { val chargePoint = chargePointService.getByIdentity(ocppSessionInfo.identity) return chargePoint.getConnector(connectorId) }