org.openmuc.jdlms.transportlayer.client.Iec21Layer Maven / Gradle / Ivy
Show all versions of jdlms Show documentation
/*
* Copyright 2012-15 Fraunhofer ISE
*
* This file is part of jDLMS.
* For more information visit http://www.openmuc.org
*
* jDLMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* jDLMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with jDLMS. If not, see .
*
*/
package org.openmuc.jdlms.transportlayer.client;
import static org.openmuc.jdlms.JDlmsException.Fault.SYSTEM;
import static org.openmuc.jdlms.JDlmsException.Fault.USER;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import org.openmuc.jdlms.FatalJDlmsException;
import org.openmuc.jdlms.HexConverter;
import org.openmuc.jdlms.JDlmsException.ExceptionId;
import org.openmuc.jdlms.settings.client.SerialSettings;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.CommPortTimeoutException;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import gnu.io.serialport.DataBits;
import gnu.io.serialport.Parity;
import gnu.io.serialport.StopBits;
/**
* This class represents a connection on the physical layer according to IEC 62056-21 in protocol mode E.
*/
public class Iec21Layer implements TransportLayer {
private static final String APP_NAME = "org.openmuc.jdlms.SERIAL";
private static final int CR = 0x0D;
private static final int LF = 0x0A;
/**
* {@code / ? }
*/
private static final byte[] REQUEST_MSG_1 = new byte[] { 0x2F, 0x3F };
/**
* {@code ! CR LF}
*/
private static final byte[] REQUEST_MSG_3 = new byte[] { 0x21, CR, LF };
/**
* {@code ACK 2 0 2 CR LF}
*
* ACK [HDLC protocol procedure] [initial bd 300] [binary mode]
*
*/
private static final byte[] ACKNOWLEDGE = new byte[] { 0x06, 0x32, 0x30, 0x32, CR, LF };
private SerialPort serialPort;
private final SerialSettings settings;
private boolean closed;
public Iec21Layer(SerialSettings settings) {
this.settings = settings;
this.closed = true;
}
@Override
public void setTimeout(int timeout) throws IOException {
try {
this.serialPort.setCommPortTimeout(timeout);
} catch (UnsupportedCommOperationException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
"RXTX is not compatible to your OS.", e);
}
}
@Override
public InputStream getInputStream() throws IOException {
return this.serialPort.inputStream();
}
@Override
public OutputStream getOutpuStream() throws IOException {
return this.serialPort.outputStream();
}
@Override
public boolean isClosed() {
return this.closed;
}
@Override
public void open() throws IOException {
if (isClosed()) {
try {
openPhysicalConnection();
if (settings.iec21Handshake() == DataFlowControl.ENABLED) {
connectWithHandshake();
}
else {
connectWithoutHandshake();
}
} catch (IOException e) {
if (serialPort != null) {
serialPort.close();
}
throw e;
}
this.closed = false;
}
}
private void connectWithHandshake() throws IOException {
try {
serialPort.setCommPortTimeout(2000);
} catch (UnsupportedCommOperationException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
"RXTX is not compatible to your OS.", e);
}
byte[] iec21AddressBytes = settings.iec21Address().trim().getBytes();
byte[] requestMsg = ByteBuffer.allocate(REQUEST_MSG_1.length + iec21AddressBytes.length + REQUEST_MSG_3.length)
.put(REQUEST_MSG_1)
.put(iec21AddressBytes)
.put(REQUEST_MSG_3)
.array();
write(requestMsg);
char baudRateSetting;
try {
baudRateSetting = listenForIdentificationMessage();
} catch (CommPortTimeoutException e) {
throw new FatalJDlmsException(ExceptionId.IEC_21_CONNECTION_ESTABLISH_ERROR, USER,
"Timed out, while waiting for the HDLC identification message.", e);
} catch (FatalJDlmsException e) {
throw e;
} catch (IOException e) {
throw new FatalJDlmsException(ExceptionId.IEC_21_CONNECTION_ESTABLISH_ERROR, SYSTEM, MessageFormat
.format("{0} Sended requestmessage: {1}", e.getMessage(), HexConverter.toHexString(requestMsg)), e);
}
int baudRate = baudRateFor(baudRateSetting);
ACKNOWLEDGE[2] = (byte) baudRateSetting;
write(ACKNOWLEDGE);
// Sleep for about 250 milliseconds to make sure, that the
// acknowledge message has been completely transmitted prior
// to changing the baud rate
try {
Thread.sleep(settings.baudrateChangeDelay());
} catch (InterruptedException e) {
}
// change mode to Z baud, 7,1,E
try {
serialPort.setSerialPortParams(baudRate, DataBits.DATABITS_7, StopBits.STOPBITS_1, Parity.EVEN);
} catch (UnsupportedCommOperationException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
MessageFormat.format("Serial Port does not support {0} bd 7E1", baudRate), e);
}
listenForAck();
// change mode to Z baud, 8,1,N
try {
serialPort.setSerialPortParams(baudRate, DataBits.DATABITS_8, StopBits.STOPBITS_1, Parity.NONE);
} catch (UnsupportedCommOperationException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
MessageFormat.format("Serial Port does not support {0}bd 8N1", baudRate));
}
}
private void connectWithoutHandshake() throws IOException {
int maxBaudrate = settings.baudrate();
try {
serialPort.setSerialPortParams(maxBaudrate, DataBits.DATABITS_8, StopBits.STOPBITS_1, Parity.NONE);
} catch (UnsupportedCommOperationException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
MessageFormat.format("Serial Port does not support {0}bd 8N1", maxBaudrate), e);
}
}
public synchronized void send(byte[] data) throws IOException {
if (isClosed()) {
throw new FatalJDlmsException(ExceptionId.CONNECTION_CLOSED, USER,
"Cannot send data. DLMS Client not connected. Open the connection to communicate.");
}
write(data);
}
private void write(byte[] data) throws IOException {
getOutpuStream().write(data);
getOutpuStream().flush();
}
@Override
public void close() {
serialPort.close();
closed = true;
}
private int baudRateFor(char baudCharacter) throws IOException {
// Encoded baud rate (see IEC 62056-21 6.3.14 13c).
switch (baudCharacter) {
case '0':
return 300;
case '1':
return 600;
case '2':
return 1200;
case '3':
return 2400;
case '4':
return 4800;
case '5':
return 9600;
case '6':
return 19200;
default:
throw new FatalJDlmsException(ExceptionId.IEC_21_CONNECTION_ESTABLISH_ERROR, SYSTEM,
String.format(
"Syntax error in identification message received: unknown baud rate received. Baud character was 0x%02X. or char '%s'",
(byte) baudCharacter, String.valueOf(baudCharacter)));
}
}
private void openPhysicalConnection() throws FatalJDlmsException {
if (serialPort == null) {
try {
serialPort = acquireSerialPort(settings.serialPortName());
} catch (FatalJDlmsException e) {
if (serialPort != null) {
serialPort.close();
}
throw e;
}
}
}
private void listenForAck() throws IOException {
byte[] ackMsg = new byte[ACKNOWLEDGE.length];
try {
int ackLength = getInputStream().read(ackMsg);
if (ackLength != ackMsg.length) {
throw new FatalJDlmsException(ExceptionId.IEC_21_UNKNOWN_ACK_MSG, SYSTEM,
"Either the remote meter implements a newer protocol, or you may clear your serial port and try again.");
}
} catch (CommPortTimeoutException e) {
throw new FatalJDlmsException(ExceptionId.IEC_21_WRONG_BAUD_RATE_CHANGE_DELAY, USER,
"The baud rate change delay was wrong. Try an onther one.");
}
}
private SerialPort acquireSerialPort(String serialPortName) throws FatalJDlmsException {
CommPortIdentifier portIdentifier;
try {
portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName);
} catch (NoSuchPortException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_NO_SUCH_PORT, USER, "The specified port does not exist.",
e);
}
CommPort commPort;
try {
commPort = portIdentifier.open(APP_NAME, 2000);
} catch (PortInUseException e) {
throw new FatalJDlmsException(ExceptionId.JRXTX_PORT_IN_USE, USER, "The specified port is already in use.",
e);
}
if (!(commPort instanceof SerialPort)) {
// may never be the case
commPort.close();
throw new FatalJDlmsException(ExceptionId.JRXTX_PORT_NOT_SERIAL, USER,
"The specified CommPort is not a serial port");
}
try {
SerialPort serialPort = (SerialPort) commPort;
serialPort.setSerialPortParams(300, DataBits.DATABITS_7, StopBits.STOPBITS_1, Parity.EVEN);
return serialPort;
} catch (UnsupportedCommOperationException e) {
if (commPort != null) {
commPort.close();
}
throw new FatalJDlmsException(ExceptionId.JRXTX_INCOMPATIBLE_TO_OS, SYSTEM,
"Unable to set the baud rate or other serial port parameters.", e);
}
}
private char listenForIdentificationMessage() throws IOException {
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {
int b;
while ((b = getInputStream().read()) != CR) {
byteStream.write(b);
}
// read CR LF
byteStream.write(b);
b = getInputStream().read();
byteStream.write(b);
byte[] response = byteStream.toByteArray();
return (char) response[4];
}
}
public enum DataFlowControl {
ENABLED,
DISABLED
}
}