package ch.elexis.connect.afinion; import gnu.io.SerialPortEvent; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Calendar; import java.util.GregorianCalendar; import ch.elexis.core.ui.importer.div.rs232.AbstractConnection; import ch.elexis.core.ui.util.Log; /** * Ueberarbeitete Version des Handshakes zwischen PC und Afinion * * @author immi * */ public class AfinionConnection extends AbstractConnection { Log _elexislog = Log.get("AfinionConnection"); private static final int NUL = 0x00; private static final int STX = 0x02; private static final int ETX = 0x03; private static final int ACK = 0x06; private static final int DLE = 0x10; private static final int NAK = 0x15; private static final int ETB = 0x17; private static final int LF = 0x0D; private static final long STARTUP_DELAY_IN_MS = 2000; // 60 Sekunden private static final long RESEND_IN_MS = 30000; // 30 Sekunden // Initialisierung public static final int INIT = 0; // 1 Minute warten public static final int WAITING = 1; // Patientenrequest senden public static final int SEND_PAT_REQUEST = 2; // Patient Record Request gesendet. Wartet auf Record Ack Meldung public static final int PAT_REQUEST_SENDED = 3; // Patient Record Request Acknowledge erhalten. Nun können Daten gelesen werden. public static final int PAT_REQUEST_ACK = 4; // Beenden public static final int ENDING = 99; private String awaitPacketNr; private static int pc_packet_nr = 21; private long last_time_ms = 0; private Calendar currentCal = new GregorianCalendar(); // Wird für Fehlerhandling verwendet. Alles wird in console geloggt. private static final boolean debugToConsole = false; public AfinionConnection(String portName, String port, String settings, ComPortListener l){ super(portName, port, settings, l); setState(INIT); } public void setCurrentDate(Calendar cal){ this.currentCal = cal; } /** * Wenn variable debug = true, dann werden alle bytes in die console geloggt. In jedem Fall wird * ins Elexis Log geloggt * * @param text */ private void debug(String text){ _elexislog.log(text, Log.DEBUGMSG); if (debugToConsole) { System.out.print(text); } } /** * Wenn variable debug = true, dann werden alle bytes in die console geloggt. * * @param text */ private void debugln(String text){ _elexislog.log(text, Log.DEBUGMSG); if (debugToConsole) { System.out.println(text); } } /** * Crc calculation */ private static long getCrc(byte[] array){ char crc = 0xFFFF; for (byte b : array) { char value = (char) b; value ^= crc & 0xFF; value ^= (value << 4) & 0xFF; crc = (char) ((crc >>> 8) ^ (value << 8) ^ (value << 3) ^ (value >>> 4)); } return crc; } /** * Retourniert Byte-Array als Textausgabe. */ private String getByteStr(byte[] bytes){ StringBuffer strBuf = new StringBuffer(); int counter = 1; for (byte b : bytes) { if (strBuf.length() > 0) { strBuf.append(", "); //$NON-NLS-1$ if (counter > 16) { strBuf.append("\n"); //$NON-NLS-1$ counter = 1; } } String byteStr = Long.toHexString((long) b); while (byteStr.length() < 2) { byteStr = "0" + byteStr; //$NON-NLS-1$ } strBuf.append("0x" + byteStr); //$NON-NLS-1$ counter++; } return strBuf.toString(); } /** * Textausgabe für debugging * * @param value * @return */ private String getText(int value){ if (value == NUL) { return "<NUL>"; //$NON-NLS-1$ } if (value == STX) { return "<STX>"; //$NON-NLS-1$ } if (value == ETX) { return "<ETX>"; //$NON-NLS-1$ } if (value == ACK) { return "<ACK>"; //$NON-NLS-1$ } if (value == DLE) { return "<DLE>"; //$NON-NLS-1$ } if (value == NAK) { return "<NAK>"; //$NON-NLS-1$ } if (value == ETB) { return "<ETB>"; //$NON-NLS-1$ } return new Character((char) value).toString(); } /** * Liest nächste Elexis-interne PacketNr * * @param strBuf * @param date */ private String nextPacketNr(){ String packetNrStr = new Integer(pc_packet_nr).toString(); pc_packet_nr++; while (packetNrStr.length() < 4) { packetNrStr = "0" + packetNrStr; //$NON-NLS-1$ } return packetNrStr; } /** * Fuegt Datum als String yyyyMMdd HH:mm:ss dazu (ohne Timezone-Umwandlung) */ private void addDate(StringBuffer strBuf){ int day = this.currentCal.get(Calendar.DATE); int month = this.currentCal.get(Calendar.MONTH) + 1; int year = this.currentCal.get(Calendar.YEAR); int hour = this.currentCal.get(Calendar.HOUR_OF_DAY); int minutes = this.currentCal.get(Calendar.MINUTE); int seconds = this.currentCal.get(Calendar.SECOND); String dayStr = (day < 10 ? "0" : "") + Integer.valueOf(day).toString(); //$NON-NLS-1$ //$NON-NLS-2$ String monthStr = (month < 10 ? "0" : "") + Integer.valueOf(month).toString(); String yearStr = Integer.valueOf(year).toString(); String hourStr = (hour < 10 ? "0" : "") + Integer.valueOf(hour).toString(); //$NON-NLS-1$ //$NON-NLS-2$ String minuteStr = (minutes < 10 ? "0" : "") + Integer.valueOf(minutes).toString(); //$NON-NLS-1$ //$NON-NLS-2$ String secondStr = (seconds < 10 ? "0" : "") + Integer.valueOf(seconds).toString(); //$NON-NLS-1$ //$NON-NLS-2$ String dateStr = yearStr + monthStr + dayStr + " " + hourStr + ":" + minuteStr + ":" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + secondStr; strBuf.append(dateStr); String text = "Request starting at:" + dateStr; System.out.println(text); _elexislog.log(text, Log.INFOS); } private void addContentStart(ByteArrayOutputStream os){ debug("<DLE>"); //$NON-NLS-1$ os.write(DLE); debug("<STX>"); //$NON-NLS-1$ os.write(STX); } private void addContentEnd(ByteArrayOutputStream os){ debug("<DLE>"); //$NON-NLS-1$ os.write(DLE); debug("<ETB>"); //$NON-NLS-1$ os.write(ETB); } private void addEnding(ByteArrayOutputStream os) throws IOException{ long crc = getCrc(os.toByteArray()); String crcStr = Long.toHexString(crc).toUpperCase(); while (crcStr.length() < 4) { crcStr = "0" + crcStr; //$NON-NLS-1$ } debug(crcStr); os.write(crcStr.getBytes()); debug("<DLE>"); //$NON-NLS-1$ os.write(DLE); debug("<ETX>"); //$NON-NLS-1$ os.write(ETX); debugln(""); //$NON-NLS-1$ } private void sendPacketACK(String packetNr){ debug("-->"); //$NON-NLS-1$ try { ByteArrayOutputStream os = new ByteArrayOutputStream(); addContentStart(os); debug(packetNr); os.write(packetNr.getBytes()); debug("<ACK>"); //$NON-NLS-1$ os.write(ACK); addContentEnd(os); addEnding(os); debugln("Send: " + getByteStr(os.toByteArray())); //$NON-NLS-1$ if (send(os.toByteArray())) { debugln("OK"); //$NON-NLS-1$ } } catch (IOException e) { e.printStackTrace(); } } private void sendMessageACK(){ debug("-->"); //$NON-NLS-1$ try { ByteArrayOutputStream os = new ByteArrayOutputStream(); addContentStart(os); String packetNr = nextPacketNr(); debug(packetNr); os.write(packetNr.getBytes()); String cmdack = "0025:cmdack@"; //$NON-NLS-1$ debug(cmdack); os.write(cmdack.getBytes()); addContentEnd(os); addEnding(os); debugln("Send: " + getByteStr(os.toByteArray())); //$NON-NLS-1$ if (send(os.toByteArray())) { debugln("OK"); //$NON-NLS-1$ } } catch (IOException e) { e.printStackTrace(); } } private void sendPacketNAK(String packetNr){ debug("-->"); //$NON-NLS-1$ try { ByteArrayOutputStream os = new ByteArrayOutputStream(); addContentStart(os); debug(packetNr); os.write(packetNr.getBytes()); debug("<NAK>"); //$NON-NLS-1$ os.write(NAK); addContentEnd(os); addEnding(os); debugln("Send: " + getByteStr(os.toByteArray())); //$NON-NLS-1$ if (send(os.toByteArray())) { debugln("OK"); //$NON-NLS-1$ } } catch (IOException e) { e.printStackTrace(); } } /** * Patienteanfrage wird gesendet * * @return */ private String sendPatRecordRequest(){ debug("-->"); //$NON-NLS-1$ StringBuffer contentBuf = new StringBuffer(); String packetNrStr = nextPacketNr(); contentBuf.append(packetNrStr); contentBuf.append("0025:record,patient@"); //$NON-NLS-1$ addDate(contentBuf); try { ByteArrayOutputStream os = new ByteArrayOutputStream(); addContentStart(os); debug(contentBuf.toString()); os.write(contentBuf.toString().getBytes()); addContentEnd(os); addEnding(os); debugln("Send: " + getByteStr(os.toByteArray())); //$NON-NLS-1$ if (send(os.toByteArray())) { debugln("OK"); //$NON-NLS-1$ } } catch (IOException e) { e.printStackTrace(); } return packetNrStr; } /** * Liest Stream bis zum nächsten <DEL><ETX> */ private void readToEnd(final InputStream inputStream) throws IOException{ ByteArrayOutputStream os = new ByteArrayOutputStream(); int data = inputStream.read(); while (data != -1 && data != ETX) { while (data != -1 && data != DLE) { os.write(data); data = inputStream.read(); } data = inputStream.read(); } debug(os.toString()); debugln("<DLE><ETX>"); } /** * Liest Stream bis zum nächsten <DEL><ETX> */ private void readToLF(final InputStream inputStream) throws IOException{ ByteArrayOutputStream os = new ByteArrayOutputStream(); int data = inputStream.read(); while (data != -1 && data != LF) { os.write(data); data = inputStream.read(); } debug("..."); debugln("<LF>"); } /** * Liest Datenstream bis <DLE><ETX> und sendet anschliessend das Ack * * @param inputStream * @throws IOException */ private void readToEndAndACK(final String packetNr, final InputStream inputStream) throws IOException{ readToEnd(inputStream); debugln(""); //$NON-NLS-1$ if (packetNr != null) { sendPacketACK(packetNr); } } /** * Verarbeitet Patientendaten */ private void handlePatientRecord(final String packetNr, final InputStream inputStream) throws IOException{ // nächste 2560 Bytes lesen ByteArrayOutputStream baos = new ByteArrayOutputStream(); // <DLE><ETB> suchen int data = inputStream.read(); while (data != -1 && data != ETB) { while (data != -1 && data != DLE) { baos.write(data); data = inputStream.read(); } if (data == DLE) { // <DLE><DLE> wird zweites DLE nicht beachtet baos.write(data); data = inputStream.read(); if (data != DLE) { baos.write(data); } data = inputStream.read(); } } byte[] bytes = baos.toByteArray(); debug(getByteStr(bytes)); debugln("<DLE><ETB>"); readToEnd(inputStream); if (bytes.length < 2560) { sendPacketNAK(packetNr); } else { sendPacketACK(packetNr); sendMessageACK(); listener.gotData(this, bytes); } } private void dataAvailable(final InputStream inputStream) throws IOException{ debug("<--"); //$NON-NLS-1$ int data = inputStream.read(); if (data == DLE) { debug("<DLE>"); //$NON-NLS-1$ data = inputStream.read(); if (data == STX) { debug("<STX>"); //$NON-NLS-1$ String packetNr = ""; //$NON-NLS-1$ for (int i = 0; i < 4; i++) { data = inputStream.read(); packetNr += (char) data; } debug(packetNr); // ACK/ NAK data = inputStream.read(); if (data == NAK) { debug("<NAK>"); data = inputStream.read(); // <DLE> debug(getText(data)); data = inputStream.read(); // <ETB> debug(getText(data)); readToEnd(inputStream); } else if (data == ACK) { debug("<ACK>"); data = inputStream.read(); // <DLE> debug(getText(data)); data = inputStream.read(); // <ETB> debug(getText(data)); readToEnd(inputStream); if (packetNr.equals(awaitPacketNr)) { // Request wurde bestaetigt setState(PAT_REQUEST_ACK); } } else { // Content lesen StringBuffer header = new StringBuffer(); while ((data != -1) && (data != '@')) { header.append((char) data); data = inputStream.read(); } String headerStr = header.toString(); debug(headerStr); debug("@"); //$NON-NLS-1$ if (headerStr.indexOf("0025:record,patient") != -1) { //$NON-NLS-1$ if (getState() == PAT_REQUEST_ACK) { handlePatientRecord(packetNr, inputStream); } else { readToEndAndACK(packetNr, inputStream); } } else if (headerStr.indexOf("0024:record.control") != -1) { //$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); } else if (headerStr.indexOf("cmdack") != -1) {//$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); } else if (headerStr.indexOf("cmderr") != -1) {//$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); // setState(SEND_PAT_REQUEST); } else if (headerStr.indexOf("cmdcmpl") != -1) {//$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); // setState(SEND_PAT_REQUEST); } else if (headerStr.indexOf("debugmsg") != -1) {//$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); } else if (headerStr.indexOf("FFFF:IC") != -1) {//$NON-NLS-1$ readToEndAndACK(packetNr, inputStream); } } } else { // Sollte nicht vorkommen readToEnd(inputStream); } } else { // {Text} <LF> readToLF(inputStream); } // Initialisierung if (getState() == INIT) { last_time_ms = new GregorianCalendar().getTimeInMillis(); setState(WAITING); } // 1 Minute delay if (getState() == WAITING) { long time_ms = new GregorianCalendar().getTimeInMillis(); if (time_ms - last_time_ms > STARTUP_DELAY_IN_MS) { setState(SEND_PAT_REQUEST); last_time_ms = new GregorianCalendar().getTimeInMillis(); } } // Überprüft Status. Nach x Sekunden wird Request nochmals gesendet if (getState() == PAT_REQUEST_SENDED || getState() == PAT_REQUEST_ACK) { // Resend nach 30 sekunden long time_ms = new GregorianCalendar().getTimeInMillis(); if (time_ms - last_time_ms > RESEND_IN_MS) { setState(SEND_PAT_REQUEST); last_time_ms = new GregorianCalendar().getTimeInMillis(); } } if (getState() == SEND_PAT_REQUEST) { awaitPacketNr = sendPatRecordRequest(); setState(PAT_REQUEST_SENDED); } } /** * Handles serial event. */ public void serialEvent(final int state, final InputStream inputStream, final SerialPortEvent e) throws IOException{ switch (e.getEventType()) { case SerialPortEvent.BI: case SerialPortEvent.OE: case SerialPortEvent.FE: case SerialPortEvent.PE: case SerialPortEvent.CD: case SerialPortEvent.CTS: case SerialPortEvent.DSR: case SerialPortEvent.RI: case SerialPortEvent.OUTPUT_BUFFER_EMPTY: break; case SerialPortEvent.DATA_AVAILABLE: dataAvailable(inputStream); break; } } @Override public void breakInterrupt(final int state){ setState(INIT); super.breakInterrupt(state); } @Override public String connect(){ setState(INIT); return super.connect(); } private String getStateText(int state){ switch (state) { case INIT: return "INIT"; case WAITING: return "WAITING"; case SEND_PAT_REQUEST: return "SEND_PAT_REQUEST"; case PAT_REQUEST_SENDED: return "SEND_PAT_REQUEST"; case PAT_REQUEST_ACK: return "SEND_PAT_REQUEST"; default: break; } return "#" + state; } public void setState(int state){ debugln(getStateText(getState()) + " -> " + getStateText(state)); super.setState(state); } }