#!/bin/sh exec scala "$0" "$@" !# 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) } } 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], val skipbytes : Int) extends Exception; 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 Ebus { def SYN = 0xaa def ACK_OK = 0x00 def ACK_FAIL = 0xff } object EbusL2Packet { def PAYLOAD_LENGTH_MAX = 16 /** * 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)).reduceRight((s1,s2) => s1+","+s2)) return d } def readPayloadByte(i:Int) : Int = 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 new EbusParseException("Invalid escape sequence (0xa9 0x%02x)".format(code), stream.slice(0, rawLength).toList, rawLength); } } } case value : Int => value } val source :: destination :: primaryCommand :: secondaryCommand :: payloadLength :: _ = readBytes(5, "QQ|ZZ|PB|SB|NN") // Maximale payload Grösse = 16 Byte if (payloadLength > PAYLOAD_LENGTH_MAX) { throw new EbusParseException("Erkannte payload Groesse zu gross: %d".format(payloadLength), stream.slice(0, rawLength).toList, rawLength); } else if (payloadLength == 0) { // Unklar throw new EbusParseException("Skip packet with payloadLength = 0", stream.slice(0, rawLength).toList, rawLength + 1); } val payload = Range(0,payloadLength).map(readPayloadByte).toList val crc = readBytes(1,"CRC")(0) val crcCalc = CRC(List(source, destination, primaryCommand, secondaryCommand, payloadLength) ++ payload) if (crc != crcCalc) { throw new EbusParseException("CRC mismatch (read) 0x%02x != 0x%02x (calc)".format(crc, crcCalc), stream.slice(0, rawLength).toList, rawLength + 1); } // Broadcast Packet ends here if (destination == 0xfe) { val syn = readBytes(1,"SYN(broadcast)")(0) if (syn != Ebus.SYN) { throw new EbusParseException("Bad SYN: 0x%02x".format(syn), stream.slice(0, rawLength).toList, rawLength); } 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 == Ebus.SYN) { if (ack != Ebus.ACK_OK) { throw new EbusParseException("Bad ACK: 0x%02x".format(ack), stream.slice(0, rawLength).toList, rawLength); } 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 > PAYLOAD_LENGTH_MAX) { throw new EbusParseException("payloadSlaveLength > 16: 0x%02x".format(payloadSlaveLength), stream.slice(0, rawLength).toList, rawLength); } val payloadSlave = Range(0,payloadSlaveLength).map(readPayloadByte).toList val crcSlave = readBytes(1,"crcSlave")(0) val crcSlaveCalc = CRC(List(payloadSlaveLength) ++ payloadSlave) if (crcSlave != crcSlaveCalc) { throw new EbusParseException("CRC mismatch (read) 0x%02x != 0x%02x (calc)".format(crcSlave, crcSlaveCalc), stream.slice(0, rawLength).toList, rawLength); } val ackSlave = readBytes(1,"ACKslave")(0) if (ackSlave != Ebus.ACK_OK) { throw new EbusParseException("Master-Slave ACK nicht 0x00 sondern 0x%02x".format(ackSlave), stream.slice(0, rawLength).toList, rawLength); } val synSlave= readBytes(1,"SYNslave")(0) if (synSlave != Ebus.SYN) { throw new EbusParseException("Master-Slave SYN nicht 0xaa sondern 0x%02x".format(synSlave), stream.slice(0, rawLength).toList, rawLength); } 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 stream = stream.dropWhile(_ == Ebus.SYN) try { 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) } } } catch { case exc : EbusParseException => { stream = stream.drop(exc.skipbytes) App.println("Exception: " + exc.message + "\n\tData: " + exc.data.map((it) => "0x%02x".format(it)).reduceRight((s1,s2) => s1+","+s2)) } } } 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]\t" + msg.toString) } def main(args: Array[String]): Unit = { args.toList match { case "-" :: List() => { source = System.in } case host :: port :: List() => { val addr = InetAddress.getByName(host) val sockAddr = new InetSocketAddress(addr, port.toInt) val s = new Socket() s.connect(sockAddr) println("Connected to %s %d".format(args(0), args(1).toInt)) source = s.getInputStream } case "dump" :: host :: port :: List() => { val addr = InetAddress.getByName(host) val sockAddr = new InetSocketAddress(addr, port.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) } } case _ => println( """Usage: PROGRAM ARGUMENTS: - Parse Ebus from stdin dump HOST PORT Read and Dump TCP Connection HOST PORT Parse Ebus from TCP Connection """) } var stream: Stream[Int] = Stream.continually({ val value = source.read if (value == -1) throw new EOFException("End of File reached (read returned -1)") value }) // Synchronisiere while (stream(0) != Ebus.SYN) { stream = stream.drop(1) } val reader = new EbusReader(stream) (() => { while (true) { try { println(reader.next) } catch { case exc : EOFException => { println("EOF") return; } } } })() println("exiting") source.close } } App.main(args)