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: Int, secondaryCommand: Int): 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: Int, val destination: Int, val primaryCommand: Int, val secondaryCommand: Int, val payloadLength: Int, val payload: List[Int], val crc: Int, 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: Int = { //val foo = payload.reduce((a, b) => ((a.intValue + b.intValue) % 256).toChar) //val sum = destination + primaryCommand + secondaryCommand + payloadLength + foo val crc = 1 + 1 1 } } class FakePacket(val length : Int); 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[Any] = { try { var rawLength = 0 val source = stream(0) val destination = stream(1) val primaryCommand = stream(2) val secondaryCommand = stream(3) val payloadLength = stream(4) rawLength = 5 println("source=0x%02x destination=0x%02x primaryCommand=0x%02x secondaryCommand=0x%02x payloadLength=0x%02x".format( source, destination, primaryCommand, secondaryCommand, payloadLength)) // Maximale payload Grösse = 16 Byte if (payloadLength > 16) { println("Erkannte payload Groesse zu gross: %d".format(payloadLength)) return None } else { println("Payload = " + payloadLength); } var payload = List[Int]() for (i <- 0 until payloadLength) { var it = stream(rawLength) rawLength += 1 // Handle escape sequences // "eBUS Spezifikation Physikalische Schicht – OSI 1 // Verbindungsschicht – OSI 2 V.1.3.1" auf Seite 8 if (it == 0xa9) { val itNext = stream.apply(rawLength) rawLength += 1 if (itNext == 0x00) { it = 0xa9 } else if (itNext == 0x01) { it = 0xaa } else { App.println("Ignore escape sequence 0xa9 0x%02x".format(itNext)) rawLength -= 1 } } payload = payload :+ it println("Payload: " + payload.map((it) => "0x%02x".format(it)).foldLeft(""){(s1,s2) => s1+","+s2}) } val crc = stream.apply(rawLength + 0) println("CRC=0x%x".format(crc)) val ack = stream.apply(rawLength + 1) println("ACK=0x%02x".format(ack)) if (destination == 0xfe) { // Broadcast if (ack != 0xaa) { println("ACK Bei Broadcast Paket unerwertet nicht 0xaa sondern 0x%02x".format(ack)) } } else { if (ack != 0x00) { println("ACK unerwartet nicht 0x00 sondern 0x%02x".format(ack)) } } rawLength += 0 if (stream.apply(rawLength + 4) == 0xaa) { // Broadcast oder Master-Master return Some(new EbusL2Packet(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength)) } else { // Master-Slave return Some(new FakePacket(rawLength)) } } 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) { // Synchronisiere while (stream(0) != 0xaa && stream(1) != 0xaa) { stream = stream.drop(1) } // Überspringe Synchronisationszeichen while (stream(0).toChar == 0xaa) { stream = stream.drop(1) } EbusL2Packet(stream) match { case Some(packet : EbusL2Packet) => { return packet } case Some(packet : FakePacket) => { println("Fake paket - forward %d bytes".format(packet.length)) stream = stream.drop(packet.length) } case None => { // Es konnte kein Paket eingelesen werden // verschiebe die Anfangsposition um ein Byte App.println("Paket konnte nicht gelesen werden, verschiebe start um 1 byte") stream = stream.drop(1) } } } 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: read == -1") System.exit(1) } System.out.write(v) } } 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) { try { EbusReader(stream) match { case l2packet : EbusL2Packet => { println("Read l2 packaet: " + l2packet); /* 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) } } } catch { case exc : EOFException => { println("EOF") source.close exit(0) } } } println("exiting") source.close } } App.main(Array("-"))