import Stream._ import java.io.{ InputStream, EOFException } import java.net.{ InetAddress, InetSocketAddress, Socket } class EbusDefinition(val ebusXmlFile: String) { import scala.xml.XML import scala.xml.NodeSeq 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 } } object EbusPacket { /** * 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 EbusPacket-Instanz als Option zurückgegen, * sonst None */ def apply(stream: Stream[Int]): Option[EbusPacket] = { 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 var payload = new Array[Char](payloadLength) 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 { println("Ignore escape sequence 0xa9 0x%02x".format(itNext)) rawLength -= 1 } } payload.update(i, it) } val crc = stream.apply(5 + payloadLength).toChar rawLength += 1 return Some(new EbusPacket(source, destination, primaryCommand, secondaryCommand, payloadLength, payload, crc, rawLength)) } catch { case exc: IndexOutOfBoundsException => { println(exc.toString) exc.printStackTrace return None } } } } class EbusPacket(val source: Char, val destination: Char, val primaryCommand: Char, val secondaryCommand: Char, val payloadLength: Char, val payload: Array[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) "Ebus: 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 */ private def calcCrc: Char = { val sum = destination + primaryCommand + secondaryCommand + payloadLength + payload.reduce((a, b) => ((a.intValue + b.intValue) % 256).toChar) val crc = 1 + 1 1 } } object EbusReader { def apply(streamI: Stream[Int]): EbusPacket = { var stream = streamI while (true) { // Überspringe Synchronisationszeichen stream = stream.dropWhile(v => v.toChar == 0xaa.toChar) EbusPacket(stream) match { case Some(packet) => { stream = stream.drop(packet.rawLength) return packet } case None => { // Es konnte kein Paket eingelesen werden // verschieben die Anfangsposition um ein Byte stream = stream.drop(1) println("skip 1 byte") } } } null } } object App { val ebusDefinition = new EbusDefinition("/home/yvesf/vcs/ebus/ebus/ebus-xml/ebus.xml") var source: InputStream = null def main(args: Array[String]): Unit = { if (args.size == 1) { 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) System.exit(1) print(v.toChar) } } else { println("Missing Arguments") println(" Read from stdin: -") println(" Read from TCP: HOST PORT") println(" Read from TCP and dump to stdout: dump HOST PORT") System.exit(1) } val read = (() => { val value = source.read if (value == -1) throw new EOFException("End of File reached (read returned -1)") value }) val stream: Stream[Int] = Stream.continually(read()) while (true) { EbusReader(stream) match { case x: EbusPacket => { println(x) val p = ebusDefinition.packetFromCommandId(x.primaryCommand, x.secondaryCommand) if (p.size == 1) println(p(0).attribute("name").get) } case null => { println("null") } } } println("exiting") source.close } }