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) { 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 rawL=%d".format( destination.toInt, primaryCommand.toInt, secondaryCommand.toInt, payloadLength.toInt, crc.toInt, p.toString, rawLength) } /** * 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 EbusL2MasterSlavePacket(source: Int, destination: Int, primaryCommand: Int, secondaryCommand: Int, payloadLength: Int, payload: List[Int], crc: Int, val payloadSlaveLength : Int, val payloadSlave : List[Int], val crcSlave : Int, rawLength: Int) extends EbusL2Packet(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength); class FakePacket(val rawLength : Int); class EbusParseException(val message : String, val data : List[Int]); object CRC { val crcTab = List[Int]( 0, 155, 173, 54, 193, 90, 108, 247, 25, 130, //0 -9 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, //10-19 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, //20 71, 220, 100, 255, 201, 82, 165, 62, 8, 147, //30 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, //100 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, //200 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123) def apply(data : List[Int]) : Int = { // initialCrc=0 return data.foldLeft(0){ (crc,byte) => crcTab(crc) ^ byte} } } 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] = { var mystream = stream var rawLength = 0 def readBytes(n:Int, msg : String = "") : List[Int] = { val d = mystream.slice(rawLength,rawLength+n).toList rawLength = rawLength + n println("RAW %15s: ".format(msg) + d.map((it) => "0x%02x".format(it)).foldLeft(""){(s1,s2) => s1+","+s2}) return d } val source :: destination :: primaryCommand :: secondaryCommand :: payloadLength :: _ = readBytes(5, "QQ|ZZ|PB|SB|NN") // Maximale payload Grösse = 16 Byte if (payloadLength > 16) { println("Erkannte payload Groesse zu gross: %d".format(payloadLength)) return Some(new FakePacket(rawLength + 1)) } else if (payloadLength == 0) { // Unklar println("Überspringe payloadLength mit Länge = 0") return Some(new FakePacket(rawLength + 1)); } // "eBUS Spezifikation Physikalische Schicht – OSI 1 // Verbindungsschicht – OSI 2 V.1.3.1" page 8 val payload = Range(0,payloadLength).map({ i => readBytes(1, "payload " + i)(0) match { case 0xa9 => { readBytes(1, "payload esc "+i)(0) match { case 0x00 => 0xa9 case 0x01 => 0xaa case code : Int => { // throw App.println("Invalid escape sequence (0xa9 0x%02x)".format(code)) return None } } } case value : Int => value } }).toList val crc = readBytes(1,"CRC")(0) val crcCalc = CRC(List(source, destination, primaryCommand, secondaryCommand, payloadLength) ++ payload) if (crc != crcCalc) { println("CRC mismatch (read) 0x%02x != 0x%02x (calc)".format(crc, crcCalc)) return Some(new FakePacket(rawLength + 1)) // + SYN } // Broadcast Packet ends here if (destination == 0xfe) { val syn = readBytes(1,"SYN(broadcast)")(0) if (syn != 0xaa) { App.println("bad SYN 0x%02x".format(syn)) return None } return Some(new EbusL2Packet(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength)) } val ack = readBytes(1,"ACK")(0) val syn = readBytes(1, "SYN/NN2")(0) // Master-Master Packet ends here if (syn == 0xaa) { if (ack != 0x00) { App.println("bad ACK=0x%02x".format(syn)) } return Some(new EbusL2Packet(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength)) } // otherwise this is a Master-Slave Telegramm // and syn is payloadSlave length val payloadSlaveLength = syn if (payloadSlaveLength > 16) { println("payloadSlaveLength > 16: 0x%02x".format(payloadSlaveLength)) return None } val payloadSlave = Range(0,payloadSlaveLength).map({ i => readBytes(1, "payloadSlave "+i)(0) match { case 0xa9 => { readBytes(1,"payloadSlave(esc) "+i)(0) match { case 0x00 => 0xa9 case 0x01 => 0xaa case code : Int => { // throw App.println("Invalid escape sequence (0xa9 0x%02x)".format(code)) return None } } } case value : Int => value } }).toList val crcSlave = readBytes(1,"crcSlave")(0) val crcSlaveCalc = CRC(List(payloadSlaveLength) ++ payloadSlave) if (crcSlave != crcSlaveCalc) { println("CRC mismatch (read) 0x%02x != 0x%02x (calc)".format(crcSlave, crcSlaveCalc)) return Some(new FakePacket(rawLength + 2)) // + ACK + SYN } val ackSlave = readBytes(1,"ACKslave")(0) if (ackSlave != 0x00) { App.println("Master-Slave ACK nicht 0x00 sondern 0x%02x".format(ackSlave)) return None } val synSlave= readBytes(1,"SYNslave")(0) if (synSlave != 0xaa) { App.println("Master-Slave SYN nicht 0xaa sondern 0x%02x".format(synSlave)) return None } return Some(new EbusL2MasterSlavePacket(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, payloadSlaveLength, payloadSlave, crcSlave, rawLength)); } } class EbusReader(var stream : Stream[Int]) { def next() : EbusL2Packet = { while (true) { // Überspringe Synchronisationszeichen var i = 0 while (stream(0) == 0xaa) { stream = stream.drop(1) i = i +1 } println("Skipped " + i + " SYNs") EbusL2Packet(stream) match { case Some(packet : EbusL2Packet) => { stream = stream.drop(packet.rawLength) return packet } case Some(packet : FakePacket) => { println("Fake paket - forward %d bytes".format(packet.rawLength)) stream = stream.drop(packet.rawLength) } 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) } } } return 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()) // Synchronisiere while (stream(0) != 0xaa && stream(1) != 0xaa) { stream = stream.drop(1) } val reader = new EbusReader(stream) while (true) { try { println(reader.next) } catch { case exc : EOFException => { println("EOF") source.close exit(0) } } } println("exiting") source.close } } App.main(Array("-"))