/* * Copyright 2018 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.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; public class WristbandProtocolDecoder extends BaseProtocolDecoder { public WristbandProtocolDecoder(Protocol protocol) { super(protocol); } private void sendResponse( Channel channel, String imei, String version, int type, String data) { if (channel != null) { String sentence = String.format("YX%s|%s|0|{F%02d#%s}\r\n", imei, version, type, data); ByteBuf response = Unpooled.buffer(); if (type != 91) { response.writeBytes(new byte[]{0x00, 0x01, 0x02}); response.writeShort(sentence.length()); } response.writeCharSequence(sentence, StandardCharsets.US_ASCII); if (type != 91) { response.writeBytes(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFC}); } channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } } private static final Pattern PATTERN = new PatternBuilder() .expression("..") // header .number("(d+)|") // imei .number("([vV]d+.d+)|") // version .number("d+|") // model .text("{") .number("F(d+)") // function .groupBegin() .text("#") .expression("(.*)") // data .groupEnd("?") .text("}") .text("\r\n") .compile(); private Position decodePosition(DeviceSession deviceSession, String sentence) throws ParseException { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); String[] values = sentence.split(","); position.setValid(true); position.setLongitude(Double.parseDouble(values[0])); position.setLatitude(Double.parseDouble(values[1])); position.setTime(new SimpleDateFormat("yyyyMMddHHmmss").parse(values[2])); position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[3]))); return position; } private List decodeMessage( Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException { Parser parser = new Parser(PATTERN, sentence); if (!parser.matches()) { return null; } String imei = parser.next(); DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); if (deviceSession == null) { return null; } String version = parser.next(); int type = parser.nextInt(); List positions = new LinkedList<>(); switch (type) { case 90: sendResponse(channel, imei, version, type, getServer(channel, ',')); break; case 91: String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); sendResponse(channel, imei, version, type, time + "|" + getServer(channel, ',')); break; case 1: sendResponse(channel, imei, version, type, "0"); break; case 2: for (String fragment : parser.next().split("\\|")) { positions.add(decodePosition(deviceSession, fragment)); } break; default: break; } return positions.isEmpty() ? null : positions; } @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; buf.skipBytes(3); // header buf.readUnsignedShort(); // length String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 3, StandardCharsets.US_ASCII); buf.skipBytes(3); // footer return decodeMessage(channel, remoteAddress, sentence); } }