import Stream._ import java.io.{InputStream, EOFException} import java.net.{InetAddress, InetSocketAddress, Socket} import scala.xml.{XML, Elem, Node, NodeSeq} /// Ebus Bus Definition object EbusDevice { def apply(node : Node): EbusDevice = { new EbusDevice( Integer.parseInt(node.attribute("address").head.text, 16), node.attribute("type").head.text, node.attribute("name").head.text) } } class EbusDevice(val address:Int, val busType :String, val name: String) { override def toString() : String = "EbusDevice: address=0x%02x type=%s name=%s".format( address, busType, name) } object EbusPacket { def apply(node : Node): EbusPacket = { new EbusPacket( EbusDefinition.parseHexNode(node.attribute("primary").get.head), EbusDefinition.parseHexNode(node.attribute("secondary").get.head), node.attribute("name").get.head.text, List[EbusField]()) } } class EbusPacket(val primary: Int, val secondary: Int, val name: String, val fields: List[EbusField]) { override def toString(): String = { "EbusPacket: primary=0x%02x secondary=0x%02x name=%s".format( primary, secondary, name) } }; object EbusDefinition { def parseHexNode(n: Node):Int = Integer.parseInt(n.text,16) def fromFile(ebusXMLPath : String) = { new EbusDefinition(XML.loadFile(ebusXMLPath)) } } class EbusDefinition(val xml : Elem) { def packet(primaryCommand: Int, secondaryCommand: Int): Option[EbusPacket] = { (xml \ "packets" \ "packet").filter(n => n.attribute("primary").flatMap(_.headOption.map(EbusDefinition.parseHexNode)) == Some(primaryCommand) && n.attribute("secondary").flatMap(_.headOption.map(EbusDefinition.parseHexNode)) == Some(secondaryCommand) ).headOption.map(EbusPacket(_)) } def device(address: Int): Option[EbusDevice] = { (xml \ "devices" \ "device").filter(n => n.attribute("address").flatMap(_.headOption.map(EbusDefinition.parseHexNode)) == Some(address) ).headOption.map(EbusDevice(_)) } } abstract class EbusField(val name : String, val offset : Int); class Data2c(name: String, offset : Int) extends EbusField(name, offset) { } object EbusField { /* def getFields(ebusDefinition : EbusDefinition, primaryCommand: Char, secondaryCommand: Char): List[EbusField] = { val packetDef = ebusDefinition.packet(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 }*/ } /// Ebus Protocol Parser object Ebus { def SYN = 0xaa def ACK_OK = 0x00 def ACK_FAIL = 0xff 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 crc(data: List[Int]): Int = { data.foldLeft(0){ (crc,byte) => crcTab(crc) ^ byte} } } // TODO buildFrom(List(,,,)) class EbusL2Header(val source: Int, val destination: Int, val primaryCommand: Int, val secondaryCommand: Int, val payloadLength: Int) { override def toString: String = { "L2Header: QQ=0x%02x ZZ=0x%02x PB=0x%02x SB=0x%02x NN=0x%02x".format( source, destination, primaryCommand, secondaryCommand, payloadLength) } }; abstract class EbusL2Packet(val l2Header : EbusL2Header, val payload: List[Int], val crc: Int, val rawLength: Int) { override def toString: String = { val p = payload.map(a => "%02x".format(a.toInt)).reduceLeft((a: Any, b: String) => a + " " + b) "L2Packet: (%s) payload=%s CRC=0x%02x rawLength=%d".format( l2Header.toString, p, crc, rawLength) } } class EbusL2MasterMasterPacket(l2Header : EbusL2Header, payload: List[Int], crc: Int, rawLength : Int) extends EbusL2Packet(l2Header, payload, crc, rawLength); class EbusL2BroadcastPacket(l2Header : EbusL2Header, payload: List[Int], crc: Int, rawLength : Int) extends EbusL2Packet(l2Header, payload, crc, rawLength); class EbusL2MasterSlavePacket(l2Header: EbusL2Header, payload: List[Int], crc: Int, val payloadSlaveLength: Int, val payloadSlave: List[Int], val crcSlave: Int, rawLength: Int) extends EbusL2Packet(l2Header, payload, crc, rawLength); class EbusL2ParseException(val message: String, val data: List[Int], val skipbytes: Int) extends Exception; 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]): EbusL2Packet = { 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 EbusL2ParseException("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") val l2Header = new EbusL2Header(source, destination, primaryCommand, secondaryCommand, payloadLength) // Maximale payload Grösse = 16 Byte if (payloadLength > PAYLOAD_LENGTH_MAX) { throw new EbusL2ParseException("Erkannte payload Groesse zu gross: %d".format(payloadLength), stream.slice(0, rawLength).toList, rawLength); } else if (payloadLength == 0) { // Unklar throw new EbusL2ParseException("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 = Ebus.crc(List(source, destination, primaryCommand, secondaryCommand, payloadLength) ++ payload) if (crc != crcCalc) { throw new EbusL2ParseException("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 EbusL2ParseException("Bad SYN: 0x%02x".format(syn), stream.slice(0, rawLength).toList, rawLength); } return new EbusL2MasterMasterPacket(l2Header, 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 EbusL2ParseException("Bad ACK: 0x%02x".format(ack), stream.slice(0, rawLength).toList, rawLength); } return new EbusL2MasterMasterPacket(l2Header, 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 EbusL2ParseException("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 = Ebus.crc(List(payloadSlaveLength) ++ payloadSlave) if (crcSlave != crcSlaveCalc) { throw new EbusL2ParseException("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 EbusL2ParseException("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 EbusL2ParseException("Master-Slave SYN nicht 0xaa sondern 0x%02x".format(synSlave), stream.slice(0, rawLength).toList, rawLength); } return new EbusL2MasterSlavePacket(l2Header, 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 { val packet = EbusL2Packet(stream) stream = stream.drop(packet.rawLength) return packet } catch { case exc : EbusL2ParseException => { 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; } } abstract class EbusL7Packet(val l2Packet: EbusL2Packet) {}; class EbusL7MasterMasterPacket(l2Packet: EbusL2MasterMasterPacket) extends EbusL7Packet(l2Packet) {}; class EbusL7MasterSlavePacket(l2Packet: EbusL2MasterSlavePacket) extends EbusL7Packet(l2Packet) {}; class EbusL7BroadcastPacket(l2Packet: EbusL2BroadcastPacket) extends EbusL7Packet(l2Packet) {}; /// Application object App { val ebusDefinition = EbusDefinition.fromFile("../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 """) exit(1) } } 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 { val packet = reader.next val sourceDevice = ebusDefinition.device(packet.l2Header.source) val destDevice = ebusDefinition.device(packet.l2Header.destination) val packetDef = ebusDefinition.packet(packet.l2Header.primaryCommand, packet.l2Header.secondaryCommand) println(sourceDevice) println(destDevice) println(packetDef) //println(packet) } catch { case exc : EOFException => { println("EOF") return; } } } })() println("exiting") source.close } }