From 3860e965c9feede1ac876261a47370b44181b5d7 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 4 Nov 2023 07:51:14 -0700 Subject: Support Dragino gateway protocol --- setup/default.xml | 1 + .../java/org/traccar/protocol/DraginoProtocol.java | 42 ++++++++++++ .../traccar/protocol/DraginoProtocolDecoder.java | 78 ++++++++++++++++++++++ .../protocol/DraginoProtocolDecoderTest.java | 20 ++++++ 4 files changed, 141 insertions(+) create mode 100644 src/main/java/org/traccar/protocol/DraginoProtocol.java create mode 100644 src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java create mode 100644 src/test/java/org/traccar/protocol/DraginoProtocolDecoderTest.java diff --git a/setup/default.xml b/setup/default.xml index 5e28ea1a9..48fd8c993 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -294,5 +294,6 @@ 5250 5251 5252 + 5253 diff --git a/src/main/java/org/traccar/protocol/DraginoProtocol.java b/src/main/java/org/traccar/protocol/DraginoProtocol.java new file mode 100644 index 000000000..d33efe2ad --- /dev/null +++ b/src/main/java/org/traccar/protocol/DraginoProtocol.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 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 io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import jakarta.inject.Inject; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +public class DraginoProtocol extends BaseProtocol { + + @Inject + public DraginoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new DraginoProtocolDecoder(DraginoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java b/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java new file mode 100644 index 000000000..5f576d723 --- /dev/null +++ b/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 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 io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.helper.DateUtil; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class DraginoProtocolDecoder extends BaseHttpProtocolDecoder { + + public DraginoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String content = request.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + String deviceId = json.getJsonObject("end_device_ids").getString("device_id"); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + JsonObject message = json.getJsonObject("uplink_message"); + JsonObject decoded = message.getJsonObject("decoded_payload"); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(DateUtil.parseDate(message.getString("received_at"))); + + position.setValid(true); + position.setLatitude(decoded.getJsonNumber("Latitude").doubleValue()); + position.setLongitude(decoded.getJsonNumber("Longitude").doubleValue()); + + position.set("humidity", decoded.getJsonNumber("Hum").doubleValue()); + position.set(Position.KEY_BATTERY, decoded.getJsonNumber("BatV").doubleValue()); + position.set(Position.PREFIX_TEMP + 1, decoded.getJsonNumber("Tem").doubleValue()); + + if (Boolean.parseBoolean(decoded.getString("ALARM_status"))) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + sendResponse(channel, HttpResponseStatus.OK); + return position; + } + +} diff --git a/src/test/java/org/traccar/protocol/DraginoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/DraginoProtocolDecoderTest.java new file mode 100644 index 000000000..26de75141 --- /dev/null +++ b/src/test/java/org/traccar/protocol/DraginoProtocolDecoderTest.java @@ -0,0 +1,20 @@ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpMethod; +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; +import org.traccar.model.Position; + +public class DraginoProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = inject(new DraginoProtocolDecoder(null)); + + verifyPosition(decoder, request(HttpMethod.POST, "/", + buffer("{\"end_device_ids\":{\"device_id\":\"eui-a840412b81874509\",\"application_ids\":{\"application_id\":\"teast12344321\"},\"dev_eui\":\"A840412B81874509\",\"join_eui\":\"A840410000000102\",\"dev_addr\":\"260BB59A\"},\"correlation_ids\":[\"gs:uplink:01HDTGK4QWSRX0AMP8E6R1R809\"],\"received_at\":\"2023-10-28T06:47:23.853335601Z\",\"uplink_message\":{\"session_key_id\":\"AYt1CUoFjSPmZ+/Hf8bbng==\",\"f_port\":2,\"f_cnt\":1,\"frm_payload\":\"AAAAAAAAAAAPoiACpwEQ\",\"decoded_payload\":{\"ALARM_status\":\"FALSE\",\"BatV\":4.002,\"Hum\":67.9,\"LON\":\"ON\",\"Latitude\":0,\"Location\":0,\"Longitude\":0,\"MD\":0,\"Tem\":27.2},\"rx_metadata\":[{\"gateway_ids\":{\"gateway_id\":\"eui-a840411b7c4c4150\",\"eui\":\"A840411B7C4C4150\"},\"time\":\"2023-10-28T06:47:23.522309Z\",\"timestamp\":2618357987,\"rssi\":-101,\"channel_rssi\":-101,\"snr\":8.5,\"uplink_token\":\"CiIKIAoUZXVpLWE4NDA0MTFiN2M0YzQxNTASCKhAQRt8TEFQEOPxw+AJGgwI+9zyqQYQ+LGJswIguO2wkpqnNg==\",\"received_at\":\"2023-10-28T06:47:23.643979512Z\"},{\"gateway_ids\":{\"gateway_id\":\"test123123\",\"eui\":\"A840411D178C4150\"},\"time\":\"2023-10-28T06:47:23.522842Z\",\"timestamp\":3064971227,\"rssi\":-78,\"channel_rssi\":-78,\"snr\":8.8,\"uplink_token\":\"ChgKFgoKdGVzdDEyMzEyMxIIqEBBHReMQVAQ2/++tQsaDAj73PKpBhDyj+e1AiD43pX0ma44\",\"received_at\":\"2023-10-28T06:47:23.649709554Z\"},{\"gateway_ids\":{\"gateway_id\":\"eui-b8ea26fdfe2d5d28\",\"eui\":\"B8EA26FDFE2D5D28\"},\"time\":\"2023-10-28T06:47:23.536703Z\",\"timestamp\":2967400725,\"rssi\":-38,\"channel_rssi\":-38,\"snr\":14.2,\"frequency_offset\":\"1061\",\"uplink_token\":\"CiIKIAoUZXVpLWI4ZWEyNmZkZmUyZDVkMjgSCLjqJv3+LV0oEJXi+4YLGgwI+9zyqQYQxqentwIgiPT2tq60NQ==\",\"received_at\":\"2023-10-28T06:47:23.532394512Z\"},{\"gateway_ids\":{\"gateway_id\":\"eu868-1\",\"eui\":\"A840411B7E5E1868\"},\"time\":\"2023-10-28T06:47:23.526195Z\",\"timestamp\":3486064627,\"rssi\":-35,\"channel_rssi\":-35,\"snr\":9,\"uplink_token\":\"ChUKEwoHZXU4NjgtMRIIqEBBG35eGGgQ87+k/gwaDAj73PKpBhDZzuW4AiC4mpPNusM1\",\"received_at\":\"2023-10-28T06:47:23.537018603Z\"}],\"settings\":{\"data_rate\":{\"lora\":{\"bandwidth\":125000,\"spreading_factor\":7,\"coding_rate\":\"4/5\"}},\"frequency\":\"868100000\",\"timestamp\":2618357987,\"time\":\"2023-10-28T06:47:23.522309Z\"},\"received_at\":\"2023-10-28T06:47:23.644805734Z\",\"consumed_airtime\":\"0.066816s\",\"network_ids\":{\"net_id\":\"000013\",\"ns_id\":\"EC656E0000000181\",\"tenant_id\":\"ttn\",\"cluster_id\":\"eu1\",\"cluster_address\":\"eu1.cloud.thethings.network\"}}}"))); + + } + +} -- cgit v1.2.3