import Stream._ import java.io.{ InputStream, EOFException } import java.net.{ InetAddress, InetSocketAddress, Socket } import scala.xml.{XML, Node, NodeSeq} class EbusDefinition(val ebusXmlFile: String) { val xml = XML.loadFile(ebusXmlFile) def packetFromCommandId(primaryCommand: Char, secondaryCommand: Char): NodeSeq = { val packet = (xml \ "packets" \ "packet").filter(n => n.attribute("primary").get.text.toInt == primaryCommand && n.attribute("secondary").get.text.toInt == secondaryCommand) packet } } abstract class EbusField(val name : String, val offset : Int); class Data2c(override val name: String, override val offset : Int) extends EbusField(name, offset) { } object EbusField { def getFields(ebusDefinition : EbusDefinition, primaryCommand: Char, secondaryCommand: Char): List[EbusField] = { val packetDef = ebusDefinition.packetFromCommandId(primaryCommand, secondaryCommand) for (fieldDef <- (packetDef \ "fields").head.child) { App.println(buildField(fieldDef)) } null } def buildField(field :Node) : Option[EbusField] = { if (field.label == "data2c") { val name = field.attribute("name").get.text val offset = field.attribute("offset").get.text.toInt return Some(new Data2c(name, offset)) } return None } } class EbusL2Packet(val source: Char, val destination: Char, val primaryCommand: Char, val secondaryCommand: Char, val payloadLength: Char, val payload: List[Char], val crc: Char, val rawLength: Int) { val length = 6 + payload.size override def toString: String = { val p = if (payload.size == 0) "" else payload.map(a => "%02x".format(a.toInt)). reduceLeft((a: Any, b: String) => a + " " + b) "L2Packet: d=%02x pC=%02x sC=%02x pL=%02x crc=%02x p=%s".format( destination.toInt, primaryCommand.toInt, secondaryCommand.toInt, payloadLength.toInt, crc.toInt, p.toString) } /** * Die CRC-Prüfsumme wird vom jeweiligen Sender über der expandierten Bytesendefolge mit dem * Generatorpolynom X8 +X7 + X4 + X3 + X +1 gebildet und als letztes Byte der Nachricht, * sofern notwendig, sogar expandiert gesendet. * * Generatorpol. 11001101 */ def calcCrc: Char = { //val foo = payload.reduce((a, b) => ((a.intValue + b.intValue) % 256).toChar) //val sum = destination + primaryCommand + secondaryCommand + payloadLength + foo val crc = 1 + 1 1 } } object EbusL2Packet { /** * Wird von {@link EbusReader} mit einer Streamposition aufgerufen. * Ausgehend von dieser Position wird versucht ein Paket aufzubauen, * und anschliesend die Checksumme geprüft. * Im Erfolgsfall wird eine EbusL2Packet-Instanz als Option zurückgegen, * sonst None */ def apply(stream: Stream[Int]): Option[EbusL2Packet] = { try { var rawLength = 0 val source = stream.apply(0).toChar val destination = stream.apply(1).toChar val primaryCommand = stream.apply(2).toChar val secondaryCommand = stream.apply(3).toChar val payloadLength = stream.apply(4).toChar rawLength = 5 // Maximale payload Grösse = 16 Byte if (payloadLength > 16) { println("Erkannte payload Groesse zu gross: %d".format(payloadLength.toInt)) return None } var payload = List[Char]() for (i <- 0 until payloadLength) { var it = stream.apply(rawLength).toChar rawLength += 1 // Handle escape sequences // "eBUS Spezifikation Physikalische Schicht – OSI 1 // Verbindungsschicht – OSI 2 V.1.3.1" auf Seite 8 if (it == 0xa9.toChar) { val itNext = stream.apply(rawLength).toChar rawLength += 1 if (itNext == 0x00.toChar) { it = 0xa9.toChar } else if (itNext == 0x01.toChar) { it = 0xaa.toChar } else { App.println("Ignore escape sequence 0xa9 0x%02x".format(itNext)) rawLength -= 1 } } payload = payload :+ it } val crc = stream.apply(5 + payloadLength).toChar val ack = stream.apply(6 + payloadLength).toChar if (ack != 0x00.toChar) { println("Fehlerhaftes ACK") return None } rawLength += 2 if (stream.apply(6+payloadLength).toChar == 0xaa.toChar) { // Broadcast oder Master-Master return Some(new EbusL2Packet(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength)) } else { // Master-Slave } } catch { case exc: IndexOutOfBoundsException => { App.println(exc.toString) exc.printStackTrace return None } } } } object EbusReader { def apply(streamI: Stream[Int]) : EbusL2Packet = { var stream = streamI while (true) { // Überspringe Synchronisationszeichen //stream = stream.dropWhile(v => v.toChar == 0xaa.toChar) // Überspringe solange nicht mindestens 6 Zeichen ohne 0xaa vorkommen while (stream.take(6).filter({v :Int => v.toChar == 0xaa.toChar}).size > 0) { // println("skip") stream = stream.drop(1) } EbusL2Packet(stream) match { case Some(packet) => { return packet } case None => { // Es konnte kein Paket eingelesen werden // verschiebe die Anfangsposition um ein Byte stream = stream.drop(1) App.println("skip 1 byte") } } } null } } object EbusL7Packet { def apply(l2packet : EbusL2Packet, ebusDefinition : EbusDefinition) : Option[EbusL7Packet] = { val packetDef = ebusDefinition.packetFromCommandId( l2packet.primaryCommand, l2packet.secondaryCommand) if (packetDef.isEmpty) return None else return Some(new EbusL7Packet(l2packet, packetDef)) } } class EbusL7Packet(val l2packet : EbusL2Packet, val packetDefinition : NodeSeq) { def name : String = { packetDefinition.map({p :Node => p.attributes.apply("name")}).mkString } override def toString : String = { "EbusL7: name=%s".format(name) } } object App { val ebusDefinition = new EbusDefinition("/home/yvesf/vcs/ebus/ebus/ebus-xml/ebus.xml") var source: InputStream = null def println(msg : Any) { Predef.println("[ebus] " + msg.toString) } def main(args: Array[String]): Unit = { if (args.size == 1 && args(0) == "-") { println("Use stdin as source") source = System.in } else if (args.size == 2) { val addr = InetAddress.getByName(args(0)) val sockAddr = new InetSocketAddress(addr, args(1).toInt) val s = new Socket() s.connect(sockAddr) println("Connected to %s %d".format(args(0), args(1).toInt)) source = s.getInputStream } else if (args.size == 3 && args(0) == "dump") { val addr = InetAddress.getByName(args(1)) val sockAddr = new InetSocketAddress(addr, args(2).toInt) val s = new Socket() s.connect(sockAddr) System.err.println("Connected to %s %d".format(args(1), args(2).toInt)) while (true) { val v = s.getInputStream.read if (v == -1) { println("Fehler: las -1") System.exit(1) } print(v.toChar) } } else { println("Missing Arguments") println(" -") println(" Read from stdin") println("HOST PORT") println(" Read from TCP Server") println("dump HOST PORT") println(" Dump TCP-Stream") System.exit(1) } val read = (() => { val value = source.read if (value == -1) throw new EOFException("End of File reached (read returned -1)") value }) var stream: Stream[Int] = Stream.continually(read()) while (true) { EbusReader(stream) match { case l2packet : EbusL2Packet => { EbusL7Packet(l2packet, ebusDefinition) match { case Some(l7packet) => { println(l7packet) } case _ => {} } println(l2packet) // Spule den Stream zur naechstmoeglichen Paketposition stream = stream.drop(l2packet.rawLength) } case null => { println("null") System.exit(1) } } } println("exiting") source.close } }