generated from Grigo/AndroidTemplate
Lucky attempt to accomplish
This commit is contained in:
@@ -4,8 +4,6 @@ import android.util.Log;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -14,55 +12,14 @@ import java.util.ArrayList;
|
||||
* Работает в гибридном режиме: координаты через Location API, остальное через NMEA
|
||||
*/
|
||||
|
||||
//TODO: Вместо регулярных выражений использовать дерево разбора
|
||||
/**
|
||||
* Контроллер для парсинга NMEA сообщений
|
||||
* Использует простой разбор по запятым вместо регулярных выражений
|
||||
*/
|
||||
public class NMEAParser {
|
||||
|
||||
private static final String TAG = "NMEAParser";
|
||||
|
||||
// Паттерны для NMEA сообщений
|
||||
private static final Pattern GGA_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]GGA,(\\d{6}\\.\\d{2}),(\\d{4}\\.\\d+),([NS]),(\\d{5}\\.\\d+),([EW]),(\\d),(\\d+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern RMC_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]RMC,(\\d{6}\\.\\d{2}),([AV][^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),(\\d{6}),([^,]*),([^,]*),([^,]*),?([^,]*)?\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern VTG_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]VTG,([^,]*),T,([^,]*),M,([^,]*),N,([^,]*),K\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern GLL_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]GLL,(\\d{4}\\.\\d{5}),([NS]),(\\d{5}\\.\\d{5}),([EW]),(\\d{6}),([AV]),([AV])\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern GSV_PATTERN = Pattern.compile(
|
||||
"\\$G[APNLQ]GSV,(\\d+),(\\d+),(\\d+),(.*)\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern GNS_PATTERN = Pattern.compile(
|
||||
"\\$GNGNS,(\\d{6}),(\\d{4}\\.\\d{5}),([NS]),(\\d{5}\\.\\d{5}),([EW]),(\\w+),(\\d+),(\\d+\\.\\d+),(\\d+\\.\\d+),([^,]*),([^,]*),([^,]*),([AV])\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
// Паттерн для GSA сообщения (DOP и активные спутники)
|
||||
private static final Pattern GSA_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]GSA,([AM]),(\\d+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
// Паттерн для обрезанных GSA сообщений
|
||||
private static final Pattern GSA_TRUNCATED_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]GSA,([^,]*),([^,]*),([^,]*)\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
// Паттерн для ZDA сообщения (Date and Time)
|
||||
private static final Pattern ZDA_PATTERN = Pattern.compile(
|
||||
"\\$G[PN]ZDA,(\\d{6}\\.\\d{2}),(\\d{2}),(\\d{2}),(\\d{4}),(\\d{2}),(\\d{2})\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private static final Pattern AIS_PATTERN = Pattern.compile(
|
||||
"!AIVDM,(\\d+),(\\d+),([^,]*),([AB12]),([^,]+),(\\d)\\*([0-9A-F]{2})"
|
||||
);
|
||||
|
||||
private Vessel ownVessel;
|
||||
private List<AISVessel> aisVessels;
|
||||
private NMEAParserListener listener;
|
||||
@@ -131,26 +88,56 @@ public class NMEAParser {
|
||||
Log.d(TAG, "Парсим NMEA: " + cleanedSentence);
|
||||
|
||||
try {
|
||||
if (cleanedSentence.startsWith("$GPGGA") || cleanedSentence.startsWith("$GNGGA")) {
|
||||
parseGGA(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPRMC") || cleanedSentence.startsWith("$GNRMC")) {
|
||||
parseRMC(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPVTG") || cleanedSentence.startsWith("$GNVTG")) {
|
||||
parseVTG(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPGLL") || cleanedSentence.startsWith("$GNGLL")) {
|
||||
parseGLL(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPGSV") || cleanedSentence.startsWith("$GAGSV") || cleanedSentence.startsWith("$GLGSV") || cleanedSentence.startsWith("$GBGSV") || cleanedSentence.startsWith("$GNGSA")) {
|
||||
parseGSV(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GNGNS")) {
|
||||
parseGNS(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPGSA") || cleanedSentence.startsWith("$GNGSA")) {
|
||||
parseGSA(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("$GPZDA") || cleanedSentence.startsWith("$GNZDA")) {
|
||||
parseZDA(cleanedSentence);
|
||||
} else if (cleanedSentence.startsWith("!AIVDM")) {
|
||||
// Разбираем сообщение по запятым
|
||||
String[] fields = cleanedSentence.split(",");
|
||||
if (fields.length < 2) {
|
||||
Log.w(TAG, "NMEA сообщение слишком короткое: " + cleanedSentence);
|
||||
return;
|
||||
}
|
||||
|
||||
// Извлекаем приамбуду (первые 6 символов после $)
|
||||
String preamble = fields[0];
|
||||
if (preamble.length() < 6) {
|
||||
Log.w(TAG, "Некорректная приамбула: " + preamble);
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем тип сообщения по последним трем символам приамбуды
|
||||
String messageType = preamble.substring(preamble.length() - 3);
|
||||
|
||||
switch (messageType) {
|
||||
case "GGA":
|
||||
parseGGA(fields);
|
||||
break;
|
||||
case "RMC":
|
||||
parseRMC(fields);
|
||||
break;
|
||||
case "VTG":
|
||||
parseVTG(fields);
|
||||
break;
|
||||
case "GLL":
|
||||
parseGLL(fields);
|
||||
break;
|
||||
case "GSV":
|
||||
parseGSV(fields);
|
||||
break;
|
||||
case "GNS":
|
||||
parseGNS(fields);
|
||||
break;
|
||||
case "GSA":
|
||||
parseGSA(fields);
|
||||
break;
|
||||
case "ZDA":
|
||||
parseZDA(fields);
|
||||
break;
|
||||
default:
|
||||
// Проверяем AIS сообщения
|
||||
if (cleanedSentence.startsWith("!AIVDM")) {
|
||||
parseAIS(cleanedSentence);
|
||||
} else {
|
||||
Log.d(TAG, "Неподдерживаемый тип NMEA сообщения: " + cleanedSentence);
|
||||
Log.d(TAG, "Неподдерживаемый тип NMEA сообщения: " + messageType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка парсинга NMEA: " + e.getMessage(), e);
|
||||
@@ -160,6 +147,46 @@ public class NMEAParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Безопасно получает поле по индексу
|
||||
*/
|
||||
private String getField(String[] fields, int index) {
|
||||
if (index < fields.length && !fields[index].trim().isEmpty()) {
|
||||
return fields[index].trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Безопасно парсит double значение из поля
|
||||
*/
|
||||
private double parseDoubleField(String[] fields, int index, double defaultValue) {
|
||||
String field = getField(fields, index);
|
||||
if (field != null) {
|
||||
try {
|
||||
return Double.parseDouble(field);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить double из поля " + index + ": '" + field + "'");
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Безопасно парсит int значение из поля
|
||||
*/
|
||||
private int parseIntField(String[] fields, int index, int defaultValue) {
|
||||
String field = getField(fields, index);
|
||||
if (field != null) {
|
||||
try {
|
||||
return Integer.parseInt(field);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить int из поля " + index + ": '" + field + "'");
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает NMEA сообщение от лишних символов
|
||||
*/
|
||||
@@ -228,51 +255,37 @@ public class NMEAParser {
|
||||
/**
|
||||
* Парсит GGA сообщение (Global Positioning System Fix Data)
|
||||
* В гибридном режиме используем только количество спутников и высоту
|
||||
* Формат: $GPGGA,time,lat,N/S,lon,E/W,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*checksum
|
||||
*/
|
||||
private void parseGGA(String gga) {
|
||||
// Log.d(TAG, "Парсим GGA: " + gga);
|
||||
// Log.d(TAG, "Применяем паттерн GGA: " + GGA_PATTERN.pattern());
|
||||
Matcher matcher = GGA_PATTERN.matcher(gga);
|
||||
if (matcher.matches()) {
|
||||
// Log.d(TAG, "GGA совпадает с паттерном");
|
||||
private void parseGGA(String[] fields) {
|
||||
Log.d(TAG, "Парсим GGA с " + fields.length + " полями");
|
||||
|
||||
int satellites = Integer.parseInt(matcher.group(7));
|
||||
// Поле 7: количество спутников
|
||||
int satellites = parseIntField(fields, 7, 0);
|
||||
|
||||
// Обрабатываем высоту - может быть пустым полем (теперь в группе 8)
|
||||
double altitude = 0.0;
|
||||
String altitudeStr = matcher.group(8);
|
||||
if (altitudeStr != null && !altitudeStr.trim().isEmpty()) {
|
||||
try {
|
||||
altitude = Double.parseDouble(altitudeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить высоту: '" + altitudeStr + "', используем 0.0");
|
||||
altitude = 0.0;
|
||||
}
|
||||
}
|
||||
// Поле 9: высота над эллипсоидом
|
||||
double altitude = parseDoubleField(fields, 9, 0.0);
|
||||
|
||||
// Log.d(TAG, String.format("GGA: sat=%d, alt=%.1f", satellites, altitude));
|
||||
Log.d(TAG, String.format("GGA: sat=%d, alt=%.1f", satellites, altitude));
|
||||
|
||||
// В гибридном режиме не обновляем координаты
|
||||
if (!hybridMode) {
|
||||
// Обрабатываем координаты - могут быть пустыми полями (группы 2,3,4,5)
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
|
||||
String latStr = matcher.group(2);
|
||||
String latDir = matcher.group(3);
|
||||
if (latStr != null && !latStr.trim().isEmpty() && latDir != null && !latDir.trim().isEmpty()) {
|
||||
latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
}
|
||||
|
||||
String lonStr = matcher.group(4);
|
||||
String lonDir = matcher.group(5);
|
||||
if (lonStr != null && !lonStr.trim().isEmpty() && lonDir != null && !lonDir.trim().isEmpty()) {
|
||||
longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
}
|
||||
|
||||
// Поля 2,3: широта и направление
|
||||
String latStr = getField(fields, 2);
|
||||
String latDir = getField(fields, 3);
|
||||
if (latStr != null && latDir != null) {
|
||||
double latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
ownVessel.setLatitude(latitude);
|
||||
}
|
||||
|
||||
// Поля 4,5: долгота и направление
|
||||
String lonStr = getField(fields, 4);
|
||||
String lonDir = getField(fields, 5);
|
||||
if (lonStr != null && lonDir != null) {
|
||||
double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
ownVessel.setLongitude(longitude);
|
||||
}
|
||||
}
|
||||
|
||||
ownVessel.setSatellites(satellites);
|
||||
ownVessel.setAltitude(altitude);
|
||||
@@ -285,78 +298,50 @@ public class NMEAParser {
|
||||
if (listener != null) {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else {
|
||||
// Log.w(TAG, "GGA не совпадает с паттерном");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит RMC сообщение (Recommended Minimum Navigation Information)
|
||||
* В гибридном режиме используем только курс и скорость
|
||||
* Формат: $GPRMC,time,status,lat,N/S,lon,E/W,speed,course,date,magVar,E/W,mode*checksum
|
||||
*/
|
||||
private void parseRMC(String rmc) {
|
||||
Log.d(TAG, "Парсим RMC: " + rmc);
|
||||
Log.d(TAG, "Применяем паттерн RMC: " + RMC_PATTERN.pattern());
|
||||
private void parseRMC(String[] fields) {
|
||||
Log.d(TAG, "Парсим RMC с " + fields.length + " полями");
|
||||
|
||||
Matcher matcher = RMC_PATTERN.matcher(rmc);
|
||||
if (matcher.matches()) {
|
||||
Log.d(TAG, "RMC совпадает с паттерном");
|
||||
|
||||
// Проверяем статус валидности (группа 2)
|
||||
String status = matcher.group(2);
|
||||
// Поле 2: статус валидности (A = валидный, V = невалидный)
|
||||
String status = getField(fields, 2);
|
||||
boolean isValid = status != null && status.startsWith("A");
|
||||
Log.d(TAG, "RMC статус: " + status + " (валидный: " + isValid + ")");
|
||||
|
||||
// Обрабатываем скорость - может быть пустым полем (группа 7)
|
||||
double speed = 0.0;
|
||||
String speedStr = matcher.group(7);
|
||||
if (speedStr != null && !speedStr.trim().isEmpty()) {
|
||||
try {
|
||||
speed = Double.parseDouble(speedStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить скорость RMC: '" + speedStr + "', используем 0.0");
|
||||
speed = 0.0;
|
||||
}
|
||||
}
|
||||
// Поле 7: скорость в узлах
|
||||
double speed = parseDoubleField(fields, 7, 0.0);
|
||||
|
||||
// Обрабатываем курс - может быть пустым полем (группа 8)
|
||||
double course = 0.0;
|
||||
String courseStr = matcher.group(8);
|
||||
if (courseStr != null && !courseStr.trim().isEmpty()) {
|
||||
try {
|
||||
course = Double.parseDouble(courseStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить курс: '" + courseStr + "', используем 0.0");
|
||||
course = 0.0;
|
||||
}
|
||||
}
|
||||
// Поле 8: курс в градусах
|
||||
double course = parseDoubleField(fields, 8, 0.0);
|
||||
|
||||
Log.d(TAG, String.format("RMC: speed=%.1f, course=%.1f, valid=%s", speed, course, isValid));
|
||||
|
||||
// В гибридном режиме не обновляем координаты
|
||||
if (!hybridMode && isValid) {
|
||||
Log.d(TAG, "Режим НЕ гибридный - обрабатываем координаты из RMC");
|
||||
// Обрабатываем координаты - могут быть пустыми полями (группы 3,4,5,6)
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
|
||||
String latStr = matcher.group(3);
|
||||
String latDir = matcher.group(4);
|
||||
if (latStr != null && !latStr.trim().isEmpty() && latDir != null && !latDir.trim().isEmpty()) {
|
||||
latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
// Поля 3,4: широта и направление
|
||||
String latStr = getField(fields, 3);
|
||||
String latDir = getField(fields, 4);
|
||||
if (latStr != null && latDir != null) {
|
||||
double latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
Log.d(TAG, "RMC широта: " + latStr + " " + latDir + " = " + latitude);
|
||||
}
|
||||
|
||||
String lonStr = matcher.group(5);
|
||||
String lonDir = matcher.group(6);
|
||||
if (lonStr != null && !lonStr.trim().isEmpty() && lonDir != null && !lonDir.trim().isEmpty()) {
|
||||
longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
Log.d(TAG, "RMC долгота: " + lonStr + " " + lonDir + " = " + longitude);
|
||||
}
|
||||
|
||||
Log.d(TAG, "RMC устанавливаем координаты: lat=" + latitude + ", lon=" + longitude);
|
||||
ownVessel.setLatitude(latitude);
|
||||
}
|
||||
|
||||
// Поля 5,6: долгота и направление
|
||||
String lonStr = getField(fields, 5);
|
||||
String lonDir = getField(fields, 6);
|
||||
if (lonStr != null && lonDir != null) {
|
||||
double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
Log.d(TAG, "RMC долгота: " + lonStr + " " + lonDir + " = " + longitude);
|
||||
ownVessel.setLongitude(longitude);
|
||||
}
|
||||
} else if (hybridMode) {
|
||||
Log.d(TAG, "Гибридный режим - координаты из RMC игнорируются");
|
||||
} else {
|
||||
@@ -377,44 +362,22 @@ public class NMEAParser {
|
||||
if (listener != null) {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "RMC не совпадает с паттерном");
|
||||
Log.w(TAG, "Сообщение: '" + rmc + "'");
|
||||
Log.w(TAG, "Паттерн: " + RMC_PATTERN.pattern());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит VTG сообщение (Course Over Ground and Ground Speed)
|
||||
* Формат: $GPVTG,course,T,course,M,speed,N,speed,K,mode*checksum
|
||||
*/
|
||||
private void parseVTG(String vtg) {
|
||||
Matcher matcher = VTG_PATTERN.matcher(vtg);
|
||||
if (matcher.matches()) {
|
||||
// Обрабатываем курс - может быть пустым полем
|
||||
double course = 0.0;
|
||||
String courseStr = matcher.group(2);
|
||||
if (courseStr != null && !courseStr.trim().isEmpty()) {
|
||||
try {
|
||||
course = Double.parseDouble(courseStr);
|
||||
} catch (NumberFormatException e) {
|
||||
// Log.w(TAG, "Не удалось распарсить курс VTG: '" + courseStr + "', используем 0.0");
|
||||
course = 0.0;
|
||||
}
|
||||
}
|
||||
private void parseVTG(String[] fields) {
|
||||
Log.d(TAG, "Парсим VTG с " + fields.length + " полями");
|
||||
|
||||
// Обрабатываем скорость - может быть пустым полем
|
||||
double speed = 0.0;
|
||||
String speedStr = matcher.group(4);
|
||||
if (speedStr != null && !speedStr.trim().isEmpty()) {
|
||||
try {
|
||||
speed = Double.parseDouble(speedStr);
|
||||
} catch (NumberFormatException e) {
|
||||
// Log.w(TAG, "Не удалось распарсить скорость VTG: '" + speedStr + "', используем 0.0");
|
||||
speed = 0.0;
|
||||
}
|
||||
}
|
||||
// Поле 1: курс в градусах (True)
|
||||
double course = parseDoubleField(fields, 1, 0.0);
|
||||
|
||||
// Log.d(TAG, String.format("VTG: course=%.1f, speed=%.1f", course, speed));
|
||||
// Поле 5: скорость в узлах
|
||||
double speed = parseDoubleField(fields, 5, 0.0);
|
||||
|
||||
Log.d(TAG, String.format("VTG: course=%.1f, speed=%.1f", course, speed));
|
||||
|
||||
ownVessel.setCourse(course);
|
||||
ownVessel.setSpeed(speed);
|
||||
@@ -423,104 +386,87 @@ public class NMEAParser {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит GLL сообщение (Geographic Position - Latitude/Longitude)
|
||||
* В гибридном режиме игнорируем
|
||||
* Формат: $GPGLL,lat,N/S,lon,E/W,time,status,mode*checksum
|
||||
*/
|
||||
private void parseGLL(String gll) {
|
||||
private void parseGLL(String[] fields) {
|
||||
if (hybridMode) {
|
||||
Log.d(TAG, "GLL игнорируется в гибридном режиме");
|
||||
return;
|
||||
}
|
||||
|
||||
// Log.d(TAG, "Парсим GLL: " + gll);
|
||||
Matcher matcher = GLL_PATTERN.matcher(gll);
|
||||
if (matcher.matches()) {
|
||||
// Log.d(TAG, "GLL совпадает с паттерном");
|
||||
// Обрабатываем координаты - могут быть пустыми полями
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
|
||||
String latStr = matcher.group(1);
|
||||
String latDir = matcher.group(2);
|
||||
if (latStr != null && !latStr.trim().isEmpty() && latDir != null && !latDir.trim().isEmpty()) {
|
||||
latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
}
|
||||
|
||||
String lonStr = matcher.group(3);
|
||||
String lonDir = matcher.group(4);
|
||||
if (lonStr != null && !lonStr.trim().isEmpty() && lonDir != null && !lonDir.trim().isEmpty()) {
|
||||
longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
}
|
||||
|
||||
// Log.d(TAG, String.format("GLL: lat=%.6f, lon=%.6f", latitude, longitude));
|
||||
Log.d(TAG, "Парсим GLL с " + fields.length + " полями");
|
||||
|
||||
// Поля 1,2: широта и направление
|
||||
String latStr = getField(fields, 1);
|
||||
String latDir = getField(fields, 2);
|
||||
if (latStr != null && latDir != null) {
|
||||
double latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
ownVessel.setLatitude(latitude);
|
||||
}
|
||||
|
||||
// Поля 3,4: долгота и направление
|
||||
String lonStr = getField(fields, 3);
|
||||
String lonDir = getField(fields, 4);
|
||||
if (lonStr != null && lonDir != null) {
|
||||
double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
ownVessel.setLongitude(longitude);
|
||||
}
|
||||
|
||||
Log.d(TAG, String.format("GLL: lat=%.6f, lon=%.6f", ownVessel.getLatitude(), ownVessel.getLongitude()));
|
||||
|
||||
if (listener != null) {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else {
|
||||
// Log.w(TAG, "GLL не совпадает с паттерном");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит GSV сообщение (GPS Satellites in View)
|
||||
* Формат: $GPGSV,totalMsgs,msgNum,totalSats,satId1,elev1,azim1,snr1,satId2,elev2,azim2,snr2,...*checksum
|
||||
*/
|
||||
private void parseGSV(String gsv) {
|
||||
// Log.d(TAG, "Парсим GSV: " + gsv);
|
||||
// Log.d(TAG, "Применяем паттерн GSV: " + GSV_PATTERN.pattern());
|
||||
Matcher matcher = GSV_PATTERN.matcher(gsv);
|
||||
if (matcher.matches()) {
|
||||
// Log.d(TAG, "GSV совпадает с паттерном");
|
||||
int totalMessages = Integer.parseInt(matcher.group(1));
|
||||
int messageNumber = Integer.parseInt(matcher.group(2));
|
||||
int satellitesInView = Integer.parseInt(matcher.group(3));
|
||||
private void parseGSV(String[] fields) {
|
||||
Log.d(TAG, "Парсим GSV с " + fields.length + " полями");
|
||||
|
||||
// Определяем тип системы спутников
|
||||
// Поля 1,2,3: общее количество сообщений, номер сообщения, общее количество спутников
|
||||
int totalMessages = parseIntField(fields, 1, 1);
|
||||
int messageNumber = parseIntField(fields, 2, 1);
|
||||
int satellitesInView = parseIntField(fields, 3, 0);
|
||||
|
||||
// Определяем тип системы спутников по приамбуде
|
||||
String systemType = "Unknown";
|
||||
if (gsv.startsWith("$GPGSV")) {
|
||||
String preamble = fields[0];
|
||||
if (preamble.startsWith("$GPGSV")) {
|
||||
systemType = "GPS";
|
||||
} else if (gsv.startsWith("$GLGSV")) {
|
||||
} else if (preamble.startsWith("$GLGSV")) {
|
||||
systemType = "GLONASS";
|
||||
} else if (gsv.startsWith("$GAGSV")) {
|
||||
} else if (preamble.startsWith("$GAGSV")) {
|
||||
systemType = "Galileo";
|
||||
} else if (gsv.startsWith("$GBGSV")) {
|
||||
} else if (preamble.startsWith("$GBGSV")) {
|
||||
systemType = "BeiDou";
|
||||
} else if (gsv.startsWith("$GNGSA")) {
|
||||
systemType = "GNSS";
|
||||
}
|
||||
|
||||
// Log.d(TAG, String.format("GSV [%s]: %d/%d, спутников в поле зрения: %d",
|
||||
// systemType, messageNumber, totalMessages, satellitesInView));
|
||||
Log.d(TAG, String.format("GSV [%s]: %d/%d, спутников в поле зрения: %d",
|
||||
systemType, messageNumber, totalMessages, satellitesInView));
|
||||
|
||||
// Парсим данные о спутниках из группы 4
|
||||
String satelliteData = matcher.group(4);
|
||||
if (satelliteData != null && !satelliteData.trim().isEmpty()) {
|
||||
String[] satFields = satelliteData.split(",");
|
||||
// Log.d(TAG, String.format("Найдено %d полей данных о спутниках", satFields.length));
|
||||
// Парсим данные о спутниках (начиная с поля 4, каждые 4 поля = 1 спутник)
|
||||
for (int i = 4; i < fields.length - 1; i += 4) { // -1 чтобы исключить контрольную сумму
|
||||
if (i + 3 < fields.length) {
|
||||
String satId = getField(fields, i);
|
||||
String elevation = getField(fields, i + 1);
|
||||
String azimuth = getField(fields, i + 2);
|
||||
String snr = getField(fields, i + 3);
|
||||
|
||||
// Логируем информацию о спутниках (каждые 4 поля = 1 спутник)
|
||||
for (int i = 0; i < satFields.length; i += 4) {
|
||||
if (i + 3 < satFields.length) {
|
||||
String satId = satFields[i];
|
||||
String elevation = satFields[i + 1];
|
||||
String azimuth = satFields[i + 2];
|
||||
String snr = satFields[i + 3];
|
||||
|
||||
if (!satId.trim().isEmpty()) {
|
||||
// Log.d(TAG, String.format("Спутник %s: elev=%s, azim=%s, SNR=%s",
|
||||
// satId, elevation, azimuth, snr));
|
||||
}
|
||||
if (satId != null) {
|
||||
Log.d(TAG, String.format("Спутник %s: elev=%s, azim=%s, SNR=%s",
|
||||
satId, elevation, azimuth, snr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GSV содержит информацию о спутниках, но не обновляет позицию
|
||||
// Обновляем количество спутников только для последнего сообщения в серии
|
||||
if (messageNumber == totalMessages) {
|
||||
// Обновляем количество спутников для соответствующей системы
|
||||
switch (systemType) {
|
||||
@@ -548,67 +494,49 @@ public class NMEAParser {
|
||||
gpsLocationListener.setSatellitesInVessel(ownVessel);
|
||||
}
|
||||
|
||||
// Log.d(TAG, String.format("GSV [%s] завершен: %d спутников. Общий счет: GPS=%d, GLONASS=%d, Galileo=%d, Всего=%d",
|
||||
// systemType, satellitesInView, gpsSatellites, glonassSatellites, galileoSatellites, totalSatellites));
|
||||
Log.d(TAG, String.format("GSV [%s] завершен: %d спутников. Общий счет: GPS=%d, GLONASS=%d, Galileo=%d, Всего=%d",
|
||||
systemType, satellitesInView, gpsSatellites, glonassSatellites, galileoSatellites, totalSatellites));
|
||||
|
||||
if (listener != null) {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Log.w(TAG, "GSV не совпадает с паттерном");
|
||||
// Log.d(TAG, "Сообщение: '" + gsv + "'");
|
||||
// Log.d(TAG, "Паттерн: " + GSV_PATTERN.pattern());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит GNS сообщение (GNSS Fix Data)
|
||||
* В гибридном режиме используем только количество спутников и высоту
|
||||
* Формат: $GNGNS,time,lat,N/S,lon,E/W,mode,numSV,HDOP,alt,sep,diffAge,diffStation,navStatus*checksum
|
||||
*/
|
||||
private void parseGNS(String gns) {
|
||||
Log.d(TAG, "Парсим GNS: " + gns);
|
||||
Matcher matcher = GNS_PATTERN.matcher(gns);
|
||||
if (matcher.matches()) {
|
||||
// Log.d(TAG, "GNS совпадает с паттерном");
|
||||
private void parseGNS(String[] fields) {
|
||||
Log.d(TAG, "Парсим GNS с " + fields.length + " полями");
|
||||
|
||||
int satellites = Integer.parseInt(matcher.group(7));
|
||||
// Поле 7: количество спутников
|
||||
int satellites = parseIntField(fields, 7, 0);
|
||||
|
||||
// Обрабатываем высоту - может быть пустым полем
|
||||
double altitude = 0.0;
|
||||
String altitudeStr = matcher.group(8);
|
||||
if (altitudeStr != null && !altitudeStr.trim().isEmpty()) {
|
||||
try {
|
||||
altitude = Double.parseDouble(altitudeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
// Log.w(TAG, "Не удалось распарсить высоту GNS: '" + altitudeStr + "', используем 0.0");
|
||||
altitude = 0.0;
|
||||
}
|
||||
}
|
||||
// Поле 9: высота над эллипсоидом
|
||||
double altitude = parseDoubleField(fields, 9, 0.0);
|
||||
|
||||
// Log.d(TAG, String.format("GNS: sat=%d, alt=%.1f", satellites, altitude));
|
||||
Log.d(TAG, String.format("GNS: sat=%d, alt=%.1f", satellites, altitude));
|
||||
|
||||
// В гибридном режиме не обновляем координаты
|
||||
if (!hybridMode) {
|
||||
// Обрабатываем координаты - могут быть пустыми полями
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
|
||||
String latStr = matcher.group(2);
|
||||
String latDir = matcher.group(3);
|
||||
if (latStr != null && !latStr.trim().isEmpty() && latDir != null && !latDir.trim().isEmpty()) {
|
||||
latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
}
|
||||
|
||||
String lonStr = matcher.group(4);
|
||||
String lonDir = matcher.group(5);
|
||||
if (lonStr != null && !lonStr.trim().isEmpty() && lonDir != null && !lonDir.trim().isEmpty()) {
|
||||
longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
}
|
||||
|
||||
// Поля 2,3: широта и направление
|
||||
String latStr = getField(fields, 2);
|
||||
String latDir = getField(fields, 3);
|
||||
if (latStr != null && latDir != null) {
|
||||
double latitude = parseCoordinate(latStr, latDir.equals("N"));
|
||||
ownVessel.setLatitude(latitude);
|
||||
}
|
||||
|
||||
// Поля 4,5: долгота и направление
|
||||
String lonStr = getField(fields, 4);
|
||||
String lonDir = getField(fields, 5);
|
||||
if (lonStr != null && lonDir != null) {
|
||||
double longitude = parseCoordinate(lonStr, lonDir.equals("E"));
|
||||
ownVessel.setLongitude(longitude);
|
||||
}
|
||||
}
|
||||
|
||||
ownVessel.setSatellites(satellites);
|
||||
ownVessel.setAltitude(altitude);
|
||||
@@ -621,31 +549,27 @@ public class NMEAParser {
|
||||
if (listener != null) {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else {
|
||||
// Log.w(TAG, "GNS не совпадает с паттерном");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит ZDA сообщение (Date and Time)
|
||||
* Формат: $GPZDA,time,day,month,year,timezoneHours,timezoneMinutes*checksum
|
||||
*/
|
||||
private void parseZDA(String zda) {
|
||||
Log.d(TAG, "Парсим ZDA: " + zda);
|
||||
Matcher matcher = ZDA_PATTERN.matcher(zda);
|
||||
if (matcher.matches()) {
|
||||
private void parseZDA(String[] fields) {
|
||||
Log.d(TAG, "Парсим ZDA с " + fields.length + " полями");
|
||||
|
||||
try {
|
||||
// Время (HHMMSS.SS)
|
||||
String timeStr = matcher.group(1);
|
||||
// День (DD)
|
||||
int day = Integer.parseInt(matcher.group(2));
|
||||
// Месяц (MM)
|
||||
int month = Integer.parseInt(matcher.group(3));
|
||||
// Год (YYYY)
|
||||
int year = Integer.parseInt(matcher.group(4));
|
||||
// Часовой пояс (часы)
|
||||
int timezoneHours = Integer.parseInt(matcher.group(5));
|
||||
// Часовой пояс (минуты)
|
||||
int timezoneMinutes = Integer.parseInt(matcher.group(6));
|
||||
// Поле 1: время (HHMMSS.SS)
|
||||
String timeStr = getField(fields, 1);
|
||||
|
||||
// Поля 2,3,4: день, месяц, год
|
||||
int day = parseIntField(fields, 2, 0);
|
||||
int month = parseIntField(fields, 3, 0);
|
||||
int year = parseIntField(fields, 4, 0);
|
||||
|
||||
// Поля 5,6: часовой пояс (часы и минуты)
|
||||
int timezoneHours = parseIntField(fields, 5, 0);
|
||||
int timezoneMinutes = parseIntField(fields, 6, 0);
|
||||
|
||||
Log.d(TAG, String.format("ZDA: %04d-%02d-%02d %s, TZ: %+03d:%02d",
|
||||
year, month, day, timeStr, timezoneHours, timezoneMinutes));
|
||||
@@ -657,65 +581,47 @@ public class NMEAParser {
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Ошибка парсинга ZDA: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "ZDA не совпадает с паттерном: " + zda);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит GSA сообщение (GPS DOP and Active Satellites)
|
||||
* КЛЮЧЕВОЕ сообщение для получения DOP и активных спутников
|
||||
* Формат: $GPGSA,mode,fixType,sat1,sat2,...,sat12,PDOP,HDOP,VDOP*checksum
|
||||
*/
|
||||
private void parseGSA(String gsa) {
|
||||
Log.d(TAG, "Парсим GSA: " + gsa);
|
||||
Matcher matcher = GSA_PATTERN.matcher(gsa);
|
||||
Matcher truncatedMatcher = GSA_TRUNCATED_PATTERN.matcher(gsa);
|
||||
private void parseGSA(String[] fields) {
|
||||
Log.d(TAG, "Парсим GSA с " + fields.length + " полями");
|
||||
|
||||
if (matcher.matches()) {
|
||||
Log.d(TAG, "GSA совпадает с паттерном");
|
||||
|
||||
// Подсчитываем активные спутники (непустые поля)
|
||||
// Подсчитываем активные спутники (поля 3-14 содержат ID спутников)
|
||||
int activeSatellites = 0;
|
||||
for (int i = 3; i <= 14; i++) { // Группы 3-14 содержат ID спутников
|
||||
String satId = matcher.group(i);
|
||||
if (satId != null && !satId.trim().isEmpty() && !satId.equals("0")) {
|
||||
for (int i = 3; i <= 14 && i < fields.length; i++) {
|
||||
String satId = getField(fields, i);
|
||||
if (satId != null && !satId.equals("0")) {
|
||||
activeSatellites++;
|
||||
Log.d(TAG, "Активный спутник: " + satId);
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем DOP значения - могут быть пустыми полями
|
||||
// Получаем DOP значения - могут быть в разных позициях в зависимости от количества полей
|
||||
double pdop = 0.0;
|
||||
double hdop = 0.0;
|
||||
double vdop = 0.0;
|
||||
|
||||
String pdopStr = matcher.group(15); // PDOP в группе 15
|
||||
if (pdopStr != null && !pdopStr.trim().isEmpty()) {
|
||||
try {
|
||||
pdop = Double.parseDouble(pdopStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить PDOP: '" + pdopStr + "', используем 0.0");
|
||||
}
|
||||
}
|
||||
|
||||
String hdopStr = matcher.group(16); // HDOP в группе 16
|
||||
if (hdopStr != null && !hdopStr.trim().isEmpty()) {
|
||||
try {
|
||||
hdop = Double.parseDouble(hdopStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить HDOP: '" + hdopStr + "', используем 0.0");
|
||||
}
|
||||
}
|
||||
|
||||
String vdopStr = matcher.group(17); // VDOP в группе 17
|
||||
if (vdopStr != null && !vdopStr.trim().isEmpty()) {
|
||||
try {
|
||||
vdop = Double.parseDouble(vdopStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Не удалось распарсить VDOP: '" + vdopStr + "', используем 0.0");
|
||||
// DOP значения обычно в последних полях перед контрольной суммой
|
||||
if (fields.length >= 17) {
|
||||
// Полное GSA сообщение
|
||||
pdop = parseDoubleField(fields, 15, 0.0); // PDOP
|
||||
hdop = parseDoubleField(fields, 16, 0.0); // HDOP
|
||||
vdop = parseDoubleField(fields, 17, 0.0); // VDOP
|
||||
} else if (fields.length >= 6) {
|
||||
// Обрезанное GSA сообщение - DOP в последних полях
|
||||
int dopStartIndex = fields.length - 4; // -4 чтобы исключить контрольную сумму
|
||||
if (dopStartIndex >= 3) {
|
||||
pdop = parseDoubleField(fields, dopStartIndex, 0.0);
|
||||
hdop = parseDoubleField(fields, dopStartIndex + 1, 0.0);
|
||||
vdop = parseDoubleField(fields, dopStartIndex + 2, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,71 +646,48 @@ public class NMEAParser {
|
||||
listener.onDOPUpdated(pdop, hdop, vdop);
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else if (truncatedMatcher.matches()) {
|
||||
Log.d(TAG, "GSA совпадает с обрезанным паттерном");
|
||||
|
||||
// Обрабатываем обрезанное GSA сообщение
|
||||
String pdopStr = truncatedMatcher.group(1);
|
||||
String hdopStr = truncatedMatcher.group(2);
|
||||
String vdopStr = truncatedMatcher.group(3);
|
||||
|
||||
double pdop = 0.0;
|
||||
double hdop = 0.0;
|
||||
double vdop = 0.0;
|
||||
|
||||
try {
|
||||
if (pdopStr != null && !pdopStr.trim().isEmpty()) {
|
||||
pdop = Double.parseDouble(pdopStr);
|
||||
}
|
||||
if (hdopStr != null && !hdopStr.trim().isEmpty()) {
|
||||
hdop = Double.parseDouble(hdopStr);
|
||||
}
|
||||
if (vdopStr != null && !vdopStr.trim().isEmpty()) {
|
||||
vdop = Double.parseDouble(vdopStr);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Ошибка парсинга DOP в обрезанном GSA: " + e.getMessage());
|
||||
}
|
||||
|
||||
Log.d(TAG, String.format("GSA (обрезанное): PDOP=%.2f, HDOP=%.2f, VDOP=%.2f", pdop, hdop, vdop));
|
||||
|
||||
// Обновляем DOP значения
|
||||
ownVessel.setPdop(pdop);
|
||||
ownVessel.setHdop(hdop);
|
||||
ownVessel.setVdop(vdop);
|
||||
|
||||
// Отправляем DOP значения в GPS Location Listener
|
||||
if (gpsLocationListener != null) {
|
||||
gpsLocationListener.setDOPValues(pdop, hdop, vdop);
|
||||
}
|
||||
|
||||
// Уведомляем слушателя о DOP
|
||||
if (listener != null) {
|
||||
listener.onDOPUpdated(pdop, hdop, vdop);
|
||||
listener.onVesselUpdated(ownVessel);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "GSA не совпадает ни с одним паттерном");
|
||||
Log.w(TAG, "Сообщение: '" + gsa + "'");
|
||||
Log.w(TAG, "Паттерн: " + GSA_PATTERN.pattern());
|
||||
Log.w(TAG, "Обрезанный паттерн: " + GSA_TRUNCATED_PATTERN.pattern());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит AIS сообщение (Automatic Identification System)
|
||||
* Формат: !AIVDM,totalFragments,fragmentNumber,sequenceId,channel,payload,fillBits*checksum
|
||||
*/
|
||||
private void parseAIS(String ais) {
|
||||
Matcher matcher = AIS_PATTERN.matcher(ais);
|
||||
if (matcher.matches()) {
|
||||
Log.d(TAG, "Парсим AIS: " + ais);
|
||||
|
||||
// Разбираем AIS сообщение по запятым
|
||||
String[] fields = ais.split(",");
|
||||
if (fields.length < 7) {
|
||||
Log.w(TAG, "AIS сообщение слишком короткое: " + ais);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int totalFragments = Integer.parseInt(matcher.group(1));
|
||||
int fragmentNumber = Integer.parseInt(matcher.group(2));
|
||||
String sequenceId = matcher.group(3);
|
||||
String channel = matcher.group(4);
|
||||
String payload = matcher.group(5);
|
||||
int fillBits = Integer.parseInt(matcher.group(6));
|
||||
String checksum = matcher.group(7);
|
||||
// Поля 1,2: общее количество фрагментов, номер фрагмента
|
||||
int totalFragments = parseIntField(fields, 1, 1);
|
||||
int fragmentNumber = parseIntField(fields, 2, 1);
|
||||
|
||||
// Поле 3: ID последовательности
|
||||
String sequenceId = getField(fields, 3);
|
||||
|
||||
// Поле 4: канал (A или B)
|
||||
String channel = getField(fields, 4);
|
||||
|
||||
// Поле 5: payload (данные)
|
||||
String payload = getField(fields, 5);
|
||||
|
||||
// Поле 6: количество бит заполнения
|
||||
int fillBits = parseIntField(fields, 6, 0);
|
||||
|
||||
// Контрольная сумма находится в последнем поле после *
|
||||
String lastField = fields[fields.length - 1];
|
||||
String checksum = null;
|
||||
if (lastField != null && lastField.contains("*")) {
|
||||
String[] parts = lastField.split("\\*");
|
||||
if (parts.length > 1) {
|
||||
checksum = parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, String.format("AIS: %d/%d, seq='%s', ch='%s', payload='%s', fillBits=%d, checksum='%s'",
|
||||
fragmentNumber, totalFragments, sequenceId, channel, payload, fillBits, checksum));
|
||||
@@ -819,27 +702,23 @@ public class NMEAParser {
|
||||
if (payload != null && !payload.trim().isEmpty()) {
|
||||
if (totalFragments == 1) {
|
||||
// Одноканальное сообщение - декодируем сразу
|
||||
decodeAISPayload(payload, channel.equals("A") ? 0 : 1);
|
||||
decodeAISPayload(payload, channel != null && channel.equals("A") ? 0 : 1);
|
||||
} else {
|
||||
// Многочастное сообщение - собираем фрагменты
|
||||
// Используем номер фрагмента как sequenceId если поле пустое
|
||||
String actualSequenceId = (sequenceId != null && !sequenceId.trim().isEmpty()) ?
|
||||
sequenceId : String.valueOf(fragmentNumber);
|
||||
collectAISFragments(actualSequenceId, fragmentNumber, totalFragments, payload, channel.equals("A") ? 0 : 1);
|
||||
collectAISFragments(actualSequenceId, fragmentNumber, totalFragments, payload, channel != null && channel.equals("A") ? 0 : 1);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "AIS payload пустой, пропускаем сообщение");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка парсинга AIS сообщения: " + e.getMessage() + " для сообщения: " + ais);
|
||||
if (listener != null) {
|
||||
listener.onParseError("Ошибка парсинга AIS: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "AIS сообщение не соответствует паттерну: " + ais);
|
||||
Log.d(TAG, "Паттерн: " + AIS_PATTERN.pattern());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user