Skip to content
Open
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
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,17 @@ expectIncoming(
)
```

### Setting up default handlers of certain messages

Sometimes Central Systems will send a charge point messages that it just doesn't
see coming. Two back-offices of major Dutch charge point vendors tend to probe
charge points periodically for their configuration settings using
`GetConfigurationReq` messages. In order to keep such unanticipated incoming
messages from getting in your incoming message queue and making your tests fail,
you can catch them with `handlingIncomingMessages`. An example of its use is given
[here](examples/ocpp1x/handling-incoming-messages.scala).


## Running on the command line with an interactive prompt

You can also go into an interactive testing session on the command line. To do that, pass the `-i` command line flag:
Expand Down Expand Up @@ -504,9 +515,6 @@ It's far from finished now. The next steps I plan to develop:

* Add a command in interactive mode to run a script from a file or URL

* Add a functionality to automatically respond to messages matching a
certain pattern in a certain way

* Messages of OCPP 2.0 that seem to be in demand:
* ChangeAvailability
* Reset
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ lazy val coreDeps = Seq(
"com.thenewmotion.ocpp" %% "ocpp-j-api" % "9.2.3",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.0",

"org.specs2" %% "specs2-core" % "4.3.4" % "test"
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.scalamock" %% "scalamock" % "5.1.0" % "test"
)

def loaderDeps(scalaVersion: String) = Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package chargepoint.docile
package test

import chargepoint.docile.dsl._
import com.thenewmotion.ocpp.VersionFamily
import com.thenewmotion.ocpp.{Version, Version1X, VersionFamily}
import com.thenewmotion.ocpp.messages.v1x.{CentralSystemReq, CentralSystemReqRes, CentralSystemRes, ChargePointReq, ChargePointReqRes, ChargePointRes}
import com.thenewmotion.ocpp.messages.v20._
import com.thenewmotion.ocpp.messages.{ReqRes, Request, Response}
Expand Down Expand Up @@ -61,6 +61,7 @@ object InteractiveOcpp1XTest {

trait V1XPromptCommands extends InteractiveOcppTest.PromptCommands[
VersionFamily.V1X.type,
Version1X,
CentralSystemReq,
CentralSystemRes,
CentralSystemReqRes,
Expand Down Expand Up @@ -98,6 +99,7 @@ object InteractiveOcpp20Test {

trait V20PromptCommands extends InteractiveOcppTest.PromptCommands[
VersionFamily.V20.type,
Version.V20.type,
CsmsRequest,
CsmsResponse,
CsmsReqRes,
Expand All @@ -114,7 +116,7 @@ object InteractiveOcppTest {

new InteractiveOcpp1XTest {

private def connDat = connectionData
private def connDat = connection

implicit val csmsMessageTypes = VersionFamily.V1XCentralSystemMessages
implicit val csMessageTypes = VersionFamily.V1XChargePointMessages
Expand All @@ -123,45 +125,46 @@ object InteractiveOcppTest {
val ops: Ocpp1XTest.V1XOps = new Ocpp1XTest.V1XOps
with expectations.Ops[VersionFamily.V1X.type, CentralSystemReq, CentralSystemRes, CentralSystemReqRes, ChargePointReq, ChargePointRes, ChargePointReqRes]
with shortsend.OpsV1X {
def connectionData = connDat
def connection = connDat

implicit val csmsMessageTypes = VersionFamily.V1XCentralSystemMessages
implicit val csMessageTypes = VersionFamily.V1XChargePointMessages
implicit val executionContext = global
}

val promptCommands: InteractiveOcpp1XTest.V1XPromptCommands = new InteractiveOcpp1XTest.V1XPromptCommands {
def connectionData = connDat
def connection = connDat
}
}.asInstanceOf[OcppTest[vfam.type]]

case VersionFamily.V20 =>

new InteractiveOcpp20Test {

private def connDat = connectionData
private def connDat = connection

implicit val csmsMessageTypes = VersionFamily.V20CsmsMessages
implicit val csMessageTypes = VersionFamily.V20CsMessages
implicit val executionContext = global

val ops: Ocpp20Test.V20Ops = new Ocpp20Test.V20Ops
with expectations.Ops[VersionFamily.V20.type, CsmsRequest, CsmsResponse, CsmsReqRes, CsRequest, CsResponse, CsReqRes] {
def connectionData = connDat
def connection = connDat

implicit val csmsMessageTypes = VersionFamily.V20CsmsMessages
implicit val csMessageTypes = VersionFamily.V20CsMessages
implicit val executionContext = global
}

val promptCommands: InteractiveOcpp20Test.V20PromptCommands = new InteractiveOcpp20Test.V20PromptCommands {
def connectionData = connDat
def connection = connDat
}
}.asInstanceOf[OcppTest[vfam.type]]
}

trait PromptCommands[
VFam <: VersionFamily,
VersionBound <: Version,
OutReq <: Request,
InRes <: Response,
OutReqRes[_ <: OutReq, _ <: InRes] <: ReqRes[_, _],
Expand All @@ -170,12 +173,12 @@ object InteractiveOcppTest {
InReqRes[_ <: InReq, _ <: OutRes] <: ReqRes[_, _]
] {

protected def connectionData: OcppConnectionData[VFam, OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]
protected def connection: DocileConnection[VFam, VersionBound, OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]

def q: Unit =
connectionData.receivedMsgManager.currentQueueContents foreach println
connection.receivedMsgManager.currentQueueContents foreach println

def whoami: Unit =
println(connectionData.chargePointIdentity)
println(connection.chargePointIdentity)
}
}
41 changes: 16 additions & 25 deletions core/src/main/scala/chargepoint/docile/dsl/CoreOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import scala.util.{Failure, Success, Try}
import com.thenewmotion.ocpp.json.api.{OcppError, OcppException}
import com.thenewmotion.ocpp.messages.{ReqRes, Request, Response}
import com.typesafe.scalalogging.Logger
import expectations.{IncomingMessage => GenericIncomingMessage}
import org.slf4j.LoggerFactory

trait CoreOps[
Expand All @@ -26,13 +25,16 @@ trait CoreOps[
InReqRes[_ <: InReq, _ <: OutRes] <: ReqRes[_, _]
] extends OpsLogging with MessageLogging {

type IncomingMessage =
GenericIncomingMessage[OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]
type IncomingMessageProcessor[+T] =
GenericIncomingMessageProcessor[OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes, T]

implicit val csmsMessageTypes: CsmsMessageTypesForVersionFamily[VFam, OutReq, InRes, OutReqRes]
implicit val csMessageTypes: CsMessageTypesForVersionFamily[VFam, InReq, OutRes, InReqRes]

implicit def executionContext: ExecutionContext

type IncomingMessage = GenericIncomingMessage[OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]
object IncomingMessage {
def apply(res: InRes): IncomingMessage = GenericIncomingMessage[OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes](res)
def apply(req: InReq, respond: OutRes => Unit): IncomingMessage = GenericIncomingMessage[OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes](req, respond)
Expand All @@ -42,7 +44,7 @@ trait CoreOps[
val logger = Logger(LoggerFactory.getLogger("script"))
def say(m: String): Unit = logger.info(m)

protected def connectionData: OcppConnectionData[VFam, OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]
protected def connection: DocileConnection[VFam, _, OutReq, InRes, OutReqRes, InReq, OutRes, InReqRes]

/**
* Send an OCPP request to the Central System under test.
Expand All @@ -57,32 +59,21 @@ trait CoreOps[
* @tparam Q
*/
def send[Q <: OutReq](req: Q)(implicit reqRes: OutReqRes[Q, _ <: InRes]): Unit =
connectionData.ocppClient match {
case None =>
throw ExpectationFailed("Trying to send an OCPP message while not connected")
case Some (client) =>
outgoingLogger.info(s"$req")
client.send(req)(reqRes) onComplete {
case Success(res) =>
incomingLogger.info(s"$res")
connectionData.receivedMsgManager.enqueue(
IncomingMessage(res)
)
case Failure(OcppException(ocppError)) =>
incomingLogger.info(s"$ocppError")
connectionData.receivedMsgManager.enqueue(
IncomingMessage(ocppError)
)
case Failure(e) =>
opsLogger.error(s"Failed to get response to outgoing OCPP request $req: ${e.getMessage}\n\t${e.getStackTrace.mkString("\n\t")}")
throw ExecutionError(e)
}
connection.sendRequestAndManageResponse(req)

// WIP an operation to add a default handler for a certain subset of incoming messages
def handlingIncomingMessages[T](proc: IncomingMessageProcessor[_])(f: => T): T = {
connection.pushIncomingMessageHandler(proc)
val result = f
connection.popIncomingMessageHandler()

result
}

def awaitIncoming(num: Int)(implicit awaitTimeout: AwaitTimeout): Seq[IncomingMessage] = {

val timeout = awaitTimeout.toDuration
def getMsgs = connectionData.receivedMsgManager.dequeue(num)
def getMsgs = connection.receivedMsgManager.dequeue(num)

Try(Await.result(getMsgs, timeout)) match {
case Success(msgs) => msgs
Expand All @@ -97,7 +88,7 @@ trait CoreOps[
* This can be used in interactive mode to get out of a situation where you've received a bunch of messages that you
* don't really care about, and you want to get on with things.
*/
def flushQ(): Unit = connectionData.receivedMsgManager.flush()
def flushQ(): Unit = connection.receivedMsgManager.flush()

def fail(message: String): Nothing = throw ExpectationFailed(message)

Expand Down
Loading