package com.bg7yoz.ft8cn.icom; /** * 控制流的基本类。 * * @author BGY70Z * @date 2023-08-26 */ import android.util.Log; import java.net.DatagramPacket; import java.util.Timer; import java.util.TimerTask; public class ControlUdp extends IcomUdpBase { private static final String TAG = "ControlUdp"; public final String APP_NAME = "FT8CN"; //与采样率有关,每20ms发送的样本数12000/50=240=F0,实际字节数是(16bit),还要乘以2,也就是480字节 public Timer tokenTimer;//续订令牌的时钟 public String userName; public String password; public String rigName = ""; public String audioName = ""; public byte[] rigMacAddress = new byte[6];//0xA8、0x90包中提供 public String connectionMode = ""; public boolean gotAuthOK = false;//token认证通过了 public boolean isAuthenticated = false;//登录成功 public boolean rigIsBusy = false; public IcomCivUdp civUdp; public AudioUdp audioUdp; public ControlUdp(String userName, String password, String remoteIp, int remotePort) { udpStyle = IcomUdpStyle.ControlUdp; this.userName = userName; this.password = password; this.rigIp = remoteIp; this.rigPort = remotePort; } @Override public void onDataReceived(DatagramPacket packet, byte[] data) { // 父类默认处理一下数据包: // 控制包0x10(CMD_I_AM_HERE、CMD_RETRANSMIT), // ping包0x15 // 变长包:RETRANSMIT包,type=IComPacketTypes.CMD_RETRANSMIT super.onDataReceived(packet, data); switch (data.length) { case IComPacketTypes.CONTROL_SIZE://在父类中已经实现0x04,0x01指令 if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_HERE) { rigIp = packet.getAddress().getHostAddress(); } //如果电台回复I'm ready,就发起login if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) { sendLoginPacket();//电台准备好了,申请登录 0x80包 startIdleTimer();//打开发送空包时钟 } break; case IComPacketTypes.TOKEN_SIZE://处理令牌的续订之类的事情 onReceiveTokenPacket(data); break; case IComPacketTypes.STATUS_SIZE://0x50电台回复我它的参数:CivPort,AudioPort等 onReceiveStatusPacket(data); break; case IComPacketTypes.LOGIN_RESPONSE_SIZE://0x60电台回复登录的请求 onReceiveLoginResponse(data); break; case IComPacketTypes.CONNINFO_SIZE://电台会回复2次0x90包,区别在于busy字段 onReceiveConnInfoPacket(data); break; case IComPacketTypes.CAP_CAPABILITIES_SIZE://0xA8数据包,返回civ地址 byte[] audioCap = IComPacketTypes.CapCapabilitiesPacket.getRadioCapPacket(data, 0); if (audioCap != null) { civUdp.supportTX = IComPacketTypes.RadioCapPacket.getSupportTX(audioCap); civUdp.civAddress = IComPacketTypes.RadioCapPacket.getCivAddress(audioCap); audioName = IComPacketTypes.RadioCapPacket.getAudioName(audioCap); } break; } } /** * 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=0,第二次busy=1。 * 在0x90数据包中取macAddress,电台名称 * 这部分留给IcomControlUdp和XieGuControlUdp来处理 * @param data 0x90数据包 */ public void onReceiveConnInfoPacket(byte[] data) { } /** * 处理电台回复登录数据包 * * @param data 0x60数据包 */ public void onReceiveLoginResponse(byte[] data) { if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; connectionMode = IComPacketTypes.LoginResponsePacket.getConnection(data); Log.d(TAG, "connection mode:" + connectionMode); if (IComPacketTypes.LoginResponsePacket.authIsOK(data)) {//errorCode=0x00,认证成功 Log.d(TAG, "onReceiveLoginResponse: Login succeed!"); if (!isAuthenticated) { rigToken = IComPacketTypes.LoginResponsePacket.getToken(data); Log.d(TAG, "onReceiveLoginResponse: send token confirm 0x02"); sendTokenPacket(IComPacketTypes.TOKEN_TYPE_CONFIRM);//发送令牌确认包 0x40 startTokenTimer();//启动令牌续订时钟 isAuthenticated = true; } } if (onStreamEvents != null) {//触发认证事件 onStreamEvents.OnLoginResponse(IComPacketTypes.LoginResponsePacket.authIsOK(data)); } } /** * 处理电台回复我的参数。0x50数据包 * * @param data 0x50数据包 */ public void onReceiveStatusPacket(byte[] data) { //if (this.authDone) return;//6100会频繁激活0x50包 if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; if (IComPacketTypes.StatusPacket.getAuthOK(data) && IComPacketTypes.StatusPacket.getIsConnected(data)) {//令牌认证成功,且处于连接状态 audioUdp.rigPort = IComPacketTypes.StatusPacket.getRigAudioPort(data); audioUdp.rigIp = rigIp; civUdp.rigPort = IComPacketTypes.StatusPacket.getRigCivPort(data); civUdp.rigIp = rigIp; Log.e(TAG, String.format("onReceiveStatusPacket: Status packet 0x50: civRigPort:%d,audioRigPort:%d" , civUdp.rigPort, audioUdp.rigPort)); //todo 6100与icom有差异 civUdp.startAreYouThereTimer();//civ端口启动连接电台 audioUdp.startAreYouThereTimer();//audio端口启动连接电台 }//else处理关闭连接??? } /** * 处理令牌数据包 * * @param data 0x40数据包 */ public void onReceiveTokenPacket(byte[] data) { //看是不是续订令牌包 if (IComPacketTypes.TokenPacket.getRequestType(data) == IComPacketTypes.TOKEN_TYPE_RENEWAL && IComPacketTypes.TokenPacket.getRequestReply(data) == 0x02 && IComPacketTypes.ControlPacket.getType(data) != IComPacketTypes.CMD_RETRANSMIT) { int response = IComPacketTypes.TokenPacket.getResponse(data); if (response == 0x0000) {//说明续订成功了 gotAuthOK = true; } else if (response == 0xffffffff) { remoteId = IComPacketTypes.ControlPacket.getSentId(data); localToken = IComPacketTypes.TokenPacket.getTokRequest(data); rigToken = IComPacketTypes.TokenPacket.getToken(data); sendConnectionRequest();//申请连接 } else { Log.e(TAG, "Token renewal failed,unknow response"); } } } /** * 发送civ指令 * * @param data 指令 */ public void sendCivData(byte[] data) { civUdp.sendCivData(data); } /** * 发送音频数据到电台 * * @param data 数据 */ public void sendWaveData(float[] data) { audioUdp.sendTxAudioData(data); } /** * 发送0x90数据包,向电台请求连接 */ public void sendConnectionRequest() { sendTrackedPacket(IComPacketTypes.ConnInfoPacket.connectRequestPacket((short) 0 , localId, remoteId, (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken , rigMacAddress, rigName, userName, IComPacketTypes.AUDIO_SAMPLE_RATE , civUdp.getLocalPort(), audioUdp.getLocalPort() , IComPacketTypes.TX_BUFFER_SIZE)); innerSeq++; } /** * 发送登录数据包0x80包 */ public void sendLoginPacket() { sendTrackedPacket(IComPacketTypes.LoginPacket.loginPacketData((short) 0 , localId, remoteId, innerSeq, localToken, rigToken, userName, password, APP_NAME)); innerSeq++; } @Override public void setOnStreamEvents(OnStreamEvents onStreamEvents) { super.setOnStreamEvents(onStreamEvents); audioUdp.onStreamEvents = onStreamEvents; civUdp.onStreamEvents = onStreamEvents; } /** * 启动令牌续订时钟 */ public void startTokenTimer() { stopTimer(tokenTimer); Log.d(TAG, String.format("start Toke Timer: local port:%d,remote port %d", localPort, rigPort)); tokenTimer = new Timer(); tokenTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { sendTokenPacket(IComPacketTypes.TOKEN_TYPE_RENEWAL); } }, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS); } public void closeAll() { sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short) 0 , localId, remoteId, IComPacketTypes.TOKEN_TYPE_DELETE, innerSeq, localToken, rigToken)); innerSeq++; this.close(); civUdp.close(); audioUdp.stopTXAudio(); audioUdp.close(); civUdp.sendOpenClose(false); } }