aboutsummaryrefslogtreecommitdiff
path: root/src/org
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2012-12-21 22:04:03 +1300
committerAnton Tananaev <anton.tananaev@gmail.com>2012-12-21 22:04:03 +1300
commitfe9f9b034713cb98531d73698a65bd03d18a771c (patch)
treee6c9c976951b018936fa849deeae5fd96afb3bb3 /src/org
parentb10a929a464eb866b47d08619d3f792fb00a92e4 (diff)
downloadtraccar-server-fe9f9b034713cb98531d73698a65bd03d18a771c.tar.gz
traccar-server-fe9f9b034713cb98531d73698a65bd03d18a771c.tar.bz2
traccar-server-fe9f9b034713cb98531d73698a65bd03d18a771c.zip
Added GT06 protocol (fix #100)
Diffstat (limited to 'src/org')
-rw-r--r--src/org/traccar/Server.java48
-rw-r--r--src/org/traccar/helper/Crc.java20
-rw-r--r--src/org/traccar/protocol/Gt02ProtocolDecoder.java4
-rw-r--r--src/org/traccar/protocol/Gt06ProtocolDecoder.java103
-rw-r--r--src/org/traccar/protocol/MeiligaoProtocolDecoder.java4
5 files changed, 123 insertions, 56 deletions
diff --git a/src/org/traccar/Server.java b/src/org/traccar/Server.java
index 3b22e8f2d..d4e4a94cb 100644
--- a/src/org/traccar/Server.java
+++ b/src/org/traccar/Server.java
@@ -28,10 +28,7 @@ import java.util.List;
import java.util.Properties;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
import java.util.logging.LogRecord;
-import java.util.logging.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelPipeline;
@@ -255,6 +252,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new XexunFrameDecoder());
pipeline.addLast("stringDecoder", new StringDecoder());
@@ -280,6 +278,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) ';' };
pipeline.addLast("frameDecoder",
@@ -308,6 +307,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) ')' };
pipeline.addLast("frameDecoder",
@@ -336,6 +336,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) 0x0 };
pipeline.addLast("frameDecoder",
@@ -364,6 +365,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '$' };
pipeline.addLast("frameDecoder",
@@ -392,6 +394,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -420,6 +423,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\n' }; // tracker bug \n\r
pipeline.addLast("frameDecoder",
@@ -447,6 +451,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -474,6 +479,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 0, 2, -2, 2));
pipeline.addLast("objectDecoder", new EnforaProtocolDecoder(getDataManager()));
@@ -498,6 +504,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 4));
pipeline.addLast("objectDecoder", new MeiligaoProtocolDecoder(getDataManager()));
@@ -518,6 +525,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -542,6 +550,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r' };
pipeline.addLast("frameDecoder",
@@ -566,6 +575,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 0));
pipeline.addLast("objectDecoder", new ProgressProtocolDecoder(getDataManager()));
@@ -590,6 +600,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '#' };
pipeline.addLast("frameDecoder",
@@ -617,6 +628,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new Jt600FrameDecoder());
pipeline.addLast("objectDecoder", new Jt600ProtocolDecoder(getDataManager()));
@@ -641,6 +653,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) ';' };
pipeline.addLast("frameDecoder",
@@ -668,6 +681,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '#', (byte) '#' };
pipeline.addLast("frameDecoder",
@@ -695,6 +709,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -723,6 +738,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -752,6 +768,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(4 * 1024, 12, 2, 2, 0));
pipeline.addLast("objectDecoder", new NavisProtocolDecoder(getDataManager()));
@@ -776,6 +793,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
byte delimiter[] = { (byte) '\r', (byte) '\n' };
pipeline.addLast("frameDecoder",
@@ -804,6 +822,7 @@ public class Server {
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("objectDecoder", new SkypatrolProtocolDecoder(getDataManager()));
}
@@ -824,12 +843,12 @@ public class Server {
TrackerServer server = new TrackerServer(new ServerBootstrap());
server.setPort(getProtocolPort(properties, protocol));
server.setAddress(getProtocolInterface(properties, protocol));
- //server.setEndianness(ByteOrder.LITTLE_ENDIAN);
final Integer resetDelay = getProtocolResetDelay(properties, protocol);
server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
- pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 1, 2, 0));
+ pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0));
pipeline.addLast("objectDecoder", new Gt02ProtocolDecoder(getDataManager()));
}
});
@@ -842,6 +861,25 @@ public class Server {
* Init GT06 server
*/
private void initGt06Server(Properties properties) throws SQLException {
+
+ String protocol = "gt06";
+ if (isProtocolEnabled(properties, protocol)) {
+
+ TrackerServer server = new TrackerServer(new ServerBootstrap());
+ server.setPort(getProtocolPort(properties, protocol));
+ server.setAddress(getProtocolInterface(properties, protocol));
+ final Integer resetDelay = getProtocolResetDelay(properties, protocol);
+
+ server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) {
+ @Override
+ protected void addSpecificHandlers(ChannelPipeline pipeline) {
+ pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0));
+ pipeline.addLast("objectDecoder", new Gt06ProtocolDecoder(getDataManager()));
+ }
+ });
+
+ serverList.add(server);
+ }
}
}
diff --git a/src/org/traccar/helper/Crc.java b/src/org/traccar/helper/Crc.java
index 5da774d65..cef0cc482 100644
--- a/src/org/traccar/helper/Crc.java
+++ b/src/org/traccar/helper/Crc.java
@@ -15,6 +15,8 @@
*/
package org.traccar.helper;
+import java.nio.ByteBuffer;
+
/**
* CRC functions
*/
@@ -93,35 +95,35 @@ public class Crc {
private static final int crc16CcittStart = 0xFFFF;
private static final int crc16CcittXorout = 0xFFFF;
- private static int crc16Unreflected(byte[] buf, int crc_in, int[] table) {
+ private static int crc16Unreflected(ByteBuffer buf, int crc_in, int[] table) {
int crc16 = crc_in;
- for (int i = 0; i < buf.length; i++) {
- crc16 = table[((crc16 >> 8) ^ buf[i]) & 0xff] ^ (crc16 << 8);
+ for (int i = 0; i < buf.remaining(); i++) {
+ crc16 = table[((crc16 >> 8) ^ buf.get(i)) & 0xff] ^ (crc16 << 8);
}
return crc16 & 0xFFFF;
}
- private static int crc16Reflected(byte[] buf, int crc_in, int[] table) {
+ private static int crc16Reflected(ByteBuffer buf, int crc_in, int[] table) {
int crc16 = crc_in;
- for (int i = 0; i < buf.length; i++) {
- crc16 = table[(crc16 ^ buf[i]) & 0xff] ^ (crc16 >> 8);
+ for (int i = 0; i < buf.remaining(); i++) {
+ crc16 = table[(crc16 ^ buf.get(i)) & 0xff] ^ (crc16 >> 8);
}
return crc16 & 0xFFFF;
}
- public static int crc16Ccitt(byte[] buf) {
+ public static int crc16Ccitt(ByteBuffer buf) {
return crc16Reflected(buf, crc16CcittStart, crc16CcittTableReverse) ^ crc16CcittXorout;
}
- public static int crc16X25Ccitt(byte[] buf) {
+ public static int crc16X25Ccitt(ByteBuffer buf) {
return crc16Unreflected(buf, crc16CcittStart, crc16CcittTable);
}
- public static int crc16CcittSeed(byte[] buf, int seed) {
+ public static int crc16CcittSeed(ByteBuffer buf, int seed) {
return crc16Reflected(buf, seed, crc16CcittTableReverse) ^ crc16CcittXorout;
}
diff --git a/src/org/traccar/protocol/Gt02ProtocolDecoder.java b/src/org/traccar/protocol/Gt02ProtocolDecoder.java
index f6d6d576b..69d54cb5b 100644
--- a/src/org/traccar/protocol/Gt02ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gt02ProtocolDecoder.java
@@ -17,8 +17,6 @@ package org.traccar.protocol;
import java.util.Calendar;
import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
@@ -116,7 +114,7 @@ public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
// Speed
position.setSpeed((double) buf.readUnsignedByte());
- // Speed
+ // Course
position.setCourse((double) buf.readUnsignedShort());
buf.skipBytes(3); // reserved
diff --git a/src/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/org/traccar/protocol/Gt06ProtocolDecoder.java
index f6d6d576b..1daf6c4ec 100644
--- a/src/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -17,13 +17,12 @@ package org.traccar.protocol;
import java.util.Calendar;
import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.traccar.GenericProtocolDecoder;
+import org.traccar.helper.Crc;
import org.traccar.helper.Log;
import org.traccar.model.DataManager;
import org.traccar.model.Position;
@@ -31,12 +30,14 @@ import org.traccar.model.Position;
/**
* T55 tracker protocol decoder
*/
-public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
+public class Gt06ProtocolDecoder extends GenericProtocolDecoder {
+
+ private Long deviceId;
/**
* Initialize
*/
- public Gt02ProtocolDecoder(DataManager dataManager) {
+ public Gt06ProtocolDecoder(DataManager dataManager) {
super(dataManager);
}
@@ -52,8 +53,24 @@ public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
return imei.toString();
}
- private static final int MSG_HEARTBEAT = 0x1A;
- private static final int MSG_DATA = 0x10;
+ private static final int MSG_LOGIN = 0x01;
+ private static final int MSG_DATA = 0x12;
+ private static final int MSG_HEARTBEAT = 0x13;
+ private static final int MSG_STRING = 0x15;
+ private static final int MSG_ALARM = 0x16;
+
+ private static void sendResponse(Channel channel, int type, int index) {
+ if (channel == null) {
+ ChannelBuffer response = ChannelBuffers.directBuffer(10);
+ response.writeByte(0x78); response.writeByte(0x78); // header
+ response.writeByte(0x05); // size
+ response.writeByte(type);
+ response.writeShort(index);
+ response.writeShort(Crc.crc16Ccitt(response.toByteBuffer(2, 4)));
+ response.writeByte(0x0D); response.writeByte(0x0A); // ending
+ channel.write(response);
+ }
+ }
/**
* Decode message
@@ -68,33 +85,28 @@ public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
buf.skipBytes(2); // header
buf.readByte(); // size
- // Zero for location messages
- buf.readByte(); // voltage
- buf.readByte(); // gsm signal
-
- String imei = readImei(buf);
- long index = buf.readUnsignedShort();
int type = buf.readUnsignedByte();
- if (type == MSG_HEARTBEAT) {
- if (channel != null) {
- byte[] response = {0x54, 0x68, 0x1A, 0x0D, 0x0A};
- channel.write(ChannelBuffers.wrappedBuffer(response));
+ if (type == MSG_LOGIN) {
+ String imei = readImei(buf);
+ try {
+ deviceId = getDataManager().getDeviceByImei(imei).getId();
+ sendResponse(channel, type, buf.readUnsignedShort());
+ } catch(Exception error) {
+ Log.warning("Unknown device - " + imei);
}
}
+ else if (type == MSG_HEARTBEAT) {
+ buf.skipBytes(5);
+ sendResponse(channel, type, buf.readUnsignedShort());
+ }
+
else if (type == MSG_DATA) {
-
// Create new position
Position position = new Position();
- position.setId(index);
-
- // Get device id
- try {
- position.setDeviceId(getDataManager().getDeviceByImei(imei).getId());
- } catch(Exception error) {
- Log.warning("Unknown device - " + imei);
- }
+ position.setDeviceId(deviceId);
+ StringBuilder extendedInfo = new StringBuilder("<protocol>gt06</protocol>");
// Date and time
Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
@@ -107,6 +119,11 @@ public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
time.set(Calendar.SECOND, buf.readUnsignedByte());
position.setTime(time.getTime());
+ // Satellites count
+ extendedInfo.append("<satellites>");
+ extendedInfo.append(buf.readUnsignedByte());
+ extendedInfo.append("</satellites>");
+
// Latitude
double latitude = buf.readUnsignedInt() / (60.0 * 30000.0);
@@ -115,22 +132,36 @@ public class Gt02ProtocolDecoder extends GenericProtocolDecoder {
// Speed
position.setSpeed((double) buf.readUnsignedByte());
-
- // Speed
- position.setCourse((double) buf.readUnsignedShort());
-
- buf.skipBytes(3); // reserved
-
- // Flags
- long flags = buf.readUnsignedInt();
- position.setValid((flags & 0x1) == 0x1);
- if ((flags & 0x2) == 0) latitude = -latitude;
- if ((flags & 0x4) == 0) longitude = -longitude;
+
+ // Course and flags
+ int union = buf.readUnsignedShort();
+ position.setCourse((double) (union & 0x03FF));
+ position.setValid((union & 0x1000) != 0);
+ if ((union & 0x0400) == 0) latitude = -latitude;
+ if ((union & 0x0800) == 0) longitude = -longitude;
position.setLatitude(latitude);
position.setLongitude(longitude);
position.setAltitude(0.0);
+
+ // Cell information
+ extendedInfo.append("<mcc>");
+ extendedInfo.append(buf.readUnsignedShort());
+ extendedInfo.append("</mcc>");
+ extendedInfo.append("<mnc>");
+ extendedInfo.append(buf.readUnsignedByte());
+ extendedInfo.append("</mnc>");
+ extendedInfo.append("<lac>");
+ extendedInfo.append(buf.readUnsignedShort());
+ extendedInfo.append("</lac>");
+ extendedInfo.append("<cell>");
+ extendedInfo.append(buf.readUnsignedShort() << 8 + buf.readUnsignedByte());
+ extendedInfo.append("</cell>");
+
+ // Index
+ position.setId((long) buf.readUnsignedShort());
+ position.setExtendedInfo(extendedInfo.toString());
return position;
}
diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
index f1332ddf9..3d8fbef75 100644
--- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
+++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
@@ -106,9 +106,7 @@ public class MeiligaoProtocolDecoder extends GenericProtocolDecoder {
sendBuf.writeBytes(array);
sendBuf.writeShort(0x4000);
sendBuf.writeByte(0x01);
- array = new byte[sendBuf.readableBytes()];
- sendBuf.getBytes(0, array);
- sendBuf.writeShort(Crc.crc16X25Ccitt(array));
+ sendBuf.writeShort(Crc.crc16X25Ccitt(sendBuf.toByteBuffer()));
sendBuf.writeByte('\r');
sendBuf.writeByte('\n');
if (channel != null) {