generated from Grigo/AndroidTemplate
added more fields
This commit is contained in:
@@ -19,7 +19,7 @@ public final class RadioSnapshot {
|
||||
public final String frame;
|
||||
public final Double frequencyMhz;
|
||||
public final Integer sf;
|
||||
public final Integer bwKhz;
|
||||
public final Double bwKhz;
|
||||
public final Double powerDbm;
|
||||
public final Double rssiDbm;
|
||||
public final Double snrDb;
|
||||
@@ -30,6 +30,18 @@ public final class RadioSnapshot {
|
||||
public final Double rxPktPerS;
|
||||
public final Double perPercent;
|
||||
public final Double rxQualityPercent;
|
||||
public final String codeRate;
|
||||
public final Integer preambleLength;
|
||||
public final String lowDataRateOpt;
|
||||
public final Boolean crcEnabled;
|
||||
public final Integer payloadLengthBytes;
|
||||
public final Double txTimeoutMs;
|
||||
public final Integer packetReceive;
|
||||
public final Integer packetTotal;
|
||||
public final Integer packetError;
|
||||
public final Integer crcError;
|
||||
public final Integer preambleDetected;
|
||||
public final Integer headerValid;
|
||||
public final Map<String, String> extraFields;
|
||||
|
||||
public RadioSnapshot(
|
||||
@@ -37,7 +49,7 @@ public final class RadioSnapshot {
|
||||
String frame,
|
||||
Double frequencyMhz,
|
||||
Integer sf,
|
||||
Integer bwKhz,
|
||||
Double bwKhz,
|
||||
Double powerDbm,
|
||||
Double rssiDbm,
|
||||
Double snrDb,
|
||||
@@ -48,6 +60,18 @@ public final class RadioSnapshot {
|
||||
Double rxPktPerS,
|
||||
Double perPercent,
|
||||
Double rxQualityPercent,
|
||||
String codeRate,
|
||||
Integer preambleLength,
|
||||
String lowDataRateOpt,
|
||||
Boolean crcEnabled,
|
||||
Integer payloadLengthBytes,
|
||||
Double txTimeoutMs,
|
||||
Integer packetReceive,
|
||||
Integer packetTotal,
|
||||
Integer packetError,
|
||||
Integer crcError,
|
||||
Integer preambleDetected,
|
||||
Integer headerValid,
|
||||
Map<String, String> extraFields
|
||||
) {
|
||||
this.role = role;
|
||||
@@ -65,12 +89,26 @@ public final class RadioSnapshot {
|
||||
this.rxPktPerS = rxPktPerS;
|
||||
this.perPercent = perPercent;
|
||||
this.rxQualityPercent = rxQualityPercent;
|
||||
this.codeRate = codeRate;
|
||||
this.preambleLength = preambleLength;
|
||||
this.lowDataRateOpt = lowDataRateOpt;
|
||||
this.crcEnabled = crcEnabled;
|
||||
this.payloadLengthBytes = payloadLengthBytes;
|
||||
this.txTimeoutMs = txTimeoutMs;
|
||||
this.packetReceive = packetReceive;
|
||||
this.packetTotal = packetTotal;
|
||||
this.packetError = packetError;
|
||||
this.crcError = crcError;
|
||||
this.preambleDetected = preambleDetected;
|
||||
this.headerValid = headerValid;
|
||||
this.extraFields = extraFields != null ? extraFields : Map.of();
|
||||
}
|
||||
|
||||
public static RadioSnapshot empty() {
|
||||
return new RadioSnapshot(null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, Map.of());
|
||||
null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
Map.of());
|
||||
}
|
||||
|
||||
public static RadioSnapshot fromMeta(String metaJson, String roleFallback, Double rssiFallback) {
|
||||
@@ -78,7 +116,9 @@ public final class RadioSnapshot {
|
||||
RadioSnapshot snap = empty();
|
||||
if (roleFallback != null || rssiFallback != null) {
|
||||
return new RadioSnapshot(roleFallback, null, null, null, null, null,
|
||||
rssiFallback, null, null, null, null, null, null, null, null, Map.of());
|
||||
rssiFallback, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
Map.of());
|
||||
}
|
||||
return snap;
|
||||
}
|
||||
@@ -108,7 +148,7 @@ public final class RadioSnapshot {
|
||||
text(o, "frame"),
|
||||
hzToMhz(lng(o, "frequency_hz")),
|
||||
integer(o, "spreading_factor"),
|
||||
integer(o, "bandwidth_khz"),
|
||||
dbl(o, "bandwidth_khz"),
|
||||
dbl(o, "power_dbm"),
|
||||
rssi,
|
||||
dbl(o, "snr_db"),
|
||||
@@ -119,11 +159,25 @@ public final class RadioSnapshot {
|
||||
dbl(o, "rx_pkt_per_s"),
|
||||
dbl(o, "per_percent"),
|
||||
dbl(o, "rx_quality_percent"),
|
||||
text(o, "code_rate"),
|
||||
integer(o, "preamble_length"),
|
||||
text(o, "low_data_rate_opt"),
|
||||
bool(o, "crc_enabled"),
|
||||
integer(o, "payload_length_bytes"),
|
||||
dbl(o, "tx_timeout_ms"),
|
||||
integer(o, "packet_receive"),
|
||||
integer(o, "packet_total"),
|
||||
integer(o, "packet_error"),
|
||||
integer(o, "crc_error"),
|
||||
integer(o, "preamble_detected"),
|
||||
integer(o, "header_valid"),
|
||||
extra
|
||||
);
|
||||
} catch (Exception ignored) {
|
||||
return new RadioSnapshot(roleFallback, null, null, null, null, null,
|
||||
rssiFallback, null, null, null, null, null, null, null, null, Map.of());
|
||||
rssiFallback, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
Map.of());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +206,14 @@ public final class RadioSnapshot {
|
||||
cmp(changed, "sf", sf, prev.sf);
|
||||
cmp(changed, "bw", bwKhz, prev.bwKhz);
|
||||
cmp(changed, "power", powerDbm, prev.powerDbm);
|
||||
cmp(changed, "packetReceive", packetReceive, prev.packetReceive);
|
||||
cmp(changed, "packetTotal", packetTotal, prev.packetTotal);
|
||||
cmp(changed, "packetError", packetError, prev.packetError);
|
||||
cmp(changed, "crcError", crcError, prev.crcError);
|
||||
cmp(changed, "preambleDetected", preambleDetected, prev.preambleDetected);
|
||||
cmp(changed, "headerValid", headerValid, prev.headerValid);
|
||||
cmp(changed, "codeRate", codeRate, prev.codeRate);
|
||||
cmp(changed, "crc", crcEnabled, prev.crcEnabled);
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -167,8 +229,12 @@ public final class RadioSnapshot {
|
||||
|| n.contains("frequency") || n.equals("power") || n.equals("rssi")
|
||||
|| n.equals("snr") || n.contains("spreading") || n.contains("bandwidth")
|
||||
|| n.equals("packet") || n.contains("packet number") || n.equals("payload")
|
||||
|| n.contains("packet receive") || n.contains("packet total") || n.contains("packet error")
|
||||
|| n.contains("crc error") || n.contains("preamble detected") || n.contains("header valid")
|
||||
|| n.contains("on air") || n.contains("tx speed") || n.contains("rx speed")
|
||||
|| n.equals("per") || n.contains("rx quality");
|
||||
|| n.equals("per") || n.contains("rx quality") || n.contains("tx timeout")
|
||||
|| n.contains("code rate") || n.contains("preamble length")
|
||||
|| n.contains("low data rate") || n.equals("crc") || n.contains("payload length");
|
||||
}
|
||||
|
||||
private static String text(JsonObject o, String key) {
|
||||
@@ -186,6 +252,11 @@ public final class RadioSnapshot {
|
||||
return e != null && e.isJsonPrimitive() ? e.getAsDouble() : null;
|
||||
}
|
||||
|
||||
private static Boolean bool(JsonObject o, String key) {
|
||||
JsonElement e = o.get(key);
|
||||
return e != null && e.isJsonPrimitive() ? e.getAsBoolean() : null;
|
||||
}
|
||||
|
||||
private static Long lng(JsonObject o, String key) {
|
||||
JsonElement e = o.get(key);
|
||||
return e != null && e.isJsonPrimitive() ? e.getAsLong() : null;
|
||||
|
||||
@@ -40,6 +40,12 @@ public final class LoraStatsFormatter {
|
||||
appendLine(sb, "Пакет", fmtInt(s.packet), "packet", changed);
|
||||
appendLine(sb, "Payload", s.payload, "payload", changed);
|
||||
appendLine(sb, "PER", fmtSuffix(s.perPercent, " %"), "per", changed);
|
||||
appendLine(sb, "Принято", fmtInt(s.packetReceive), "packetReceive", changed);
|
||||
appendLine(sb, "Всего пакетов", fmtInt(s.packetTotal), "packetTotal", changed);
|
||||
appendLine(sb, "Ошибки пакетов", fmtInt(s.packetError), "packetError", changed);
|
||||
appendLine(sb, "CRC Error", fmtInt(s.crcError), "crcError", changed);
|
||||
appendLine(sb, "Preamble Det.", fmtInt(s.preambleDetected), "preambleDetected", changed);
|
||||
appendLine(sb, "Header Valid", fmtInt(s.headerValid), "headerValid", changed);
|
||||
appendLine(sb, "TX Speed", fmtSuffix(s.txPktPerS, " pkt/s"), "txSpeed", changed);
|
||||
appendLine(sb, "RX Speed", fmtSuffix(s.rxPktPerS, " pkt/s"), "rxSpeed", changed);
|
||||
for (Map.Entry<String, String> e : s.extraFields.entrySet()) {
|
||||
@@ -58,8 +64,14 @@ public final class LoraStatsFormatter {
|
||||
}
|
||||
appendLine(sb, "Частота", fmtSuffix(s.frequencyMhz, " MHz"), "frequency", changed);
|
||||
appendLine(sb, "SF", fmtInt(s.sf), "sf", changed);
|
||||
appendLine(sb, "BW", fmtSuffix(s.bwKhz, " kHz"), "bw", changed);
|
||||
appendLine(sb, "BW", fmtBw(s.bwKhz), "bw", changed);
|
||||
appendLine(sb, "Мощность TX", fmtDbm(s.powerDbm), "power", changed);
|
||||
appendLine(sb, "Code Rate", s.codeRate, "codeRate", changed);
|
||||
appendLine(sb, "Preamble Len", fmtInt(s.preambleLength), "preambleLength", changed);
|
||||
appendLine(sb, "Low DR Opt", s.lowDataRateOpt, "lowDataRateOpt", changed);
|
||||
appendLine(sb, "CRC", fmtCrc(s.crcEnabled), "crc", changed);
|
||||
appendLine(sb, "Payload len", fmtSuffix(s.payloadLengthBytes, " byte"), "payloadLength", changed);
|
||||
appendLine(sb, "TX Timeout", fmtSuffix(s.txTimeoutMs, " ms"), "txTimeout", changed);
|
||||
appendLine(sb, "On Air", fmtSuffix(s.onAirMs, " ms"), "onAir", changed);
|
||||
return sb.toString().trim();
|
||||
}
|
||||
@@ -111,11 +123,22 @@ public final class LoraStatsFormatter {
|
||||
return v != null ? String.valueOf(v) : null;
|
||||
}
|
||||
|
||||
private static String fmtSuffix(Double v, String suffix) {
|
||||
return v != null ? String.format(Locale.US, "%s%s", v, suffix) : null;
|
||||
private static String fmtBw(Double v) {
|
||||
return v != null ? String.format(Locale.US, "%.2f kHz", v) : null;
|
||||
}
|
||||
|
||||
private static String fmtCrc(Boolean enabled) {
|
||||
if (enabled == null) {
|
||||
return null;
|
||||
}
|
||||
return enabled ? "On" : "Off";
|
||||
}
|
||||
|
||||
private static String fmtSuffix(Integer v, String suffix) {
|
||||
return v != null ? v + suffix : null;
|
||||
}
|
||||
|
||||
private static String fmtSuffix(Double v, String suffix) {
|
||||
return v != null ? String.format(Locale.US, "%s%s", v, suffix) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,26 @@ public class StatsExtractor {
|
||||
private static final Pattern SNR = Pattern.compile("SNR\\s*:\\s*(-?\\d+(?:\\.\\d+)?)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern FREQUENCY = Pattern.compile("Frequency\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern SPREADING = Pattern.compile("Spreading Factor\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern BANDWIDTH = Pattern.compile("Bandwidth\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET = Pattern.compile("Packet\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern BANDWIDTH = Pattern.compile("Bandwidth\\s*:\\s*([\\d.]+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET_TX = Pattern.compile("(?m)^\\s*Packet\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET_NUMBER = Pattern.compile("Packet Number\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET_RECEIVE = Pattern.compile("Packet Receive\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET_TOTAL = Pattern.compile("Packet Total\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PACKET_ERROR = Pattern.compile("Packet Error\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern CRC_ERROR = Pattern.compile("CRC Error\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PREAMBLE_DETECTED = Pattern.compile(
|
||||
"Preamble Detected\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern HEADER_VALID = Pattern.compile("Header Valid\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern CODE_RATE = Pattern.compile("Code Rate\\s*:\\s*(\\S+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PREAMBLE_LENGTH = Pattern.compile(
|
||||
"Preamble Length\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern LOW_DATA_RATE = Pattern.compile(
|
||||
"Low Data Rate Opt\\s*:\\s*(\\S+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern CRC = Pattern.compile("CRC\\s*:\\s*(On|Off)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PAYLOAD_LENGTH = Pattern.compile(
|
||||
"Payload length\\s*:\\s*(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern TX_TIMEOUT = Pattern.compile(
|
||||
"TX Timeout\\s*:\\s*([\\d.]+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern PAYLOAD = Pattern.compile("Payload\\s*:\\s*(.+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ON_AIR = Pattern.compile("On Air\\s*:\\s*([\\d.]+)", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern TX_SPEED = Pattern.compile("TX Speed\\s*:\\s*([\\d.]+)", Pattern.CASE_INSENSITIVE);
|
||||
@@ -89,10 +106,10 @@ public class StatsExtractor {
|
||||
|
||||
putLong(meta, "frequency_hz", matchLong(FREQUENCY, normalized));
|
||||
putInt(meta, "spreading_factor", matchInt(SPREADING, normalized));
|
||||
putInt(meta, "bandwidth_khz", matchInt(BANDWIDTH, normalized));
|
||||
putDouble(meta, "bandwidth_khz", matchDouble(BANDWIDTH, normalized));
|
||||
Integer packet = matchInt(PACKET_NUMBER, normalized);
|
||||
if (packet == null) {
|
||||
packet = matchInt(PACKET, normalized);
|
||||
packet = matchInt(PACKET_TX, normalized);
|
||||
}
|
||||
putInt(meta, "packet", packet);
|
||||
putString(meta, "payload", matchString(PAYLOAD, normalized));
|
||||
@@ -101,6 +118,18 @@ public class StatsExtractor {
|
||||
putDouble(meta, "rx_pkt_per_s", matchDouble(RX_SPEED, normalized));
|
||||
putDouble(meta, "per_percent", matchDouble(PER, normalized));
|
||||
putDouble(meta, "rx_quality_percent", matchDouble(RX_QUALITY, normalized));
|
||||
putString(meta, "code_rate", matchString(CODE_RATE, normalized));
|
||||
putInt(meta, "preamble_length", matchInt(PREAMBLE_LENGTH, normalized));
|
||||
putString(meta, "low_data_rate_opt", matchString(LOW_DATA_RATE, normalized));
|
||||
putBool(meta, "crc_enabled", matchBool(CRC, normalized));
|
||||
putInt(meta, "payload_length_bytes", matchInt(PAYLOAD_LENGTH, normalized));
|
||||
putDouble(meta, "tx_timeout_ms", matchDouble(TX_TIMEOUT, normalized));
|
||||
putInt(meta, "packet_receive", matchInt(PACKET_RECEIVE, normalized));
|
||||
putInt(meta, "packet_total", matchInt(PACKET_TOTAL, normalized));
|
||||
putInt(meta, "packet_error", matchInt(PACKET_ERROR, normalized));
|
||||
putInt(meta, "crc_error", matchInt(CRC_ERROR, normalized));
|
||||
putInt(meta, "preamble_detected", matchInt(PREAMBLE_DETECTED, normalized));
|
||||
putInt(meta, "header_valid", matchInt(HEADER_VALID, normalized));
|
||||
|
||||
if (!fields.isEmpty()) {
|
||||
meta.put("fields", fields);
|
||||
@@ -144,8 +173,12 @@ public class StatsExtractor {
|
||||
return n.equals("frequency") || n.equals("power") || n.equals("rssi")
|
||||
|| n.equals("snr") || n.contains("spreading factor") || n.equals("bandwidth")
|
||||
|| n.equals("packet") || n.contains("packet number") || n.equals("payload")
|
||||
|| n.contains("packet receive") || n.contains("packet total") || n.contains("packet error")
|
||||
|| n.contains("crc error") || n.contains("preamble detected") || n.contains("header valid")
|
||||
|| n.contains("on air") || n.contains("tx speed") || n.contains("rx speed")
|
||||
|| n.equals("per") || n.contains("rx quality") || n.equals("timeout");
|
||||
|| n.equals("per") || n.contains("rx quality") || n.contains("tx timeout")
|
||||
|| n.contains("code rate") || n.contains("preamble length")
|
||||
|| n.contains("low data rate") || n.equals("crc") || n.contains("payload length");
|
||||
}
|
||||
|
||||
private static ExtractedStats empty(String frame) {
|
||||
@@ -208,6 +241,20 @@ public class StatsExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void putBool(Map<String, Object> meta, String key, Boolean value) {
|
||||
if (value != null) {
|
||||
meta.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean matchBool(Pattern pattern, String text) {
|
||||
Matcher m = pattern.matcher(text);
|
||||
if (!m.find()) {
|
||||
return null;
|
||||
}
|
||||
return "on".equalsIgnoreCase(m.group(1).trim());
|
||||
}
|
||||
|
||||
private static Double matchDouble(Pattern pattern, String text) {
|
||||
Matcher m = pattern.matcher(text);
|
||||
if (m.find()) {
|
||||
|
||||
@@ -92,14 +92,30 @@ public class RadioComparePanel extends LinearLayout {
|
||||
addRow(table, "Пакет", fmtInt(tx.packet), fmtInt(rx.packet), "packet", changedTx, changedRx);
|
||||
addRow(table, "Payload", str(tx.payload), str(rx.payload), "payload", changedTx, changedRx);
|
||||
addRow(table, "PER", fmtSuffix(tx.perPercent, " %"), fmtSuffix(rx.perPercent, " %"), "per", changedTx, changedRx);
|
||||
addRow(table, "Принято", fmtInt(tx.packetReceive), fmtInt(rx.packetReceive), "packetReceive", changedTx, changedRx);
|
||||
addRow(table, "Всего", fmtInt(tx.packetTotal), fmtInt(rx.packetTotal), "packetTotal", changedTx, changedRx);
|
||||
addRow(table, "Ошибки", fmtInt(tx.packetError), fmtInt(rx.packetError), "packetError", changedTx, changedRx);
|
||||
addRow(table, "CRC err", fmtInt(tx.crcError), fmtInt(rx.crcError), "crcError", changedTx, changedRx);
|
||||
addRow(table, "Preamble", fmtInt(tx.preambleDetected), fmtInt(rx.preambleDetected),
|
||||
"preambleDetected", changedTx, changedRx);
|
||||
addRow(table, "Header OK", fmtInt(tx.headerValid), fmtInt(rx.headerValid), "headerValid", changedTx, changedRx);
|
||||
addRow(table, "TX spd", fmtSuffix(tx.txPktPerS, " p/s"), fmtSuffix(rx.txPktPerS, " p/s"), "txSpeed", changedTx, changedRx);
|
||||
addRow(table, "RX spd", fmtSuffix(tx.rxPktPerS, " p/s"), fmtSuffix(rx.rxPktPerS, " p/s"), "rxSpeed", changedTx, changedRx);
|
||||
} else {
|
||||
addRow(table, "Роль", LoraStatsFormatter.roleLabel(tx.role), LoraStatsFormatter.roleLabel(rx.role), "role", changedTx, changedRx);
|
||||
addRow(table, "Частота", fmtMhz(tx.frequencyMhz), fmtMhz(rx.frequencyMhz), "frequency", changedTx, changedRx);
|
||||
addRow(table, "SF", fmtInt(tx.sf), fmtInt(rx.sf), "sf", changedTx, changedRx);
|
||||
addRow(table, "BW", fmtSuffixInt(tx.bwKhz, " kHz"), fmtSuffixInt(rx.bwKhz, " kHz"), "bw", changedTx, changedRx);
|
||||
addRow(table, "BW", fmtBw(tx.bwKhz), fmtBw(rx.bwKhz), "bw", changedTx, changedRx);
|
||||
addRow(table, "Мощн.", fmtDbm(tx.powerDbm), fmtDbm(rx.powerDbm), "power", changedTx, changedRx);
|
||||
addRow(table, "Code Rate", str(tx.codeRate), str(rx.codeRate), "codeRate", changedTx, changedRx);
|
||||
addRow(table, "Preamble", fmtInt(tx.preambleLength), fmtInt(rx.preambleLength), "preambleLength", changedTx, changedRx);
|
||||
addRow(table, "LDR", str(tx.lowDataRateOpt), str(rx.lowDataRateOpt), "lowDataRateOpt", changedTx, changedRx);
|
||||
addRow(table, "CRC", fmtCrc(tx.crcEnabled), fmtCrc(rx.crcEnabled), "crc", changedTx, changedRx);
|
||||
addRow(table, "Payl.len", fmtSuffixInt(tx.payloadLengthBytes, " B"), fmtSuffixInt(rx.payloadLengthBytes, " B"),
|
||||
"payloadLength", changedTx, changedRx);
|
||||
addRow(table, "TX Timeout", fmtSuffix(tx.txTimeoutMs, " ms"), fmtSuffix(rx.txTimeoutMs, " ms"),
|
||||
"txTimeout", changedTx, changedRx);
|
||||
addRow(table, "On Air", fmtSuffix(tx.onAirMs, " ms"), fmtSuffix(rx.onAirMs, " ms"), "onAir", changedTx, changedRx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +199,17 @@ public class RadioComparePanel extends LinearLayout {
|
||||
return v != null ? v + suffix : "—";
|
||||
}
|
||||
|
||||
private static String fmtBw(Double v) {
|
||||
return v != null ? String.format(Locale.US, "%.2f kHz", v) : "—";
|
||||
}
|
||||
|
||||
private static String fmtCrc(Boolean enabled) {
|
||||
if (enabled == null) {
|
||||
return "—";
|
||||
}
|
||||
return enabled ? "On" : "Off";
|
||||
}
|
||||
|
||||
private static String fmtSuffixInt(Integer v, String suffix) {
|
||||
return v != null ? v + suffix : "—";
|
||||
}
|
||||
|
||||
@@ -54,11 +54,10 @@ public class LoraFrameExtractTest {
|
||||
public void parsesAllLabeledLinesFromSendScreen() {
|
||||
StatsExtractor extractor = StatsExtractor.withDefaults();
|
||||
StatsExtractor.ExtractedStats stats = extractor.extract(FULL_SEND);
|
||||
assertTrue(stats.metaJson.contains("\"fields\""));
|
||||
assertTrue(stats.metaJson.contains("Frequency"));
|
||||
assertTrue(stats.metaJson.contains("Spreading Factor"));
|
||||
assertTrue(stats.metaJson.contains("Packet"));
|
||||
assertTrue(stats.metaJson.contains("Payload"));
|
||||
assertTrue(stats.metaJson.contains("\"code_rate\":\"4/5\""));
|
||||
assertTrue(stats.metaJson.contains("\"spreading_factor\":12"));
|
||||
assertTrue(stats.metaJson.contains("\"packet\":304"));
|
||||
assertTrue(stats.metaJson.contains("Test TX!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,7 +82,8 @@ public class LoraFrameExtractTest {
|
||||
assertEquals(StatsExtractor.ROLE_RX, stats.role);
|
||||
assertEquals(-78.0, stats.rssi, 0.01);
|
||||
assertEquals(10.5, stats.snrDb, 0.01);
|
||||
assertTrue(stats.metaJson.contains("\"fields\""));
|
||||
assertTrue(stats.metaJson.contains("\"packet\":0"));
|
||||
assertTrue(stats.metaJson.contains("\"rx_pkt_per_s\":0.45"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,6 +96,73 @@ public class LoraFrameExtractTest {
|
||||
assertTrue(!stats.metaJson.contains("RX Quality"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsesRxCountersAndCrcErrors() {
|
||||
StatsExtractor extractor = StatsExtractor.withDefaults();
|
||||
String frame = """
|
||||
RECEIVE
|
||||
Frequency: 433500000 Hz
|
||||
Power: 0 dBm
|
||||
Spreading Factor: 5
|
||||
Bandwidth: 7.81 kHz
|
||||
Code Rate: 4/6
|
||||
Preamble Length: 8
|
||||
Low Data Rate Opt: Off
|
||||
CRC: Off
|
||||
Payload length: 32 byte
|
||||
On Air: 427.14 ms, 2.34 pkt/c
|
||||
Packet Number: 0
|
||||
Payload: test
|
||||
RSSI: -78
|
||||
SNR: 10.5
|
||||
RX Speed: 0.45 pkt/s, 120 bit/s
|
||||
Packet Receive: 12
|
||||
Packet Total: 100
|
||||
Packet Error: 3
|
||||
CRC Error: 2
|
||||
PER: 3.00 %
|
||||
Preamble Detected: 2
|
||||
Header Valid: 1
|
||||
RX Quality: 87 %
|
||||
""";
|
||||
StatsExtractor.ExtractedStats stats = extractor.extract(frame);
|
||||
assertTrue(stats.metaJson.contains("\"crc_error\":2"));
|
||||
assertTrue(stats.metaJson.contains("\"packet_error\":3"));
|
||||
assertTrue(stats.metaJson.contains("\"packet_total\":100"));
|
||||
assertTrue(stats.metaJson.contains("\"packet_receive\":12"));
|
||||
assertTrue(stats.metaJson.contains("\"bandwidth_khz\":7.81"));
|
||||
assertTrue(stats.metaJson.contains("\"crc_enabled\":false"));
|
||||
assertTrue(stats.metaJson.contains("\"code_rate\":\"4/6\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsesSendCrcAndConfig() {
|
||||
StatsExtractor extractor = StatsExtractor.withDefaults();
|
||||
String frame = """
|
||||
SEND
|
||||
Frequency: 433500000 Hz
|
||||
Power: 0 dBm
|
||||
Spreading Factor: 5
|
||||
Bandwidth: 125 kHz
|
||||
Code Rate: 4/5
|
||||
Preamble Length: 8
|
||||
Low Data Rate Opt: Off
|
||||
CRC: On
|
||||
Payload length: 32 byte
|
||||
On Air: 23.10 ms, 43.28 pkt/c
|
||||
Packet: 3816
|
||||
Payload: Test TX!
|
||||
TX Timeout: 0 ms
|
||||
TX Speed: 32.06 pkt/s, 8206 bit/s
|
||||
""";
|
||||
StatsExtractor.ExtractedStats stats = extractor.extract(frame);
|
||||
assertTrue(stats.metaJson.contains("\"packet\":3816"));
|
||||
assertTrue(stats.metaJson.contains("\"crc_enabled\":true"));
|
||||
assertTrue(stats.metaJson.contains("\"payload_length_bytes\":32"));
|
||||
assertTrue(stats.metaJson.contains("\"tx_timeout_ms\":0"));
|
||||
assertTrue(stats.metaJson.contains("\"preamble_length\":8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitsTwoFramesByReceiveHeaderWithoutEsc() {
|
||||
List<String> frames = new ArrayList<>();
|
||||
|
||||
@@ -379,7 +379,7 @@ def health():
|
||||
return {
|
||||
"ok": status["db_ok"],
|
||||
"ts": time.time(),
|
||||
"api_build": "2026-06-16f",
|
||||
"api_build": "2026-06-16g",
|
||||
**status,
|
||||
**elevation_status(),
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
{ position: 'topright', collapsed: true }
|
||||
).addTo(map);
|
||||
|
||||
const API_BUILD = '2026-06-16f';
|
||||
const API_BUILD = '2026-06-16g';
|
||||
|
||||
const markers = {};
|
||||
let selectedId = null;
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
const KNOWN_LABELS = new Set([
|
||||
'send', 'receive', 'frequency', 'power', 'rssi', 'snr',
|
||||
'spreading factor', 'bandwidth', 'packet', 'packet number', 'payload',
|
||||
'on air', 'tx speed', 'rx speed', 'per', 'rx quality'
|
||||
'packet receive', 'packet total', 'packet error', 'crc error',
|
||||
'preamble detected', 'header valid',
|
||||
'on air', 'tx speed', 'rx speed', 'per', 'rx quality',
|
||||
'code rate', 'preamble length', 'low data rate', 'crc', 'payload length', 'tx timeout'
|
||||
]);
|
||||
|
||||
function roleLabel(role) {
|
||||
@@ -22,6 +25,15 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function fmtCrc(enabled) {
|
||||
if (enabled == null) return '—';
|
||||
return enabled ? 'On' : 'Off';
|
||||
}
|
||||
|
||||
function fmtBw(khz) {
|
||||
return khz != null ? `${Number(khz).toFixed(2)} kHz` : '—';
|
||||
}
|
||||
|
||||
function parseRadioSnapshot(meta, roleFallback, rssiFallback) {
|
||||
const snap = {
|
||||
role: roleFallback || null,
|
||||
@@ -39,6 +51,18 @@
|
||||
rxPktPerS: null,
|
||||
perPercent: null,
|
||||
rxQualityPercent: null,
|
||||
codeRate: null,
|
||||
preambleLength: null,
|
||||
lowDataRateOpt: null,
|
||||
crcEnabled: null,
|
||||
payloadLengthBytes: null,
|
||||
txTimeoutMs: null,
|
||||
packetReceive: null,
|
||||
packetTotal: null,
|
||||
packetError: null,
|
||||
crcError: null,
|
||||
preambleDetected: null,
|
||||
headerValid: null,
|
||||
extraFields: {}
|
||||
};
|
||||
if (!meta) return snap;
|
||||
@@ -62,6 +86,18 @@
|
||||
if (o.per_percent != null) snap.perPercent = Number(o.per_percent);
|
||||
if (o.rx_quality_percent != null) snap.rxQualityPercent = Number(o.rx_quality_percent);
|
||||
if (o.stats_at != null) snap.statsAt = Number(o.stats_at);
|
||||
if (o.code_rate != null) snap.codeRate = String(o.code_rate);
|
||||
if (o.preamble_length != null) snap.preambleLength = Number(o.preamble_length);
|
||||
if (o.low_data_rate_opt != null) snap.lowDataRateOpt = String(o.low_data_rate_opt);
|
||||
if (o.crc_enabled != null) snap.crcEnabled = Boolean(o.crc_enabled);
|
||||
if (o.payload_length_bytes != null) snap.payloadLengthBytes = Number(o.payload_length_bytes);
|
||||
if (o.tx_timeout_ms != null) snap.txTimeoutMs = Number(o.tx_timeout_ms);
|
||||
if (o.packet_receive != null) snap.packetReceive = Number(o.packet_receive);
|
||||
if (o.packet_total != null) snap.packetTotal = Number(o.packet_total);
|
||||
if (o.packet_error != null) snap.packetError = Number(o.packet_error);
|
||||
if (o.crc_error != null) snap.crcError = Number(o.crc_error);
|
||||
if (o.preamble_detected != null) snap.preambleDetected = Number(o.preamble_detected);
|
||||
if (o.header_valid != null) snap.headerValid = Number(o.header_valid);
|
||||
if (o.fields && typeof o.fields === 'object') {
|
||||
for (const [k, v] of Object.entries(o.fields)) {
|
||||
if (!isKnownLabel(k)) snap.extraFields[k] = String(v);
|
||||
@@ -79,11 +115,16 @@
|
||||
const changed = new Set();
|
||||
if (!a || !b) return changed;
|
||||
const keys = ['gps', 'packetTime', 'role', 'rssiDbm', 'snrDb', 'rxQualityPercent', 'packet', 'payload', 'perPercent',
|
||||
'txPktPerS', 'rxPktPerS', 'frequencyMhz', 'sf', 'bwKhz', 'powerDbm'];
|
||||
const map = { gps: 'gps', packetTime: 'packetTime', role: 'role', rssiDbm: 'rssi', snrDb: 'snr', rxQualityPercent: 'rxQuality',
|
||||
packet: 'packet',
|
||||
payload: 'payload', perPercent: 'per', txPktPerS: 'txSpeed', rxPktPerS: 'rxSpeed',
|
||||
frequencyMhz: 'frequency', sf: 'sf', bwKhz: 'bw', powerDbm: 'power' };
|
||||
'packetReceive', 'packetTotal', 'packetError', 'crcError', 'preambleDetected', 'headerValid',
|
||||
'txPktPerS', 'rxPktPerS', 'frequencyMhz', 'sf', 'bwKhz', 'powerDbm', 'codeRate', 'crcEnabled'];
|
||||
const map = {
|
||||
gps: 'gps', packetTime: 'packetTime', role: 'role', rssiDbm: 'rssi', snrDb: 'snr',
|
||||
rxQualityPercent: 'rxQuality', packet: 'packet', payload: 'payload', perPercent: 'per',
|
||||
packetReceive: 'packetReceive', packetTotal: 'packetTotal', packetError: 'packetError',
|
||||
crcError: 'crcError', preambleDetected: 'preambleDetected', headerValid: 'headerValid',
|
||||
txPktPerS: 'txSpeed', rxPktPerS: 'rxSpeed',
|
||||
frequencyMhz: 'frequency', sf: 'sf', bwKhz: 'bw', powerDbm: 'power', codeRate: 'codeRate', crcEnabled: 'crc'
|
||||
};
|
||||
for (const k of keys) {
|
||||
if (a[k] !== b[k] && !(a[k] == null && b[k] == null)) changed.add(map[k]);
|
||||
}
|
||||
@@ -99,6 +140,12 @@
|
||||
{ key: 'packet', label: 'Пакет', fmt: s => s.packet != null ? String(s.packet) : '—' },
|
||||
{ key: 'payload', label: 'Payload', fmt: s => s.payload || '—' },
|
||||
{ key: 'per', label: 'PER', fmt: s => s.perPercent != null ? `${s.perPercent} %` : '—' },
|
||||
{ key: 'packetReceive', label: 'Принято', fmt: s => s.packetReceive != null ? String(s.packetReceive) : '—' },
|
||||
{ key: 'packetTotal', label: 'Всего', fmt: s => s.packetTotal != null ? String(s.packetTotal) : '—' },
|
||||
{ key: 'packetError', label: 'Ошибки', fmt: s => s.packetError != null ? String(s.packetError) : '—' },
|
||||
{ key: 'crcError', label: 'CRC err', fmt: s => s.crcError != null ? String(s.crcError) : '—' },
|
||||
{ key: 'preambleDetected', label: 'Preamble', fmt: s => s.preambleDetected != null ? String(s.preambleDetected) : '—' },
|
||||
{ key: 'headerValid', label: 'Header OK', fmt: s => s.headerValid != null ? String(s.headerValid) : '—' },
|
||||
{ key: 'txSpeed', label: 'TX Speed', fmt: s => s.txPktPerS != null ? `${s.txPktPerS} pkt/s` : '—' },
|
||||
{ key: 'rxSpeed', label: 'RX Speed', fmt: s => s.rxPktPerS != null ? `${s.rxPktPerS} pkt/s` : '—' }
|
||||
];
|
||||
@@ -107,8 +154,14 @@
|
||||
{ key: 'role', label: 'Роль', fmt: s => roleLabel(s.role) },
|
||||
{ key: 'frequency', label: 'Частота', fmt: s => s.frequencyMhz != null ? `${s.frequencyMhz.toFixed(3)} MHz` : '—' },
|
||||
{ key: 'sf', label: 'SF', fmt: s => s.sf != null ? String(s.sf) : '—' },
|
||||
{ key: 'bw', label: 'BW', fmt: s => s.bwKhz != null ? `${s.bwKhz} kHz` : '—' },
|
||||
{ key: 'bw', label: 'BW', fmt: s => fmtBw(s.bwKhz) },
|
||||
{ key: 'power', label: 'Мощность', fmt: s => s.powerDbm != null ? `${s.powerDbm} dBm` : '—' },
|
||||
{ key: 'codeRate', label: 'Code Rate', fmt: s => s.codeRate || '—' },
|
||||
{ key: 'preambleLength', label: 'Preamble', fmt: s => s.preambleLength != null ? String(s.preambleLength) : '—' },
|
||||
{ key: 'lowDataRateOpt', label: 'LDR', fmt: s => s.lowDataRateOpt || '—' },
|
||||
{ key: 'crc', label: 'CRC', fmt: s => fmtCrc(s.crcEnabled) },
|
||||
{ key: 'payloadLength', label: 'Payl.len', fmt: s => s.payloadLengthBytes != null ? `${s.payloadLengthBytes} B` : '—' },
|
||||
{ key: 'txTimeout', label: 'TX Timeout', fmt: s => s.txTimeoutMs != null ? `${s.txTimeoutMs} ms` : '—' },
|
||||
{ key: 'onAir', label: 'On Air', fmt: s => s.onAirMs != null ? `${s.onAirMs} ms` : '—' }
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user