codec2_talkie/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/aprs/AprsDataPositionReportMicE....

345 wiersze
12 KiB
Java

package com.radio.codec2talkie.protocol.aprs;
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.TextTools;
import com.radio.codec2talkie.tools.UnitTools;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class AprsDataPositionReportMicE implements AprsData {
private String _dstCallsign;
private Position _position;
private byte[] _binary;
private boolean _isValid;
private final Map<String, Integer> _miceMessageTypeMap = new HashMap<String, Integer>() {{
// standard
put("off_duty", 0b111);
put("en_route", 0b110);
put("in_service", 0b101);
put("returning", 0b100);
put("committed", 0b011);
put("special", 0b010);
put("priority", 0b001);
// custom
put("custom_0", 0b111);
put("custom_1", 0b110);
put("custom_2", 0b101);
put("custom_3", 0b100);
put("custom_4", 0b011);
put("custom_5", 0b010);
put("custom_6", 0b001);
// emergency
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;
_position = position;
// latitude into the callsign
_dstCallsign = generateDestinationCallsign(position);
position.dstCallsign = _dstCallsign;
ByteBuffer buffer = ByteBuffer.allocate(256);
// identifier
buffer.put((byte)'`');
// longitude, speed/course into the information field
buffer.put(generateInfo(position));
// symbol code + symbol table id
byte[] symbol = position.symbolCode.getBytes();
buffer.put(symbol[1]);
buffer.put(symbol[0]);
// encode altitude if enabled
if (position.isAltitudeEnabled && position.hasAltitude) {
int datumAltitude = (int) (10000 + position.altitudeMeters);
int a1 = 33 + (datumAltitude / (91 * 91));
buffer.put((byte)a1);
int a2 = 33 + ((datumAltitude % (91 * 91)) / 91);
buffer.put((byte)a2);
int a3 = 33 + ((datumAltitude % (91 * 91)) % 91);
buffer.put((byte)a3);
buffer.put((byte)'}');
}
// comment
buffer.put(position.comment.getBytes());
// return
buffer.flip();
_binary = new byte[buffer.remaining()];
buffer.get(_binary);
_isValid = true;
}
@Override
public void fromTextMessage(TextMessage textMessage) {
_isValid = false;
}
@Override
public Position toPosition() {
return _position;
}
@Override
public TextMessage toTextMessage() {
return null;
}
@Override
public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) {
_isValid = false;
_position = new Position();
_dstCallsign = dstCallsign;
_position.srcCallsign = srcCallsign;
_position.dstCallsign = dstCallsign;
_position.digipath = digipath;
_position.privacyLevel = 0;
_position.status = "";
_position.comment = "";
if (srcCallsign == null || dstCallsign == null) return;
if (dstCallsign.length() < 6 || infoData.length < 8) return;
// read latitude
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
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%02d.%02d", d, m, h);
_position.longitude = UnitTools.nmeaToDecimal(longitude, Character.toString(we));
// read course/speed
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]);
if (infoData.length > 11 && infoData[11] == '}') {
_position.hasAltitude = true;
_position.altitudeMeters = ((infoData[8] - 33) * 91 * 91 + (infoData[9] - 33) * 91 + (infoData[10] - 33)) - 10000;
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 12, infoData.length), StandardCharsets.UTF_8));
} else {
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 8, infoData.length), StandardCharsets.UTF_8));
}
_position.maidenHead = UnitTools.decimalToMaidenhead(_position.latitude, _position.longitude);
_isValid = true;
}
@Override
public byte[] toBinary() {
return _binary;
}
@Override
public boolean isValid() {
return _isValid;
}
private String generateDestinationCallsign(Position position) {
byte[] latitude = AprsTools.applyPrivacyOnUncompressedNmeaCoordinate(
UnitTools.decimalToNmea(position.latitude, true),
position.privacyLevel).getBytes();
byte[] longitude = UnitTools.decimalToNmea(position.longitude, false).getBytes();
// get Mic-E status bit mapping
boolean isCustom = position.status.startsWith("custom");
Integer miceMessageTypeEncoding = _miceMessageTypeMap.get(position.status);
miceMessageTypeEncoding = miceMessageTypeEncoding == null
? 0b111
: miceMessageTypeEncoding;
// decide if Mic-E "longitude offset" must be added
byte lonDeg = (byte)(Integer.parseInt(new String(longitude).substring(0, 3)));
int longOffset = (lonDeg >= 10 && lonDeg < 100) ? 0 : 1;
// generate Mic-E position and flags into the destination callsign, add N, W indicators
ByteBuffer buffer = ByteBuffer.allocate(16);
// 0, mic-e status
buffer.put((byte)(latitude[0] + (isCustom ? 17 : 32) * ((miceMessageTypeEncoding >> 2) & 1)));
// 1, mic-e status
buffer.put((byte)(latitude[1] + (isCustom ? 17 : 32) * ((miceMessageTypeEncoding >> 1) & 1)));
// 2, mic-e status
if (latitude[2] == (byte)' ')
buffer.put((miceMessageTypeEncoding & 1) == 1 ? isCustom ? (byte)'K' : (byte)'Z' : (byte)'L');
else
buffer.put((byte)(latitude[2] + (isCustom ? 17 : 32) * ((miceMessageTypeEncoding) & 1)));
// 3, north/south
if (latitude[3] == (byte)' ')
buffer.put(latitude[6] == 'N' ? (byte)'Z' : (byte)'L');
else
buffer.put((byte)(latitude[3] + 32 * (latitude[6] == 'N' ? 1 : 0)));
// 4, long offset
if (latitude[4] == (byte)' ')
buffer.put((longOffset & 1) == 1 ? (byte)'Z' : (byte)'L');
else
buffer.put((byte)(latitude[4] + 32 * (longOffset & 1)));
// 5, west/east
if (latitude[5] == (byte)' ')
buffer.put(longitude[7] == 'W' ? (byte)'Z' : (byte)'L');
else
buffer.put((byte) (latitude[5] + 32 * (longitude[7] == 'W' ? 1 : 0)));
// include E-Mic digipath if specified (!= 0)
if (position.extDigipathSsid > 0) {
buffer.put((byte)'-');
buffer.put(Integer.toString(position.extDigipathSsid).getBytes());
}
// return
buffer.flip();
byte[] callsign = new byte[buffer.remaining()];
buffer.get(callsign);
return new String(callsign);
}
private byte[] generateInfo(Position position) {
byte[] longitude = UnitTools.decimalToNmea(position.longitude, false).getBytes();
ByteBuffer buffer = ByteBuffer.allocate(16);
String longStr = new String(longitude);
// encode Mic-E longitude into the information field
byte lonDeg = (byte)(Integer.parseInt(longStr.substring(0, 3)));
if (lonDeg >= 0 && lonDeg <= 9) lonDeg += (90 + 28);
else if (lonDeg >= 10 && lonDeg <= 99) lonDeg += 28;
else if (lonDeg >= 100 && lonDeg <= 109) lonDeg += 8;
else lonDeg -= (44 + 28);
buffer.put(lonDeg);
byte lonMin = (byte)(Integer.parseInt(longStr.substring(3, 5)));
if (lonMin >= 0 && lonMin <= 9) lonMin += (60 + 28);
else lonMin += 28;
buffer.put(lonMin);
byte lonMinHun = (byte)(Integer.parseInt(longStr.substring(5, 7)));
lonMinHun += 28;
buffer.put(lonMinHun);
// encode speed/course
long speed = UnitTools.metersPerSecondToKnots(position.speedMetersPerSecond);
byte speedHun = (byte)((speed / 10) + 28);
buffer.put(speedHun);
byte speedTen = (byte)(speed % 10);
byte courseHun = (byte)(position.bearingDegrees / 100.0);
buffer.put((byte)(speedTen * 10 + courseHun + 28));
// encode bearing
buffer.put((byte)(position.bearingDegrees % 100.0 + 28));
// return
buffer.flip();
byte[] info = new byte[buffer.remaining()];
buffer.get(info);
return info;
}
}