Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add handling for TxDefaultProfile #6

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<ChargingProfile>(
name = "tx_default_profile",
objectMapper = MontaSerialization.getDefaultMapper()
)
}

// DAO
class TxDefaultDAO(
id: EntityID<Long>
) : LongEntity(id) {
companion object : LongEntityClass<TxDefaultDAO>(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
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,6 +26,7 @@ class DatabaseService {
ChargePointTable,
ChargePointConnectorTable,
ChargePointTransaction,
TxDefault,
PreviousMessagesTable
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
)
Expand All @@ -66,17 +74,76 @@ class SmartChargingClientHandler(
SetChargingProfileConfirmation(
status = SetChargingProfileStatus.Accepted
)
} else if (setDefaultChargingProfile(ocppSessionInfo, request)) {
SetChargingProfileConfirmation(
status = SetChargingProfileStatus.Accepted
)
} else {
SetChargingProfileConfirmation(
status = SetChargingProfileStatus.Rejected
)
}
}

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
Expand Down Expand Up @@ -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)
}
Expand Down
Loading