From 51920cae6438f8888090f177761a82afff33067f Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 12 May 2017 19:04:15 +1200 Subject: Implement DMT HTTP JSON protocol --- setup/default.xml | 1 + src/org/traccar/protocol/DmtHttpProtocol.java | 45 +++++++ .../traccar/protocol/DmtHttpProtocolDecoder.java | 145 +++++++++++++++++++++ .../protocol/DmtHttpProtocolDecoderTest.java | 19 +++ 4 files changed, 210 insertions(+) create mode 100644 src/org/traccar/protocol/DmtHttpProtocol.java create mode 100644 src/org/traccar/protocol/DmtHttpProtocolDecoder.java create mode 100644 test/org/traccar/protocol/DmtHttpProtocolDecoderTest.java diff --git a/setup/default.xml b/setup/default.xml index e80c516dd..eed2584bd 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -509,5 +509,6 @@ 5136 5137 5138 + 5139 diff --git a/src/org/traccar/protocol/DmtHttpProtocol.java b/src/org/traccar/protocol/DmtHttpProtocol.java new file mode 100644 index 000000000..da8f43bbd --- /dev/null +++ b/src/org/traccar/protocol/DmtHttpProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed 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 org.traccar.protocol; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.codec.http.HttpRequestDecoder; +import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.util.List; + +public class DmtHttpProtocol extends BaseProtocol { + + public DmtHttpProtocol() { + super("dmthttp"); + } + + @Override + public void initTrackerServers(List serverList) { + serverList.add(new TrackerServer(new ServerBootstrap(), getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("httpEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpDecoder", new HttpRequestDecoder()); + pipeline.addLast("objectDecoder", new DmtHttpProtocolDecoder(DmtHttpProtocol.this)); + } + }); + } + +} diff --git a/src/org/traccar/protocol/DmtHttpProtocolDecoder.java b/src/org/traccar/protocol/DmtHttpProtocolDecoder.java new file mode 100644 index 000000000..4592bdd55 --- /dev/null +++ b/src/org/traccar/protocol/DmtHttpProtocolDecoder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed 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 org.traccar.protocol; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.codec.http.DefaultHttpResponse; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.jboss.netty.handler.codec.http.HttpVersion; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; + +public class DmtHttpProtocolDecoder extends BaseProtocolDecoder { + + public DmtHttpProtocolDecoder(DmtHttpProtocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, HttpResponseStatus status) { + if (channel != null) { + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); + response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, 0); + channel.write(response); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + HttpRequest request = (HttpRequest) msg; + JsonObject root = Json.createReader( + new StringReader(request.getContent().toString(StandardCharsets.US_ASCII))).readObject(); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, root.getString("IMEI")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + List positions = new LinkedList<>(); + + JsonArray records = root.getJsonArray("Records"); + + for (int i = 0; i < records.size(); i++) { + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + JsonObject record = records.getJsonObject(i); + + position.set(Position.KEY_INDEX, record.getInt("SeqNo")); + position.set(Position.KEY_EVENT, record.getInt("Reason")); + + position.setDeviceTime(dateFormat.parse(record.getString("DateUTC"))); + + JsonArray fields = record.getJsonArray("Fields"); + + for (int j = 0; j < fields.size(); j++) { + JsonObject field = fields.getJsonObject(j); + switch (field.getInt("FType")) { + case 0: + position.setFixTime(dateFormat.parse(field.getString("GpsUTC"))); + position.setLatitude(field.getJsonNumber("Lat").doubleValue()); + position.setLatitude(field.getJsonNumber("Long").doubleValue()); + position.setAltitude(field.getInt("Alt")); + position.setSpeed(UnitsConverter.knotsFromCps(field.getInt("Spd"))); + position.setCourse(field.getInt("Head")); + position.setAccuracy(field.getInt("PosAcc")); + position.setValid(field.getInt("GpsStat") > 0); + break; + case 2: + int input = field.getInt("DIn"); + int output = field.getInt("DOut"); + + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + + position.set(Position.KEY_INPUT, input); + position.set(Position.KEY_OUTPUT, output); + position.set(Position.KEY_STATUS, field.getInt("DevStat")); + break; + case 6: + JsonObject adc = field.getJsonObject("AnalogueData"); + if (adc.containsKey("1")) { + position.set(Position.KEY_BATTERY, adc.getInt("1") * 0.001); + } + if (adc.containsKey("2")) { + position.set(Position.KEY_POWER, adc.getInt("2") * 0.01); + } + if (adc.containsKey("3")) { + position.set(Position.KEY_DEVICE_TEMP, adc.getInt("3") * 0.01); + } + if (adc.containsKey("4")) { + position.set(Position.KEY_RSSI, adc.getInt("4")); + } + if (adc.containsKey("5")) { + position.set("solarPower", adc.getInt("5") * 0.001); + } + break; + default: + break; + } + } + + positions.add(position); + } + + sendResponse(channel, HttpResponseStatus.OK); + return positions; + } + +} diff --git a/test/org/traccar/protocol/DmtHttpProtocolDecoderTest.java b/test/org/traccar/protocol/DmtHttpProtocolDecoderTest.java new file mode 100644 index 000000000..e7fd086a7 --- /dev/null +++ b/test/org/traccar/protocol/DmtHttpProtocolDecoderTest.java @@ -0,0 +1,19 @@ +package org.traccar.protocol; + +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class DmtHttpProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + DmtHttpProtocolDecoder decoder = new DmtHttpProtocolDecoder(new DmtHttpProtocol()); + + verifyPositions(decoder, request(HttpMethod.POST, "/", + buffer("{\"SerNo\":131693,\"IMEI\":\"356692063643328\",\"ICCID\":\"8944538523010771676\",\"ProdId\":33,\"FW\":\"33.4.1.27\",\"Records\":[{\"SeqNo\":125,\"Reason\":11,\"DateUTC\":\"2017-05-11 05:58:44\",\"Fields\":[{\"GpsUTC\":\"2017-05-08 18:04:57\",\"Lat\":43.7370138,\"Long\":-79.3462607,\"Alt\":197,\"Spd\":0,\"SpdAcc\":13,\"Head\":66,\"PDOP\":18,\"PosAcc\":37,\"GpsStat\":7,\"FType\":0},{\"DIn\":2,\"DOut\":0,\"DevStat\":2,\"FType\":2},{\"AnalogueData\":{\"1\":14641,\"3\":2484,\"4\":26,\"5\":10868},\"FType\":6},{\"AnalogueData\":{\"11\":34,\"12\":0,\"13\":309,\"14\":9921,\"15\":3},\"FType\":7}]},{\"SeqNo\":128,\"Reason\":11,\"DateUTC\":\"2017-05-11 17:59:45\",\"Fields\":[{\"GpsUTC\":\"2017-05-08 18:04:57\",\"Lat\":43.7370138,\"Long\":-79.3462607,\"Alt\":197,\"Spd\":0,\"SpdAcc\":13,\"Head\":66,\"PDOP\":18,\"PosAcc\":37,\"GpsStat\":7,\"FType\":0},{\"DIn\":2,\"DOut\":0,\"DevStat\":2,\"FType\":2},{\"AnalogueData\":{\"1\":14607,\"3\":2752,\"4\":26,\"5\":11062},\"FType\":6},{\"AnalogueData\":{\"11\":34,\"12\":1,\"13\":325,\"14\":10881,\"15\":3},\"FType\":7}]},{\"SeqNo\":130,\"Reason\":9,\"DateUTC\":\"2017-05-11 19:30:03\",\"Fields\":[{\"GpsUTC\":\"2017-05-08 18:04:57\",\"Lat\":43.7370138,\"Long\":-79.3462607,\"Alt\":197,\"Spd\":0,\"SpdAcc\":13,\"Head\":66,\"PDOP\":18,\"PosAcc\":37,\"GpsStat\":3,\"FType\":0},{\"DIn\":6,\"DOut\":0,\"DevStat\":2,\"FType\":2},{\"AnalogueData\":{\"1\":14599,\"3\":2731,\"4\":27,\"5\":10965},\"FType\":6},{\"AnalogueData\":{\"11\":34,\"12\":2,\"13\":329,\"14\":11121,\"15\":3},\"FType\":7}]},{\"SeqNo\":131,\"Reason\":11,\"DateUTC\":\"2017-05-11 19:32:03\",\"Fields\":[{\"GpsUTC\":\"2017-05-08 18:04:57\",\"Lat\":43.7370138,\"Long\":-79.3462607,\"Alt\":197,\"Spd\":0,\"SpdAcc\":13,\"Head\":66,\"PDOP\":18,\"PosAcc\":37,\"GpsStat\":7,\"FType\":0},{\"DIn\":6,\"DOut\":0,\"DevStat\":2,\"FType\":2},{\"AnalogueData\":{\"1\":14403,\"3\":2783,\"4\":27,\"5\":10965},\"FType\":6},{\"AnalogueData\":{\"11\":34,\"12\":2,\"13\":330,\"14\":11181,\"15\":3},\"FType\":7}]},{\"SeqNo\":133,\"Reason\":11,\"DateUTC\":\"2017-05-11 19:36:15\",\"Fields\":[{\"GpsUTC\":\"2017-05-08 18:04:57\",\"Lat\":43.7370138,\"Long\":-79.3462607,\"Alt\":197,\"Spd\":0,\"SpdAcc\":13,\"Head\":66,\"PDOP\":18,\"PosAcc\":37,\"GpsStat\":7,\"FType\":0},{\"DIn\":6,\"DOut\":0,\"DevStat\":2,\"FType\":2},{\"AnalogueData\":{\"1\":14319,\"3\":2898,\"4\":23,\"5\":10965},\"FType\":6},{\"AnalogueData\":{\"11\":34,\"12\":3,\"13\":331,\"14\":11241,\"15\":3},\"FType\":7}]}]}"))); + + } + +} -- cgit v1.2.3