This Gitlab instance will require Two-Factor-Authentication from 2021-10-01. Read how to enable 2FA.

Commit 0e3d4a85 authored by Dustin Peterson's avatar Dustin Peterson

Initial commit.

parents
.idea
project/target
target
# CanXML
This is **CanXML**, a small Scala utility to specify and simulate virtual CAN networks using SocketCAN.
Currently, feature support is limited, but it can be used for educational purposes.
## Building
You can build it by yourself as follows (you need to have SocketCAN and can-utils installed):
```
sbt compile assembly
```
Afterwards you find the JAR-file in `target/scala-2.12/canxml-assembly-0.1.jar`.
## Running
The JAR has one main class that has to be called with an XML file. There is one example in the repository: *tutorial.xml*
We provide a Shell script (that requires you to have sudo rights because of starting virtual can devices) that you can use:
```
./canxml.sh tutorial.xml
```
## The XML file
Basically, if you have a look at the tutorial file, the XML format should be easily understandable. Beside the configuration-tag, there
are four main-tags:
* **bus**-tag: Creates a new bus sub system with a given virtual CAN device (vcan0, vcan1, vcan2, vcan3) and a baudrate.
* attribute **name** specifies the device name (vcan0, vcan1, vcan2, vcan3)
* attribute **baudrate** specifies the baudrate in bytes per second.
* **device**-tag: Creates a new communicating device in the current bus with a given name.
* attribute **name** specifies the device name (can be anything).
* **send**-tag: Specifies that the current device creates a periodic message with random content of given length, a given period in milliseconds and a message ID.
* attribute **id** specifies the message ID as hex number.
* attribute **length** specifies the message length in bytes.
* attribute **period** specifies the period in milliseconds.
* **receive**-tag: Specifies that the current device receives messages of the given ID.
* attribute **id** specifies the message ID as hex number.
## Tutorial
Assume we have the following communication specifications:
* There are four devices A, B, C, D.
* A and B reside in a separate bus subsystem represented by vcan0.
* C and D reside in a separate bus subsystem represented by vcan1.
* A sends two periodic messages of length 3 and period 1.0ms, one with ID 3AB and one with 1AC.
* A receives a message with ID 2BA.
* B sends a message of length 3 and period 1.0ms with ID 2BA.
* B receives a message with ID 3AB.
* C sends a message of length 3 and period 1.0ms with ID 4CD.
* C receives a message with ID 1AC.
* D receives a message with ID 4CD.
Therefore, there are two subsystems which are connected through a gateway (because message 1AC goes from one subsystem to the other). The bus topology might look like the following:
[tutorial.png]
To implement this using SocketCAN and simulate the resulting bus loads (vcan0 and vcan1 both have baudrate 500Kbps), one could use the following script:
```
sudo modprobe -a can can_gw can_raw vcan
sudo cangw -F
sudo ip link add dev vcan0 type vcan &> /dev/null
sudo ip link set up vcan0 &> /dev/null
sudo ip link add dev vcan1 type vcan &> /dev/null
sudo ip link set up vcan1 &> /dev/null
cangen vcan0 -g 1.0 -I 3AB -L 3 -D 'r' &
cangen vcan0 -g 1.0 -I 2BA -L 3 -D 'r' &
cangen vcan0 -g 1.0 -I 1AC -L 3 -D 'r' &
cangen vcan1 -g 1.0 -I 4CD -L 3 -D 'r' &
sudo cangw -A -s vcan0 -d vcan1 -e -f 1AC:7FF
canbusload vcan0@500000 vcan1@500000
```
To implement this in CanXML and simulate the resulting bus loads, one could use also CanXML and a more readable XML input:
```
<configuration>
<bus name="vcan0" baudrate="500000">
<device name="A">
<send id="3AB" length="3" period="1.0" />
<send id="1AC" length="3" period="1.0" />
<receive id="2BA" />
</device>
<device name="B">
<send id="2BA" length="3" period="1.0" />
<receive id="3AB" />
</device>
</bus>
<bus name="vcan1" baudrate="500000">
<device name="C">
<send id="4CD" length="3" period="1.0" />
<receive id="1AC" />
</device>
<device name="D">
<receive id="4CD" />
</device>
</bus>
</configuration>
```
The main advantage is, that it automatically generates CAN gateway rules, when devices communicate from one subsystem to another. Therefore one can easily explore different partitioning alternatives just by copying one device from one subsystem to the other, like so (now A is in one subsystem and all others in another):
```
<configuration>
<bus name="vcan0" baudrate="500000">
<device name="A">
<send id="3AB" length="3" period="1.0" />
<send id="1AC" length="3" period="1.0" />
<receive id="2BA" />
</device>
</bus>
<bus name="vcan1" baudrate="500000">
<device name="C">
<send id="4CD" length="3" period="1.0" />
<receive id="1AC" />
</device>
<device name="D">
<receive id="4CD" />
</device>
<device name="B">
<send id="2BA" length="3" period="1.0" />
<receive id="3AB" />
</device>
</bus>
</configuration>
```
\ No newline at end of file
name := "canxml"
organization := "de.uni-tuebingen.embedded"
version := "0.1"
isSnapshot := true
publishArtifact in packageDoc := true
// Compiler Options
scalaVersion := "2.12.8"
val scalaCompilerOptions = Seq(
"-encoding", "UTF-8",
"-unchecked",
"-deprecation",
"-Xfuture",
"-feature",
"-Yno-adapted-args",
"-language:postfixOps",
"-opt:l:method",
"-opt:l:inline",
"-opt-inline-from:de.uni_tuebingen.embedded.eda"
)
scalacOptions in (Compile, compile) ++= scalaCompilerOptions
scalacOptions in Test ++= scalaCompilerOptions
javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-Xlint", "-target:jvm-1.8")
// Further resolvers to overcome waiting for Maven central to sync some changes
resolvers += Resolver.sonatypeRepo("releases")
// Library Dependencies
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.1.1"
if [ -z "$1" ]; then
echo "Usage: ./canxml.sh <config.xml>"
else
sudo killall -9 canbusload 2> /dev/null
sudo java -jar target/scala-2.12/canxml-assembly-0.1.jar $1
fi
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
package de.uni_tuebingen.embedded
import scala.collection.mutable
case class Bus(name: String) {
name match {
case "vcan0"|"vcan1"|"vcan2"|"vcan3" =>
case _ =>
println("Name needs to be one of: vcan0, vcan1, vcan2, vcan3")
System.exit(0)
}
var baudrate: Int = 500000
val devices = mutable.HashMap.empty[String, Device]
def device(name: String): Device = this.devices.getOrElseUpdate(name, new Device(name))
}
package de.uni_tuebingen.embedded
import scala.collection.mutable
class BusSystem {
val busses = mutable.HashMap.empty[String, Bus]
val messages = mutable.HashMap.empty[BigInt, Message]
def bus(name: String): Bus = this.busses.getOrElseUpdate(name, new Bus(name))
def message(id: BigInt): Message = this.messages.getOrElseUpdate(id, new Message(id))
def info(): Unit = {
println(s"There are ${busses.size} busses: ")
for (bus <- busses.values.toArray.sortBy(_.name)) {
println(s" -${bus.name}")
}
println()
for ((_, bus) <- busses) {
println(s"The following devices are attached to bus ${bus.name}:")
for (dev <- bus.devices.values.toArray.sortBy(_.name)) {
println(s" - ${dev.name}")
}
}
println()
println("The following messages per device are sent:")
for ((_, bus) <- busses) {
for ((_, dev) <- bus.devices) {
print(s" - ${dev.name}: ")
val iterator = dev.send.iterator
while (iterator.hasNext) {
val message = iterator.next()
print(s"${message.message.id.toString(16).toUpperCase()} (${message.period}ms, ${message.length} bytes)")
if (iterator.hasNext)
print(", ")
}
println()
}
}
println()
println("The following messages per device are received:")
for ((_, bus) <- busses) {
for ((_, dev) <- bus.devices) {
print(s" - ${dev.name}: ")
val iterator = dev.receive.iterator
while (iterator.hasNext) {
val message = iterator.next()
print(s"${message.id.toString(16).toUpperCase()}")
if (iterator.hasNext)
print(", ")
}
println()
}
}
println()
println("The following messages are sent over gateways: ")
val readersWriters = determineReadersWriters()
for ((message, (readers, writers)) <- readersWriters) {
for (writer <- writers; reader <- readers if reader != writer) {
println(s" - ID: ${message.id.toString(16).toUpperCase()}, From: ${writer.name}, To: ${reader.name}")
routeMessage(message, writer, reader)
}
}
}
def simulate(): Unit = {
loadDrivers()
clearMessages()
for ((_, bus) <- this.busses)
initializeBus(bus)
clearRoutes()
val readersWriters = determineReadersWriters()
for ((message, (readers, writers)) <- readersWriters) {
for (writer <- writers; reader <- readers if reader != writer) {
routeMessage(message, writer, reader)
}
}
for ((_, bus) <- this.busses) {
for ((_, device) <- bus.devices) {
for (message <- device.send) {
addMessage(bus, message.message.id, message.period, message.length)
}
}
}
val process = printWorkloads()
System.in.read()
process.destroyForcibly()
}
private def determineReadersWriters(): scala.collection.Map[Message, (Set[Bus], Set[Bus])] = {
val readersWriters = mutable.HashMap.empty[Message, (Set[Bus], Set[Bus])]
for ((_, bus) <- this.busses) {
for ((_, device) <- bus.devices) {
for (message <- device.receive) {
val (readers, writers) = readersWriters.getOrElse(message, Set.empty[Bus] -> Set.empty[Bus])
readersWriters += message -> ((readers + bus) -> writers)
}
for (message <- device.send) {
val (readers, writers) = readersWriters.getOrElse(message.message, Set.empty[Bus] -> Set.empty[Bus])
readersWriters += message.message -> (readers -> (writers + bus))
}
}
}
readersWriters
}
private def loadDrivers(): Unit = {
val builder = new ProcessBuilder("modprobe", "-a", "can", "can_raw", "can_gw", "vcan")
val process = builder.start()
process.waitFor()
for ((_, bus) <- this.busses) {
initializeBus(bus)
}
}
private def initializeBus(bus: Bus): Unit = {
val builder0 = new ProcessBuilder("ip", "link", "add", "dev", bus.name, "type", "vcan")
val process0 = builder0.start()
process0.waitFor()
val builder1 = new ProcessBuilder("ip", "link", "set", "up", bus.name)
val process1 = builder1.start()
process1.waitFor()
}
private def clearMessages(): Unit = {
val builder = new ProcessBuilder("killall", "cangen")
val process = builder.start()
process.waitFor()
}
private def clearRoutes(): Unit = {
val builder = new ProcessBuilder("cangw", "-F")
val process = builder.start()
process.waitFor()
}
private def routeMessage(message: Message, from: Bus, to: Bus): Unit = {
val builder = new ProcessBuilder("cangw", "-A", "-s", from.name, "-d", to.name, "-e", "-f", s"${message.id.toString(16).toUpperCase()}:7FF")
val process = builder.start()
process.waitFor()
}
private def addMessage(bus: Bus, id: BigInt, period: Double, length: Int): Process = {
val builder = new ProcessBuilder("cangen", bus.name, "-g", period.toString, "-I", id.toString(16), "-L", length.toString, "-D", "r")
val process = builder.start()
process
}
private def printWorkloads(): Process = {
val args = mutable.ArrayBuffer.empty[String]
args += "canbusload"
for ((_, bus) <- this.busses) {
args += s"${bus.name}@${bus.baudrate}"
}
val builder = new ProcessBuilder(args: _*)
val process = builder.inheritIO().start()
process
}
}
package de.uni_tuebingen.embedded
import scala.collection.mutable
case class Device(name: String) {
val receive = mutable.HashSet.empty[Message]
val send = mutable.ArrayBuffer.empty[SentMessage]
def reads(message: Message): Unit = {
this.receive += message
}
def writes(message: Message, period: Double, length: Int): Unit = {
this.send += SentMessage(message, period, length)
}
}
package de.uni_tuebingen.embedded
case class Message(id: BigInt)
\ No newline at end of file
package de.uni_tuebingen.embedded
case class SentMessage(message: Message, period: Double, length: Int)
\ No newline at end of file
package de.uni_tuebingen.embedded
import java.nio.file.Paths
import scala.xml.XML
object main extends App {
if (args.length != 1) {
println("Usage: cansim <configuration.xml>")
System.exit(0)
}
val configFile = Paths.get(args(0))
val xml = XML.loadFile(configFile.toFile)
print("Load XML configuration...")
val system = new BusSystem
for(xmlBus <- xml \ "bus"){
val bus = system.bus(xmlBus \@ "name")
val xmlBaudrate = xmlBus \@ "baudrate"
try {
bus.baudrate = xmlBaudrate.toInt
} catch {
case _: Throwable =>
println(s"No valid integer: $xmlBaudrate")
System.exit(1)
}
for(xmlDevice <- xmlBus \ "device"){
val device = bus.device(xmlDevice \@ "name")
for(xmlSend <- xmlDevice \ "send"){
val xmlId = xmlSend \@ "id"
val xmlLength = xmlSend \@ "length"
val xmlPeriod = xmlSend \@ "period"
var id = BigInt(0)
var length = 0
var period = 0.0
try {
id = BigInt(xmlId, 16)
} catch {
case _: Throwable =>
println(s"No valid hex ID: $xmlId. Needs to fulfill: [0-9a-fA-F]+")
System.exit(1)
}
try {
length = xmlLength.toInt
} catch {
case _: Throwable =>
println(s"No valid value for length: $xmlLength. Needs to fulfill: [0-9]+")
System.exit(1)
}
try {
period = xmlPeriod.toDouble
} catch {
case _: Throwable =>
println(s"No valid value for period: $xmlPeriod. Needs to fulfill: [0-9]+(\\.[0-9]+)?")
System.exit(1)
}
val message = system.message(id)
device.writes(message, period, length)
}
for(xmlReceive <- xmlDevice \ "receive"){
val xmlId = xmlReceive \@ "id"
var id = BigInt(0)
try {
id = BigInt(xmlId, 16)
} catch {
case _: Throwable =>
println(s"No valid hex ID: $xmlId. Needs to fulfill: [0-9a-fA-F]+")
System.exit(1)
}
val message = system.message(id)
device.reads(message)
}
}
}
println("Done.")
system.info()
println()
println("Start simulation...")
system.simulate()
}
\ No newline at end of file
<configuration>
<!-- With <bus> you can specify a new CAN bus subsystem -->
<bus name="vcan0" baudrate="500000">
<!-- With <device> you can specify a new ECU device that is attached to the current CAN bus subsysten -->
<device name="A">
<!-- Each <send> specifies a message of a given ID (hexadecimal), length (number of bytes) and sending period (in milliseconds) that is sent by this device -->
<send id="3AB" length="3" period="1.0" />
<send id="1AC" length="3" period="1.0" />
<!-- Each <receive> specifies a message of a given ID that is received by this device -->
<receive id="2BA" />
</device>
<device name="B">
<send id="2BA" length="3" period="1.0" />
<receive id="3AB" />
</device>
</bus>
<bus name="vcan1" baudrate="500000">
<device name="C">
<send id="4CD" length="3" period="1.0" />
<receive id="1AC" />
</device>
<device name="D">
<receive id="4CD" />
</device>
</bus>
</configuration>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment