@Override
  protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {

    // Parse message
    Matcher parser = pattern.matcher((String) msg);
    if (!parser.matches()) {
      return null;
    }

    // Create new position
    Position position = new Position();
    ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("wondex");
    int index = 1;

    // Device identifier
    String id = parser.group(index++);
    try {
      position.setDeviceId(getDataManager().getDeviceByImei(id).getId());
    } catch (Exception error) {
      Log.warning("Unknown device - " + id);
      return null;
    }

    // Time
    Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    time.clear();
    time.set(Calendar.YEAR, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MONTH, Integer.valueOf(parser.group(index++)) - 1);
    time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.HOUR_OF_DAY, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MINUTE, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.SECOND, Integer.valueOf(parser.group(index++)));
    position.setTime(time.getTime());

    // Position data
    position.setLongitude(Double.valueOf(parser.group(index++)));
    position.setLatitude(Double.valueOf(parser.group(index++)));
    position.setSpeed(Double.valueOf(parser.group(index++)) * 0.539957);
    position.setCourse(Double.valueOf(parser.group(index++)));
    position.setAltitude(Double.valueOf(parser.group(index++)));

    // Satellites
    int satellites = Integer.valueOf(parser.group(index++));
    position.setValid(satellites >= 3);
    extendedInfo.set("satellites", satellites);

    // Event
    extendedInfo.set("event", parser.group(index++));

    // Battery
    extendedInfo.set("battery", parser.group(index++));

    // Milage
    extendedInfo.set("milage", parser.group(index++));

    // Input
    extendedInfo.set("input", parser.group(index++));

    // ADC
    extendedInfo.set("adc1", parser.group(index++));
    extendedInfo.set("adc2", parser.group(index++));

    // Output
    extendedInfo.set("output", parser.group(index++));

    position.setExtendedInfo(extendedInfo.toString());
    return position;
  }
  @Override
  protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {

    String sentence = (String) msg;

    // Heartbeat
    if (sentence.startsWith("$ECHK") && channel != null) {
      channel.write(sentence + "\r\n");
      return null;
    }

    // Parse message
    Matcher parser = pattern.matcher(sentence);
    if (!parser.matches()) {
      return null;
    }

    // Create new position
    Position position = new Position();
    ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("laipac");
    Integer index = 1;

    // Identification
    String id = parser.group(index++);
    try {
      position.setDeviceId(getDataManager().getDeviceByImei(id).getId());
    } catch (Exception error) {
      Log.warning("Unknown device - " + id);
    }

    // Time
    Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    time.clear();
    time.set(Calendar.HOUR_OF_DAY, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MINUTE, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.SECOND, Integer.valueOf(parser.group(index++)));

    // Validity
    String status = parser.group(index++);
    position.setValid(status.compareToIgnoreCase("A") == 0);

    // Latitude
    Double latitude = Double.valueOf(parser.group(index++));
    latitude += Double.valueOf(parser.group(index++)) / 60;
    if (parser.group(index++).compareTo("S") == 0) latitude = -latitude;
    position.setLatitude(latitude);

    // Longitude
    Double longitude = Double.valueOf(parser.group(index++));
    longitude += Double.valueOf(parser.group(index++)) / 60;
    if (parser.group(index++).compareTo("W") == 0) longitude = -longitude;
    position.setLongitude(longitude);

    // Speed
    position.setSpeed(Double.valueOf(parser.group(index++)));

    // Course
    position.setCourse(Double.valueOf(parser.group(index++)));

    // Date
    time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MONTH, Integer.valueOf(parser.group(index++)) - 1);
    time.set(Calendar.YEAR, 2000 + Integer.valueOf(parser.group(index++)));
    position.setTime(time.getTime());

    // Altitude
    position.setAltitude(0.0);

    // Response
    String type = parser.group(index++);
    String checksum = parser.group(index++);
    String response = null;

    if (type.equals("0") && Character.isLowerCase(status.charAt(0))) {
      response = "$EAVACK,0," + checksum;
      response += Crc.nmeaChecksum(response);
    } else if (type.equals("S") || type.equals("T")) {
      response = "$AVCFG,00000000,t*21";
    } else if (type.equals("3")) {
      response = "$AVCFG,00000000,d*31";
    } else if (type.equals("X") || type.equals("4")) {
      response = "$AVCFG,00000000,x*2D";
    }

    if (response != null && channel != null) {
      channel.write(response + "\r\n");
    }

    position.setExtendedInfo(extendedInfo.toString());
    return position;
  }
  @Override
  protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {

    // Parse message
    String sentence = (String) msg;
    Matcher parser = pattern.matcher(sentence);
    if (!parser.matches()) {
      return null;
    }

    // Create new position
    Position position = new Position();
    ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("xexun2");

    Integer index = 1;

    // Serial
    extendedInfo.set("serial", parser.group(index++));

    // Number
    extendedInfo.set("number", parser.group(index++));

    // Time
    Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    time.clear();
    time.set(Calendar.HOUR, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MINUTE, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.SECOND, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MILLISECOND, Integer.valueOf(parser.group(index++)));

    // Validity
    position.setValid(parser.group(index++).compareTo("A") == 0 ? true : false);

    // Latitude
    Double latitude = Double.valueOf(parser.group(index++));
    latitude += Double.valueOf(parser.group(index++)) / 60;
    if (parser.group(index++).compareTo("S") == 0) latitude = -latitude;
    position.setLatitude(latitude);

    // Longitude
    Double lonlitude = Double.valueOf(parser.group(index++));
    lonlitude += Double.valueOf(parser.group(index++)) / 60;
    if (parser.group(index++).compareTo("W") == 0) lonlitude = -lonlitude;
    position.setLongitude(lonlitude);

    // Speed
    position.setSpeed(Double.valueOf(parser.group(index++)));

    // Course
    String course = parser.group(index++);
    if (course != null) {
      position.setCourse(Double.valueOf(course));
    } else {
      position.setCourse(0.0);
    }

    // Date
    time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(parser.group(index++)));
    time.set(Calendar.MONTH, Integer.valueOf(parser.group(index++)) - 1);
    time.set(Calendar.YEAR, 2000 + Integer.valueOf(parser.group(index++)));
    position.setTime(time.getTime());

    // Signal
    extendedInfo.set("signal", parser.group(index++));

    // Alarm
    extendedInfo.set("alarm", parser.group(index++));

    // Get device by IMEI
    String imei = parser.group(index++);
    try {
      position.setDeviceId(getDataManager().getDeviceByImei(imei).getId());
    } catch (Exception error) {
      Log.warning("Unknown device - " + imei);
      return null;
    }

    // Satellites
    extendedInfo.set("satellites", parser.group(index++).replaceFirst("^0*(?![\\.$])", ""));

    // Altitude
    String altitude = parser.group(index++);
    if (altitude != null) {
      position.setAltitude(Double.valueOf(altitude));
    } else {
      position.setAltitude(0.0);
    }

    // Power
    position.setPower(Double.valueOf(parser.group(index++)));

    // Extended info
    position.setExtendedInfo(extendedInfo.toString());

    return position;
  }