kopia lustrzana https://github.com/sh123/codec2_talkie
Merge branch 'master' into legacy
commit
5043aa9d06
|
@ -167,7 +167,7 @@ public class AppWorker extends Thread {
|
|||
Message msg = new Message();
|
||||
msg.what = AppMessage.CMD_SEND_LOCATION_TO_TNC.toInt();
|
||||
msg.obj = position;
|
||||
Log.i(TAG, String.format("Position sent: %f %f %f %f %f",
|
||||
Log.i(TAG, String.format("Position sent: lat: %f, lon: %f, course: %f, speed: %f, alt: %f",
|
||||
position.latitude, position.longitude,
|
||||
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters));
|
||||
_onMessageReceived.sendMessage(msg);
|
||||
|
@ -226,9 +226,10 @@ public class AppWorker extends Thread {
|
|||
private final ProtocolCallback _protocolCallback = new ProtocolCallback() {
|
||||
@Override
|
||||
protected void onReceivePosition(Position position) {
|
||||
Log.i(TAG, String.format("Position received: %f %f %f %f %f",
|
||||
Log.i(TAG, String.format("Position received: lat: %f, lon: %f, course: %f, speed: %f, alt: %f, sym: %s, status: %s, comment: %s",
|
||||
position.latitude, position.longitude,
|
||||
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters));
|
||||
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters,
|
||||
position.symbolCode, position.status, position.comment));
|
||||
// TODO, store to database
|
||||
}
|
||||
|
||||
|
|
|
@ -145,11 +145,10 @@ public class Aprs implements Protocol {
|
|||
protected void onReceiveData(String src, String dst, byte[] data) {
|
||||
if (data.length == 0) return;
|
||||
AprsDataType dataType = new AprsDataType((char)data[0]);
|
||||
AprsData aprsData = AprsDataFactory.fromBinary(data);
|
||||
AprsData aprsData = AprsDataFactory.fromBinary(src, dst, data);
|
||||
if (aprsData != null && aprsData.isValid()) {
|
||||
if (dataType.isTextMessage()) {
|
||||
TextMessage textMessage = aprsData.toTextMessage();
|
||||
textMessage.src = src;
|
||||
_parentProtocolCallback.onReceiveTextMessage(textMessage);
|
||||
return;
|
||||
} else if (dataType.isPositionReport()) {
|
||||
|
|
|
@ -8,7 +8,7 @@ public interface AprsData {
|
|||
void fromTextMessage(TextMessage textMessage);
|
||||
Position toPosition();
|
||||
TextMessage toTextMessage();
|
||||
void fromBinary(byte[] infoData);
|
||||
void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData);
|
||||
byte[] toBinary();
|
||||
boolean isValid();
|
||||
}
|
||||
|
|
|
@ -22,14 +22,14 @@ public class AprsDataFactory {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static AprsData fromBinary(byte[] infoData) {
|
||||
public static AprsData fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(infoData);
|
||||
AprsDataType dataType = new AprsDataType((char)buffer.get());
|
||||
AprsData aprsData = create(dataType);
|
||||
if (aprsData == null) return null;
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
buffer.get(data);
|
||||
aprsData.fromBinary(data);
|
||||
aprsData.fromBinary(srcCallsign, dstCallsign, data);
|
||||
return aprsData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.radio.codec2talkie.protocol.aprs.tools.AprsTools;
|
|||
import com.radio.codec2talkie.protocol.message.TextMessage;
|
||||
import com.radio.codec2talkie.protocol.position.Position;
|
||||
import com.radio.codec2talkie.tools.MathTools;
|
||||
import com.radio.codec2talkie.tools.TextTools;
|
||||
import com.radio.codec2talkie.tools.UnitTools;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -43,9 +44,13 @@ public class AprsDataPositionReport implements AprsData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void fromBinary(byte[] infoData) {
|
||||
public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) {
|
||||
_isValid = false;
|
||||
_position = new Position();
|
||||
_position.srcCallsign = srcCallsign;
|
||||
_position.dstCallsign = dstCallsign;
|
||||
_position.status = "";
|
||||
_position.privacyLevel = 0;
|
||||
if ((infoData[0] == '/' || infoData[0] == '\\') && fromCompressedBinary(infoData)) {
|
||||
_position.isCompressed = true;
|
||||
_isValid = true;
|
||||
|
@ -201,18 +206,23 @@ public class AprsDataPositionReport implements AprsData {
|
|||
byte[] tail = new byte[buffer.remaining()];
|
||||
buffer.get(tail);
|
||||
String strTail = new String(tail);
|
||||
Pattern latLonPattern = Pattern.compile("^(\\d{4}[.]\\d{2})(N|S)([/\\\\])(\\d{5}[.]\\d{2})(E|W)(\\S)(.+)$");
|
||||
Pattern latLonPattern = Pattern.compile("^([\\d ]{4}[.][\\d ]{2})(N|S)([/\\\\])([\\d ]{5}[.][\\d ]{2})(E|W)(\\S)(.+)$");
|
||||
Matcher latLonMatcher = latLonPattern.matcher(strTail);
|
||||
if (!latLonMatcher.matches()) return false;
|
||||
|
||||
String lat = latLonMatcher.group(1);
|
||||
String latSuffix = latLonMatcher.group(2);
|
||||
if (lat == null || latSuffix == null) return false;
|
||||
_position.privacyLevel = TextTools.countChars(lat, ' ');
|
||||
// NOTE, ambiguity, replace with 0
|
||||
lat = lat.replace(' ', '0');
|
||||
_position.latitude = UnitTools.nmeaToDecimal(lat, latSuffix);
|
||||
String table = latLonMatcher.group(3);
|
||||
String lon = latLonMatcher.group(4);
|
||||
String lonSuffix = latLonMatcher.group(5);
|
||||
if (lon == null || lonSuffix == null) return false;
|
||||
// NOTE, ambiguity, replace with 0
|
||||
lon = lon.replace(' ', '0');
|
||||
_position.longitude = UnitTools.nmeaToDecimal(lon, lonSuffix);
|
||||
String symbol = latLonMatcher.group(6);
|
||||
_position.symbolCode = String.format("%s%s", table, symbol);
|
||||
|
|
|
@ -6,7 +6,9 @@ import com.radio.codec2talkie.protocol.position.Position;
|
|||
import com.radio.codec2talkie.tools.UnitTools;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class AprsDataPositionReportMicE implements AprsData {
|
||||
|
@ -37,6 +39,28 @@ public class AprsDataPositionReportMicE implements AprsData {
|
|||
put("emergency", 0b000);
|
||||
}};
|
||||
|
||||
private final Map<Integer, String> _miceMessageReverseTypeMapStd = new HashMap<Integer, String>() {{
|
||||
put(0b000, "emergency");
|
||||
put(0b111, "off_duty");
|
||||
put(0b110, "en_route");
|
||||
put(0b101, "in_service");
|
||||
put(0b100, "returning");
|
||||
put(0b011, "committed");
|
||||
put(0b010, "special");
|
||||
put(0b001, "priority");
|
||||
}};
|
||||
|
||||
private final Map<Integer, String> _miceMessageReverseTypeMapCustom = new HashMap<Integer, String>() {{
|
||||
put(0b000, "emergency");
|
||||
put(0b111, "custom_0");
|
||||
put(0b110, "custom_1");
|
||||
put(0b101, "custom_2");
|
||||
put(0b100, "custom_3");
|
||||
put(0b011, "custom_4");
|
||||
put(0b010, "custom_5");
|
||||
put(0b001, "custom_6");
|
||||
}};
|
||||
|
||||
@Override
|
||||
public void fromPosition(Position position) {
|
||||
_isValid = false;
|
||||
|
@ -85,18 +109,106 @@ public class AprsDataPositionReportMicE implements AprsData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void fromBinary(byte[] infoData) {
|
||||
public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) {
|
||||
_isValid = false;
|
||||
// TODO, implement fromBinary, needs dst callsign
|
||||
// ByteBuffer buffer = ByteBuffer.wrap(infoData);
|
||||
_position = new Position();
|
||||
_dstCallsign = dstCallsign;
|
||||
_position.srcCallsign = srcCallsign;
|
||||
_position.dstCallsign = dstCallsign;
|
||||
_position.privacyLevel = 0;
|
||||
_position.status = "";
|
||||
_position.comment = "";
|
||||
|
||||
if (srcCallsign == null || dstCallsign == null) return;
|
||||
if (dstCallsign.length() < 6 || infoData.length < 8) return;
|
||||
|
||||
// read latitude
|
||||
// read symbol table
|
||||
boolean isCustom = false;
|
||||
char ns = 'S';
|
||||
char we = 'E';
|
||||
int longOffset = 0;
|
||||
int messageId = 0;
|
||||
byte[] dstCallsignBuf = dstCallsign.getBytes();
|
||||
StringBuilder latitude = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
char c = (char) dstCallsignBuf[i];
|
||||
if (c >= 'A' && c <= 'L') {
|
||||
if (i < 3) {
|
||||
isCustom = true;
|
||||
if (c != 'L') messageId |= 1;
|
||||
}
|
||||
if (c == 'K' || c == 'L') {
|
||||
// NOTE, using 0 instead of ' ' for position ambiguity
|
||||
c = '0';
|
||||
_position.privacyLevel += 1;
|
||||
} else
|
||||
c = (char) (c - 17);
|
||||
} else if (c >= 'P' && c <= 'Z') {
|
||||
if (i < 3) {
|
||||
messageId |= 1;
|
||||
} else if (i == 3) {
|
||||
ns = 'N';
|
||||
} else if (i == 4) {
|
||||
longOffset = 100;
|
||||
} else {
|
||||
we = 'W';
|
||||
}
|
||||
if (c == 'Z') {
|
||||
// NOTE, using 0 instead of ' ' for position ambiguity
|
||||
c = '0';
|
||||
_position.privacyLevel += 1;
|
||||
} else
|
||||
c = (char) (c - 32);
|
||||
}
|
||||
if (i < 2) messageId <<= 1;
|
||||
if (i == 4) latitude.append('.');
|
||||
latitude.append(c);
|
||||
}
|
||||
|
||||
_position.latitude = UnitTools.nmeaToDecimal(latitude.toString(), Character.toString(ns));
|
||||
_position.status = isCustom
|
||||
? _miceMessageReverseTypeMapCustom.get(messageId)
|
||||
: _miceMessageReverseTypeMapStd.get(messageId);
|
||||
|
||||
// read longitude
|
||||
// read symbol
|
||||
int d = ((int)infoData[0] - 28) + longOffset;
|
||||
if (d >= 180 && d <= 189) d -= 80;
|
||||
else if (d >= 190) d -= 190;
|
||||
|
||||
int m = ((int)infoData[1] - 28);
|
||||
if (m >= 60) m -= 60;
|
||||
|
||||
int h = ((int)infoData[2] - 28);
|
||||
|
||||
String longitude = String.format(Locale.US, "%03d%d.%d", d, m, h);
|
||||
_position.longitude = UnitTools.nmeaToDecimal(longitude, Character.toString(we));
|
||||
|
||||
// read course/speed
|
||||
// read altitude (could be anywhere inside the comment)
|
||||
int sp = 10 * ((int)infoData[3] - 28);
|
||||
int dcSp = ((int)infoData[4] - 28) / 10;
|
||||
int dcSe = (((int)infoData[4] - 28) % 10) * 100;
|
||||
int se = (int)infoData[5] - 28;
|
||||
|
||||
int speed = sp + dcSp;
|
||||
if (speed >= 800) speed -= 800;
|
||||
|
||||
int course = dcSe + se;
|
||||
if (course >= 400) course -= 400;
|
||||
|
||||
_position.hasBearing = true;
|
||||
_position.bearingDegrees = course;
|
||||
|
||||
_position.hasSpeed = true;
|
||||
_position.speedMetersPerSecond = UnitTools.knotsToMetersPerSecond(speed);
|
||||
|
||||
// read symbol table + symbol code
|
||||
_position.symbolCode = String.format(Locale.US, "%c%c", infoData[7], infoData[6]);
|
||||
|
||||
// read comment until the end
|
||||
// _isValid = true;
|
||||
_position.comment = new String(Arrays.copyOfRange(infoData, 8, infoData.length));
|
||||
|
||||
_isValid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -191,7 +303,7 @@ public class AprsDataPositionReportMicE implements AprsData {
|
|||
lonMinHun += 28;
|
||||
buffer.put(lonMinHun);
|
||||
|
||||
// encode speed/cou8rse
|
||||
// encode speed/course
|
||||
long speed = UnitTools.metersPerSecondToKnots(position.speedMetersPerSecond);
|
||||
byte speedHun = (byte)((speed / 10) + 28);
|
||||
buffer.put(speedHun);
|
||||
|
|
|
@ -7,6 +7,7 @@ import java.nio.ByteBuffer;
|
|||
|
||||
public class AprsDataTextMessage implements AprsData {
|
||||
|
||||
public String srcCallsign;
|
||||
public String dstCallsign;
|
||||
public String textMessage;
|
||||
|
||||
|
@ -32,20 +33,22 @@ public class AprsDataTextMessage implements AprsData {
|
|||
@Override
|
||||
public TextMessage toTextMessage() {
|
||||
TextMessage textMessage = new TextMessage();
|
||||
textMessage.src = this.srcCallsign;
|
||||
textMessage.dst = this.dstCallsign;
|
||||
textMessage.text = this.textMessage;
|
||||
return textMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBinary(byte[] infoData) {
|
||||
public void fromBinary(String srcCallsign, String dstCallsign, byte[] infoData) {
|
||||
_isValid = false;
|
||||
if (infoData.length < 10) return;
|
||||
this.srcCallsign = srcCallsign;
|
||||
ByteBuffer buffer = ByteBuffer.wrap(infoData);
|
||||
// callsign, trim ending spaces
|
||||
byte[] callsign = new byte[9];
|
||||
buffer.get(callsign);
|
||||
dstCallsign = new String(callsign).replaceAll("\\s+$", "");
|
||||
this.dstCallsign = new String(callsign).replaceAll("\\s+$", "");
|
||||
// ':' separator
|
||||
byte b = buffer.get();
|
||||
if (b != ':') return;
|
||||
|
|
|
@ -9,8 +9,8 @@ public class Position {
|
|||
public double latitude;
|
||||
public double longitude;
|
||||
public double altitudeMeters;
|
||||
public float bearingDegrees;
|
||||
public float speedMetersPerSecond;
|
||||
public double bearingDegrees;
|
||||
public double speedMetersPerSecond;
|
||||
public String status;
|
||||
public String comment;
|
||||
public String symbolCode;
|
||||
|
|
|
@ -4,4 +4,15 @@ public class TextTools {
|
|||
public static String addZeroWidthSpaces(String text) {
|
||||
return text.replaceAll(".(?!$)", "$0\u200b");
|
||||
}
|
||||
|
||||
public static int countChars(String text, char ch) {
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
if (text.charAt(i) == ch) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ public class UnitTools {
|
|||
double degreesIntegral = Math.abs(degrees) - degreesFractional;
|
||||
degreesFractional *= 60;
|
||||
degreesIntegral *= 100;
|
||||
long nmeaDouble = (long)Math.round((degreesIntegral + degreesFractional) * 100.0);
|
||||
long nmeaDouble = Math.round((degreesIntegral + degreesFractional) * 100.0);
|
||||
return String.format(
|
||||
Locale.US,
|
||||
isLatitude ? "%06d%c" : "%07d%c",
|
||||
|
@ -37,22 +37,22 @@ public class UnitTools {
|
|||
}
|
||||
|
||||
public static long metersToFeet(double meters) {
|
||||
return (long)(meters * 3.2808);
|
||||
return (long)(meters * 3.28084);
|
||||
}
|
||||
|
||||
public static double feetToMeters(long feet) {
|
||||
return feet * 0.3048;
|
||||
return feet / 3.28084;
|
||||
}
|
||||
|
||||
public static long metersPerSecondToKnots(float metersPerSecond) {
|
||||
public static long metersPerSecondToKnots(double metersPerSecond) {
|
||||
return (long)(metersPerSecond / 0.514444);
|
||||
}
|
||||
|
||||
public static float knotsToMetersPerSecond(long knots) {
|
||||
return (float) (knots * 0.5144444);
|
||||
public static double knotsToMetersPerSecond(long knots) {
|
||||
return knots * 0.5144444;
|
||||
}
|
||||
|
||||
public static double metersPerSecondToMilesPerHour(float metersPerSecond) {
|
||||
public static double metersPerSecondToMilesPerHour(double metersPerSecond) {
|
||||
return metersPerSecond * 2.23693629;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ public class Smart implements Tracker {
|
|||
private boolean isCornerPeggingTriggered(Position newPosition, Position oldPosition) {
|
||||
long timeDifferenceSeconds = UnitTools.millisToSeconds(newPosition.timestampEpochMs - oldPosition.timestampEpochMs);
|
||||
|
||||
float turnAngleDegrees = Math.abs(newPosition.bearingDegrees - oldPosition.bearingDegrees) % 360;
|
||||
double turnAngleDegrees = Math.abs(newPosition.bearingDegrees - oldPosition.bearingDegrees) % 360;
|
||||
turnAngleDegrees = turnAngleDegrees <= 180 ? turnAngleDegrees : 360 - turnAngleDegrees;
|
||||
|
||||
if (!newPosition.hasBearing || newPosition.speedMetersPerSecond == 0)
|
||||
|
|
Ładowanie…
Reference in New Issue