From c094abad7b14fb16827b13e5671665753be9ecad Mon Sep 17 00:00:00 2001 From: Luke Wood Date: Wed, 9 Jan 2013 10:40:38 +0000 Subject: [PATCH] [#561] [#912] Add Rxtx transport --- example/pom.xml | 5 + .../io/netty/example/rxtx/RxtxClient.java | 61 ++++ .../netty/example/rxtx/RxtxClientHandler.java | 37 ++ pom.xml | 7 + transport-rxtx/pom.xml | 54 +++ .../io/netty/transport/rxtx/RxtxChannel.java | 178 ++++++++++ .../transport/rxtx/RxtxChannelConfig.java | 317 ++++++++++++++++++ .../transport/rxtx/RxtxChannelOptions.java | 47 +++ .../transport/rxtx/RxtxDeviceAddress.java | 45 +++ .../io/netty/transport/rxtx/package-info.java | 20 ++ 10 files changed, 771 insertions(+) create mode 100644 example/src/main/java/io/netty/example/rxtx/RxtxClient.java create mode 100644 example/src/main/java/io/netty/example/rxtx/RxtxClientHandler.java create mode 100644 transport-rxtx/pom.xml create mode 100644 transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannel.java create mode 100644 transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelConfig.java create mode 100644 transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelOptions.java create mode 100644 transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxDeviceAddress.java create mode 100644 transport-rxtx/src/main/java/io/netty/transport/rxtx/package-info.java diff --git a/example/pom.xml b/example/pom.xml index 24c22d7e50..8416c11ad6 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -75,6 +75,11 @@ netty-transport-udt ${project.version} + + ${project.groupId} + netty-transport-rxtx + ${project.version} + diff --git a/example/src/main/java/io/netty/example/rxtx/RxtxClient.java b/example/src/main/java/io/netty/example/rxtx/RxtxClient.java new file mode 100644 index 0000000000..c18525ce2c --- /dev/null +++ b/example/src/main/java/io/netty/example/rxtx/RxtxClient.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.example.rxtx; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.oio.OioEventLoopGroup; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.transport.rxtx.RxtxChannel; +import io.netty.transport.rxtx.RxtxDeviceAddress; + +/** + * Sends one message to a serial device + */ +public final class RxtxClient { + + public static void main(String[] args) throws Exception { + Bootstrap b = new Bootstrap(); + try { + b.group(new OioEventLoopGroup()) + .channel(RxtxChannel.class) + .remoteAddress(new RxtxDeviceAddress("/dev/ttyUSB0")) + .handler(new ChannelInitializer() { + @Override + public void initChannel(RxtxChannel ch) throws Exception { + ch.pipeline().addLast( + new LineBasedFrameDecoder(32768), + new StringEncoder(), + new StringDecoder(), + new RxtxClientHandler() + ); + } + }); + + ChannelFuture f = b.connect().sync(); + + f.channel().closeFuture().sync(); + } finally { + b.shutdown(); + } + } + + private RxtxClient() { + } +} diff --git a/example/src/main/java/io/netty/example/rxtx/RxtxClientHandler.java b/example/src/main/java/io/netty/example/rxtx/RxtxClientHandler.java new file mode 100644 index 0000000000..5b42bd8874 --- /dev/null +++ b/example/src/main/java/io/netty/example/rxtx/RxtxClientHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.example.rxtx; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; + +public class RxtxClientHandler extends ChannelInboundMessageHandlerAdapter { + + @Override + public void channelActive(ChannelHandlerContext ctx) { + ctx.write("AT\n"); + } + + @Override + protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception { + if ("OK".equals(msg)) { + System.out.println("Serial port responded to AT"); + } else { + System.out.println("Serial port responded with not-OK: " + msg); + } + ctx.close(); + } +} diff --git a/pom.xml b/pom.xml index ea635cef21..4651ba63d2 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ codec-http codec-socks transport + transport-rxtx transport-sctp transport-udt handler @@ -106,6 +107,12 @@ 2.4.1 + + org.rxtx + rxtx + 2.1.7 + + javax.servlet servlet-api diff --git a/transport-rxtx/pom.xml b/transport-rxtx/pom.xml new file mode 100644 index 0000000000..71e46bec3f --- /dev/null +++ b/transport-rxtx/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + io.netty + netty-parent + 4.0.0.Beta1-SNAPSHOT + + + netty-transport-rxtx + jar + + Netty/RXTX Transport + + + + ${project.groupId} + netty-buffer + ${project.version} + + + ${project.groupId} + netty-codec + ${project.version} + + + ${project.groupId} + netty-transport + ${project.version} + + + org.rxtx + rxtx + + + + + diff --git a/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannel.java b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannel.java new file mode 100644 index 0000000000..3bde80ec2d --- /dev/null +++ b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannel.java @@ -0,0 +1,178 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.transport.rxtx; + +import static io.netty.transport.rxtx.RxtxChannelOptions.*; + +import io.netty.buffer.BufType; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.socket.oio.AbstractOioByteChannel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketAddress; +import java.net.SocketTimeoutException; +import java.nio.channels.NotYetConnectedException; + +import gnu.io.CommPort; +import gnu.io.CommPortIdentifier; +import gnu.io.SerialPort; + +/** + * A channel to a serial device using the RXTX library. + */ +public class RxtxChannel extends AbstractOioByteChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(BufType.BYTE, true); + + private final ChannelConfig config; + + private RxtxDeviceAddress deviceAddress; + private SerialPort serialPort; + private InputStream in; + private OutputStream out; + + public RxtxChannel() { + super(null, null); + + config = new RxtxChannelConfig(this); + } + + @Override + public ChannelConfig config() { + return config; + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public boolean isActive() { + return in != null && out != null; + } + + @Override + protected int available() { + try { + return in.available(); + } catch (IOException e) { + return 0; + } + } + + @Override + protected int doReadBytes(ByteBuf buf) throws Exception { + try { + return buf.writeBytes(in, buf.writableBytes()); + } catch (SocketTimeoutException e) { + return 0; + } + } + + @Override + protected void doWriteBytes(ByteBuf buf) throws Exception { + if (out == null) { + throw new NotYetConnectedException(); + } + buf.readBytes(out, buf.readableBytes()); + } + + @Override + protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + RxtxDeviceAddress remote = (RxtxDeviceAddress) remoteAddress; + final CommPortIdentifier cpi = + CommPortIdentifier.getPortIdentifier(remote.getDeviceAddress()); + final CommPort commPort = cpi.open(getClass().getName(), 1000); + + deviceAddress = remote; + + serialPort = (SerialPort) commPort; + serialPort.setSerialPortParams( + config().getOption(BAUD_RATE), + config().getOption(DATA_BITS).getValue(), + config().getOption(STOP_BITS).getValue(), + config().getOption(PARITY_BIT).getValue() + ); + serialPort.setDTR(config().getOption(DTR)); + serialPort.setRTS(config().getOption(RTS)); + + out = serialPort.getOutputStream(); + in = serialPort.getInputStream(); + } + + @Override + protected SocketAddress localAddress0() { + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + return deviceAddress; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected void doClose() throws Exception { + IOException ex = null; + + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + ex = e; + } + + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + ex = e; + } + + if (serialPort != null) { + serialPort.removeEventListener(); + serialPort.close(); + } + + in = null; + out = null; + serialPort = null; + + if (ex != null) { + throw ex; + } + } +} diff --git a/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelConfig.java b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelConfig.java new file mode 100644 index 0000000000..6233011531 --- /dev/null +++ b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelConfig.java @@ -0,0 +1,317 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.transport.rxtx; + +import static io.netty.transport.rxtx.RxtxChannelOptions.*; + +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultChannelConfig; + +import java.util.Map; + +import gnu.io.SerialPort; + +/** + * A configuration class for RXTX device connections. + */ +public class RxtxChannelConfig extends DefaultChannelConfig { + + public enum Stopbits { + /** + * 1 stop bit will be sent at the end of every character + */ + STOPBITS_1(SerialPort.STOPBITS_1), + /** + * 2 stop bits will be sent at the end of every character + */ + STOPBITS_2(SerialPort.STOPBITS_2), + /** + * 1.5 stop bits will be sent at the end of every character + */ + STOPBITS_1_5(SerialPort.STOPBITS_1_5); + + private final int value; + + Stopbits(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Stopbits ofValue(int value) { + for (Stopbits stopbit : Stopbits.values()) { + if (stopbit.value == value) { + return stopbit; + } + } + throw new IllegalArgumentException("Unknown value for Stopbits: " + value + '.'); + } + } + + public enum Databits { + /** + * 5 data bits will be used for each character (ie. Baudot code) + */ + DATABITS_5(SerialPort.DATABITS_5), + /** + * 6 data bits will be used for each character + */ + DATABITS_6(SerialPort.DATABITS_6), + /** + * 7 data bits will be used for each character (ie. ASCII) + */ + DATABITS_7(SerialPort.DATABITS_7), + /** + * 8 data bits will be used for each character (ie. binary data) + */ + DATABITS_8(SerialPort.DATABITS_8); + + private final int value; + + Databits(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Databits ofValue(int value) { + for (Databits databit : Databits.values()) { + if (databit.value == value) { + return databit; + } + } + throw new IllegalArgumentException("Unknown value for Databits: " + value + '.'); + } + } + + public enum Paritybit { + /** + * No parity bit will be sent with each data character at all + */ + NONE(SerialPort.PARITY_NONE), + /** + * An odd parity bit will be sent with each data character, ie. will be set + * to 1 if the data character contains an even number of bits set to 1. + */ + ODD(SerialPort.PARITY_ODD), + /** + * An even parity bit will be sent with each data character, ie. will be set + * to 1 if the data character contains an odd number of bits set to 1. + */ + EVEN(SerialPort.PARITY_EVEN), + /** + * A mark parity bit (ie. always 1) will be sent with each data character + */ + MARK(SerialPort.PARITY_MARK), + /** + * A space parity bit (ie. always 0) will be sent with each data character + */ + SPACE(SerialPort.PARITY_SPACE); + + private final int value; + + Paritybit(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Paritybit ofValue(int value) { + for (Paritybit paritybit : Paritybit.values()) { + if (paritybit.value == value) { + return paritybit; + } + } + throw new IllegalArgumentException("Unknown value for paritybit: " + value + '.'); + } + } + + private volatile int baudrate = 115200; + + private volatile boolean dtr; + + private volatile boolean rts; + + private volatile Stopbits stopbits = Stopbits.STOPBITS_1; + + private volatile Databits databits = Databits.DATABITS_8; + + private volatile Paritybit paritybit = Paritybit.NONE; + + public RxtxChannelConfig(RxtxChannel channel) { + super(channel); + } + + @Override + public Map, Object> getOptions() { + return getOptions(super.getOptions(), + BAUD_RATE, DTR, RTS, STOP_BITS, DATA_BITS, PARITY_BIT); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == BAUD_RATE) { + return (T) Integer.valueOf(getBaudrate()); + } + if (option == DTR) { + return (T) Boolean.valueOf(isDtr()); + } + if (option == RTS) { + return (T) Boolean.valueOf(isRts()); + } + if (option == STOP_BITS) { + return (T) getStopbits(); + } + if (option == DATA_BITS) { + return (T) getDatabits(); + } + if (option == PARITY_BIT) { + return (T) getParitybit(); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == BAUD_RATE) { + setBaudrate((Integer) value); + } else if (option == DTR) { + setDtr((Boolean) value); + } else if (option == RTS) { + setRts((Boolean) value); + } else if (option == STOP_BITS) { + setStopbits((Stopbits) value); + } else if (option == DATA_BITS) { + setDatabits((Databits) value); + } else if (option == PARITY_BIT) { + setParitybit((Paritybit) value); + } else { + return super.setOption(option, value); + } + return true; + } + + /** + * Sets the baud rate (ie. bits per second) for communication with the serial device. + * The baud rate will include bits for framing (in the form of stop bits and parity), + * such that the effective data rate will be lower than this value. + * + * @param baudrate The baud rate (in bits per second) + */ + public void setBaudrate(final int baudrate) { + this.baudrate = baudrate; + } + + /** + * Sets the number of stop bits to include at the end of every character to aid the + * serial device in synchronising with the data. + * + * @param stopbits The number of stop bits to use + */ + public void setStopbits(final Stopbits stopbits) { + this.stopbits = stopbits; + } + + /** + * Sets the number of data bits to use to make up each character sent to the serial + * device. + * + * @param databits The number of data bits to use + */ + public void setDatabits(final Databits databits) { + this.databits = databits; + } + + /** + * Sets the type of parity bit to be used when communicating with the serial device. + * + * @param paritybit The type of parity bit to be used + */ + public void setParitybit(final Paritybit paritybit) { + this.paritybit = paritybit; + } + + /** + * @return The configured baud rate, defaulting to 115200 if unset + */ + public int getBaudrate() { + return baudrate; + } + + /** + * @return The configured stop bits, defaulting to {@link Stopbits#STOPBITS_1} if unset + */ + public Stopbits getStopbits() { + return stopbits; + } + + /** + * @return The configured data bits, defaulting to {@link Databits#DATABITS_8} if unset + */ + public Databits getDatabits() { + return databits; + } + + /** + * @return The configured parity bit, defaulting to {@link Paritybit#NONE} if unset + */ + public Paritybit getParitybit() { + return paritybit; + } + + /** + * @return true if the serial device should support the Data Terminal Ready signal + */ + public boolean isDtr() { + return dtr; + } + + /** + * Sets whether the serial device supports the Data Terminal Ready signal, used for + * flow control + * + * @param dtr true if DTR is supported, false otherwise + */ + public void setDtr(final boolean dtr) { + this.dtr = dtr; + } + + /** + * @return true if the serial device should support the Ready to Send signal + */ + public boolean isRts() { + return rts; + } + + /** + * Sets whether the serial device supports the Request To Send signal, used for flow + * control + * + * @param rts true if RTS is supported, false otherwise + */ + public void setRts(final boolean rts) { + this.rts = rts; + } +} diff --git a/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelOptions.java b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelOptions.java new file mode 100644 index 0000000000..a50ab28b30 --- /dev/null +++ b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxChannelOptions.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.transport.rxtx; + +import io.netty.channel.ChannelOption; +import io.netty.transport.rxtx.RxtxChannelConfig.Databits; +import io.netty.transport.rxtx.RxtxChannelConfig.Paritybit; +import io.netty.transport.rxtx.RxtxChannelConfig.Stopbits; + +/** + * Options for configuring a serial port connection + */ +public final class RxtxChannelOptions { + public static final ChannelOption BAUD_RATE = + new ChannelOption("BAUD_RATE"); + + public static final ChannelOption DTR = + new ChannelOption("DTR"); + + public static final ChannelOption RTS = + new ChannelOption("RTS"); + + public static final ChannelOption STOP_BITS = + new ChannelOption("STOP_BITS"); + + public static final ChannelOption DATA_BITS = + new ChannelOption("DATA_BITS"); + + public static final ChannelOption PARITY_BIT = + new ChannelOption("PARITY_BIT"); + + private RxtxChannelOptions() { + } +} diff --git a/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxDeviceAddress.java b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxDeviceAddress.java new file mode 100644 index 0000000000..d1db101436 --- /dev/null +++ b/transport-rxtx/src/main/java/io/netty/transport/rxtx/RxtxDeviceAddress.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.transport.rxtx; + +import java.net.SocketAddress; + +/** + * A {@link SocketAddress} subclass to wrap the serial port address of a RXTX + * device (e.g. COM1, /dev/ttyUSB0). + */ +public class RxtxDeviceAddress extends SocketAddress { + + private static final long serialVersionUID = -2907820090993709523L; + + private final String deviceAddress; + + /** + * Creates a RxtxDeviceAddress representing the address of the serial port. + * + * @param deviceAddress the address of the device (e.g. COM1, /dev/ttyUSB0, ...) + */ + public RxtxDeviceAddress(String deviceAddress) { + this.deviceAddress = deviceAddress; + } + + /** + * @return The serial port address of the device (e.g. COM1, /dev/ttyUSB0, ...) + */ + public String getDeviceAddress() { + return deviceAddress; + } +} diff --git a/transport-rxtx/src/main/java/io/netty/transport/rxtx/package-info.java b/transport-rxtx/src/main/java/io/netty/transport/rxtx/package-info.java new file mode 100644 index 0000000000..98223e759e --- /dev/null +++ b/transport-rxtx/src/main/java/io/netty/transport/rxtx/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * A serial and parallel port communication transport based on RXTX. + */ +package io.netty.transport.rxtx;