package com.bg7yoz.ft8cn.ft8signal; /** * 按照FT8协议打包符号。 * * @author BGY70Z * @date 2023-03-20 */ import android.util.Log; import com.bg7yoz.ft8cn.Ft8Message; import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; public class FT8Package { private static final String TAG = "FT8Package"; public static final int NTOKENS = 2063592; public static final int MAX22 = 4194304; public static final int MAXGRID4 = 32400; private static final String A1 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String A2 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String A3 = "0123456789"; private static final String A4 = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String A5 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"; static { System.loadLibrary("ft8cn"); } /** * 生成i3=4的非标准消息的77位数据包。 * @param message 消息 * @return 数据包 */ public static byte[] generatePack77_i4(Ft8Message message) { String toCall = message.callsignTo.replace("<", "").replace(">", ""); String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); int hash12; if (message.checkIsCQ()) {//如果是CQ,就把自己的呼号的哈希加上 hash12 = getHash12(fromCall); } else { hash12 = getHash12(toCall); } if (fromCall.length() > 11) {//非标准呼号的长度不等长于11位 fromCall = fromCall.substring(0, 11); } byte[] data = new byte[10]; long n58 = 0; for (int i = 0; i < fromCall.length(); i++) { n58 = n58 * 38 + A5.indexOf(fromCall.charAt(i)); } //n58=3479529522318088L; data[0] = (byte) ((hash12 & 0x00000fff) >> 4); data[1] = (byte) ((hash12 & 0x0000000f) << 4); data[1] = (byte) (data[1] | ((n58 & 0x0fff_ffff_ffff_ffffL) >> 54)); data[2] = (byte) (((n58 & 0x00ff_ffff_ffff_ffffL) >> 54 - 8)); data[3] = (byte) (((n58 & 0x0000_ffff_ffff_ffffL) >> 54 - 8 - 8)); data[4] = (byte) (((n58 & 0x0000_00ff_ffff_ffffL) >> 54 - 8 - 8 - 8)); data[5] = (byte) (((n58 & 0x0000_0000_ffff_ffffL) >> 54 - 8 - 8 - 8 - 8)); data[6] = (byte) (((n58 & 0x0000_0000_00ff_ffffL) >> 54 - 8 - 8 - 8 - 8 - 8)); data[7] = (byte) (((n58 & 0x0000_0000_0000_ffffL) >> 54 - 48)); data[8] = (byte) (((n58 & 0x0000_0000_0000_00ffL) << 2)); //RRR=1,RR73=2,73=3,""=0 if (message.checkIsCQ()) { data[9] = (byte) 0x60; } else { data[9] = (byte) 0x20; switch (message.extraInfo) { case "RRR": //r2=1 data[8] = (byte) (data[8] & 0xfe); data[9] = (byte) (data[9] | 0x80); break; case "RR73": //r2=2 data[8] = (byte) (data[8] | 0x01); //data[9] = (byte) (data[9] | 0x00);//data[9]无需改变 break; case "73": //r2=3 data[8] = (byte) (data[8] | 0x01); data[9] = (byte) (data[9] | 0x80); break; } } return data; } /** * 从复合呼号中提取标准呼号,复合呼号是指带“/”的呼号。 * 此应用场景:双方都是复合呼号,那么发送方(我方)要改为标准呼号。 * 从复合呼号提取标准呼号的逻辑是:拆解“/”分隔的部分,取符合FT8标准呼号的正则表达式,如果没有则取最长字符串部分。 * @param compoundCallsign 复合呼号 * @return 标准呼号 */ public static String getStdCall(String compoundCallsign) { if (!compoundCallsign.contains("/")) return compoundCallsign; String[] callsigns = compoundCallsign.split("/"); for (String callsign : callsigns) {//用正则表达式提取标准呼号 //FT8的认定:标准业余呼号由一个或两个字符的前缀组成,其中至少一个必须是字母,后跟一个十进制数字和最多三个字母的后缀。 if (callsign.matches("[A-Z0-9]?[A-Z0-9][0-9][A-Z][A-Z0-9]?[A-Z]?")) { return callsign; } } //当无法提取标准呼号时,取最长的字段 int len = 0; int index = 0; for (int i = 0; i < callsigns.length; i++) { if (callsigns[i].length() > len) { len = callsigns[i].length(); index = i; } } return callsigns[index]; } /** * i1=1,i1=2,在FT8协议的定义当中,分别是标准消息,和欧盟甚高频(EU VHF),这两个消息的唯一区别是: * i1=1,消息可以带/R,i1=2,消息是可以带/P * 所以,这两个消息可以合并为一个类型。 * * @param message 原始消息 * @return packet77 */ public static byte[] generatePack77_i1(Ft8Message message) { String toCall = message.callsignTo.replace("<", "").replace(">", ""); String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); if (message.checkIsCQ() && message.modifier != null) {//把修饰符加上 if (message.modifier.length() > 0) { toCall = toCall + " " + message.modifier; } } //如果以/P 或/R结尾的呼号,要把这个/P /R去掉 if (toCall.endsWith("/P") || toCall.endsWith("/R")) { toCall = toCall.substring(0, toCall.length() - 2); } if (fromCall.endsWith("/P") || fromCall.endsWith("/R")) { fromCall = message.callsignFrom.substring(0, message.callsignFrom.length() - 2); } //当双方都是复合呼号或非标准呼号时(带/的呼号),我的呼号变成标准呼号 if ((toCall.contains("/")) && fromCall.contains("/")) { fromCall = getStdCall(fromCall);//从复合呼号中提取标准呼号 // fromCall = fromCall.substring(0, fromCall.indexOf("/")); } byte[] data = new byte[12]; data[0] = (byte) ((pack_c28(toCall) & 0x0fffffff) >> 20); data[1] = (byte) ((pack_c28(toCall) & 0x00ffffff) >> 12); data[2] = (byte) ((pack_c28(toCall) & 0x0000ffff) >> 4); data[3] = (byte) ((pack_c28(toCall) & 0x0000000f) << 4); data[3] = (byte) (data[3] | (pack_r1_p1(message.callsignTo) << 3)); data[3] = (byte) (data[3] | (pack_c28(fromCall) & 0x00fffffff) >> 25); data[4] = (byte) ((pack_c28(fromCall) & 0x003ffffff) >> 25 - 8); data[5] = (byte) ((pack_c28(fromCall) & 0x00003ffff) >> 25 - 8 - 8); data[6] = (byte) ((pack_c28(fromCall) & 0x0000003ff) >> 25 - 8 - 8 - 8); data[7] = (byte) ((pack_c28(fromCall) & 0x0000000ff) << 7); data[7] = (byte) (data[7] | (pack_r1_p1(message.getCallsignFrom())) << 6); data[7] = (byte) (data[7] | (pack_R1_g15(message.extraInfo) & 0x0ffff) >> 10); data[8] = (byte) ((pack_R1_g15(message.extraInfo) & 0x0003fff) >> 2); data[9] = (byte) ((pack_R1_g15(message.extraInfo) & 0x00000ff) << 6); data[9] = (byte) (data[9] | (message.i3 & 0x3) << 3); return data; } /** * 生成R1+g15数据(网格、或信号报告),实际是16位,包括前面R1。如R-17:R1=1,-17:R1=0 * * @param grid4 网格或信号报告 * @return 返回R1+g15数据 */ public static int pack_R1_g15(String grid4) { if (grid4 == null)// 只有两个呼号,没有信号报告和网格 { return MAXGRID4 + 1; } if (grid4.length() == 0) {// 只有两个呼号,没有信号报告和网格 return MAXGRID4 + 1; } // 特殊的报告,RRR,RR73,73 if (grid4.equals("RRR")) return MAXGRID4 + 2; if (grid4.equals("RR73")) return MAXGRID4 + 3; if (grid4.equals("73")) return MAXGRID4 + 4; // 检查是不是标准的4字符网格 if (grid4.matches("[A-Z][A-Z][0-9][0-9]")) { int igrid4 = grid4.charAt(0) - 'A'; igrid4 = igrid4 * 18 + (grid4.charAt(1) - 'A'); igrid4 = igrid4 * 10 + (grid4.charAt(2) - '0'); igrid4 = igrid4 * 10 + (grid4.charAt(3) - '0'); return igrid4; } // 检查是不是信号报告: +dd / -dd / R+dd / R-dd // 信号报告在-30到99dB之间 // 信号报告的正则:[R]?[+-][0-9]{1,2} String s = grid4; if (grid4.charAt(0) == 'R') { s = grid4.substring(1); int irpt = 35 + Integer.parseInt(s); return (MAXGRID4 + irpt) | 0x8000; // R1 = 1 } else { int irpt = 35 + Integer.parseInt(grid4); return (MAXGRID4 + irpt); // R1 = 0 } } public static byte pack_r1_p1(String callsign) { String s = callsign.substring(callsign.length() - 2); if (s.equals("/R") || s.equals("/P")) { return 1; } else { return 0; } } /** * 根据呼号生成c28数据。呼号为标准呼号时,不带/R或/P。如果呼号不是标准呼号,用hash22+2063592; * * @param callsign 呼号 * @return c28数据 */ public static int pack_c28(String callsign) { //byte[] data=new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}; switch (callsign) { case "DE": return 0; case "QRZ": return 1; case "CQ": return 2; } //判断是否有修饰符000-999,A-Z,AA-ZZ,AAA-ZZZ,AAAA-ZZZZ if (callsign.startsWith("CQ ") && callsign.length() > 3) { String temp = callsign.substring(3).trim().toUpperCase(); if (temp.matches("[0-9]{3}")) { int i = Integer.parseInt(temp); return i + 3; } if (temp.matches("[A-Z]{1,4}")) { int a0 = 0; int a1 = 0; int a2 = 0; int a3 = 0; if (temp.length() == 1) {//A-Z a0 = (int) temp.charAt(0) - 65; return a0 + 1004; } if (temp.length() == 2) {//AA-ZZ a0 = (int) temp.charAt(0) - 65; a1 = (int) temp.charAt(1) - 65; return a0 * 27 + a1 + 1031; } if (temp.length() == 3) {//AAA-ZZZ a0 = (int) temp.charAt(0) - 65; a1 = (int) temp.charAt(1) - 65; a2 = (int) temp.charAt(2) - 65; return a0 * 27 * 27 + a1 * 27 + a2 + 1760; } if (temp.length() == 4) {//AAAA-ZZZZ a0 = (int) temp.charAt(0) - 65; a1 = (int) temp.charAt(1) - 65; a2 = (int) temp.charAt(2) - 65; a3 = (int) temp.charAt(3) - 65; return a0 * 27 * 27 * 27 + a1 * 27 * 27 + a2 * 27 + a3 + 21443; } } } //格式化成标准的呼号。6位、第3位带数字 //c6也可以是非标准呼号。大于6位的都是非标准呼号 String c6 = formatCallsign(callsign); //判断是不是标准呼号 if (!GenerateFT8.checkIsStandardCallsign(callsign)) {//生成HASH22+2063592 return NTOKENS + getHash22(callsign); } //6位呼号取值 int i0, i1, i2, i3, i4, i5; i0 = A1.indexOf(c6.substring(0, 1)); i1 = A2.indexOf(c6.substring(1, 2)); i2 = A3.indexOf(c6.substring(2, 3)); i3 = A4.indexOf(c6.substring(3, 4)); i4 = A4.indexOf(c6.substring(4, 5)); i5 = A4.indexOf(c6.substring(5, 6)); int n28 = i0; n28 = n28 * 36 + i1; n28 = n28 * 10 + i2; n28 = n28 * 27 + i3; n28 = n28 * 27 + i4; n28 = n28 * 27 + i5; return NTOKENS + MAX22 + n28; } /** * 格式化标准的呼号 * 标准呼号是6位,前缀是1~2个字母+1个数字,后缀对多3个字母 * 格式化的内容: * 1.斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ * 2.几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ * 3.第2位是数字的呼号,前面用空格补充。A0XYZ->" A0XYZ" * 4.后缀不足3位的,也要补足空格。BA2BI->"BA2BI " * * @param callsign 呼号 * @return 返回C28值,以int的值来表示 */ private static String formatCallsign(String callsign) { String c6 = callsign; // 解决斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ if (callsign.length() > 3 && callsign.substring(0, 4).equals("3DA0") && callsign.length() <= 7) { c6 = "3D0" + callsign.substring(4); // 解决几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ } else if (callsign.length() > 3 && callsign.substring(0, 3).matches("3X[A-Z]") && callsign.length() <= 7) { c6 = "Q" + callsign.substring(2); } else { // 第2位是数字,第3位是字母的,要在前面补充空格:A0XYZ -> " A0XYZ",A6开头的除外 if (callsign.substring(0, 3).matches("[A-Z][0-9][A-Z]")) { c6 = " " + callsign; } } if (c6.length() < 6) {//如果长度不足6位,结尾补充空格 for (int i = 0; i < 6 - c6.length() + 1; i++) { c6 = c6 + " "; } } return c6; } public static native int getHash12(String callsign); public static native int getHash10(String callsign); public static native int getHash22(String callsign); }