Repository: IanWraith/DMRDecode Branch: master Commit: 56d20f9c32d2 Files: 36 Total size: 268.1 KB Directory structure: gitextract_wyqdejc4/ ├── .classpath ├── .project ├── README.txt ├── pom.xml └── src/ └── main/ └── java/ ├── com/ │ └── dmr/ │ ├── AudioInThread.java │ ├── AudioMixer.java │ ├── BPTC19696.java │ ├── BareBonesBrowserLaunch.java │ ├── CSBK.java │ ├── CSVFileFilter.java │ ├── DMRData.java │ ├── DMRDataDecode.java │ ├── DMRDecode.java │ ├── DMREmbedded.java │ ├── DMRVoice.java │ ├── DecodeCACH.java │ ├── DisplayBar.java │ ├── DisplayFrame.java │ ├── DisplayModel.java │ ├── DisplayView.java │ ├── EmbeddedLC.java │ ├── FullLinkControl.java │ ├── JStatusBar.java │ ├── ShortLC.java │ ├── SlotType.java │ ├── SocketOut.java │ ├── TextfileFilter.java │ ├── Trellis.java │ ├── UsersLogged.java │ ├── Utilities.java │ ├── VoiceData.java │ └── crc.java └── test/ └── com/ └── dmr/ ├── BPTC19696Test.java ├── SlotTypeTest.java ├── TrellisTest.java └── crcTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .classpath ================================================ ================================================ FILE: .project ================================================ DMRDecode org.eclipse.jdt.core.javabuilder org.eclipse.jdt.core.javanature ================================================ FILE: README.txt ================================================ PLEASE NOTE : This project is obsolete and no longer supported. It has been superseded by other better DMR decoders but I'm leaving the repository on here as people seem to be interested. ### The aim of this project is to provide a user friendly DMR data decoder for hobbyists. It is Java based so should run under Microsoft Windows , Apple and Linux PC's. The only hardware needed will be a radio scanner with a discriminator audio output. A binary version of this program which will run without being compiled is available from .. https://drive.google.com/file/d/0B48eZhmoMv5nZUlpdTRNamNrQkk/view?usp=share_link&resourcekey=0-e1snv0OJnYG3bPY8_TfsDg This was however compiled for an earlier version of Java so may or may not still run. The core of the program is based around the open source DSD program by an unknown author. This program was written in C and runs under Linux only so my first job was to convert that code to Java and remove all sections that don't relate to DMR. Milestone 1 - To detect voice and data sync words reached (3rd September 2010) Milestone 2 - To reliably detect voice and data sync words. (Reached 15th October 2010) Milestone 3 - To decode the CACH TACT (Reached 22nd October 2010) Milestone 3.5 (!) - To decode the SLOT TYPE PDU (Reached 4th November 2010) Milestone 4 - To decode the Short LCs that make up the CACH payload. (Reached 25th November 2010) Milestone 5 - To work out why and then fix the problem that is causing single bit errors in frames. (Reached 14th February 2011) Milestone 6 - Add a feature where all DMR users are logged. (Reached 19th February 2011) Milestone 7 - To fix a memory leak type problem where the heap memory used by the program keeps growing until it slows down and crashes. (Reached 28th February 2011) Milestone 8 - To pass certain data on to other programs via TCP/IP socket. (Reached 1st March 2011) Build 48 - Convert the Connect Plus site ID field in SLCO 10 packets from 3 bits to 8 bits Add the Utilities class to remove duplicated code. Display the manufacturers name in Proprietary Data Headers. Start adding build change details in this README file. Build 49 - Display the payload in half rate data packets both as binary and ASCII. Build 50 - Correctly display an 8 bit Site ID in Connect Plus SLCO 9 packets. Build 51 - Display Privacy Header PDUs as raw binary Build 52 - Fix a bug which meant that PI Header PDUs in embedded frames weren't being displayed Build 53 - Allows the users settings to be saved in DMRDecode_settings.xml and reloaded on start up. Build 54 - Adds a CPU utilization fix contributed by Chris Sams Build 55 - Adds an option to not view voice frames and also replaces all instances of StringBuffer with StringBuilder Build 56 - Adds the date to the quick log. Displays the data filters at the start of each log and shows any filter changes in the log file. From build 57 onwards Java 7 is required to run DMRDecode. Build 57 - Decode Connect Plus CSBKO 01 FID=6 PDUs Build 58 - Add a link to the new download page. Also decode Rate ¾ Data Continuation PDUs as raw binary. Build 59 - Display the Time Slot in Connect Plus Channel Grants (CSBKO 03 FID=6). This information was kindly provided by W8EMX on the Radioreference forums. Build 60 - Allows the user to select their audio source. Build 61 - Now saves the selected audio source in the settings file Build 62 - Disable the capture and debug menu items. Also change some code so virtual audio cables can be selected. Build 63 - Grey out the capture and debug items. Catch errors when the audio source is set from the settings file and better display errors when changing audio sources. Build 64 - Ensure that only sound capture devices can be selected when choosing a mixer. Also better display information from any errors during mixer selection. Build 65 - Saves half rate to data to debug.csv for later analysis Build 68 - Decodes rate 3/4 data and has a unified method of handling data. Build 69 - Improve the decoding of Capacity Plus CSBKO=62 PDUs. This information was kindly provided by Eric Cottrell on the Radioreference forums. Add the Linkedin menu item. Build 70 - Fix a bug in the big_m_csbko62() method. Build 71 - Change so that if a quick log file exists ask the user if they want to overwrite it or append to the existing file. Change so that if log file exists ask the user if they want to overwrite it or append data to the existing file. Add support for CSBKO=31 FID=16 (Call Alert) and CSBKO=32 FID=16 (Call Alert Ack) this information kindly provided by bben95 on the Radioreference forums. Decode Tier III Sys_Parms Short LCs Build 72 - Fix bugs in the csbko31fid16() and csbko32fid16() methods were the to and from idents were transposed. Build 73 - Add basic support for monitoring MS and Direct mode. Build 74 - The program now supports nearly all Tier III CSBKOs Ian Wraith (12th November 2022) ================================================ FILE: pom.xml ================================================ 4.0.0 com.dmr DRMDecode 1.0-SNAPSHOT jar 1.8 UTF-8 UTF-8 1.8 1.8 org.apache.maven.plugins maven-jar-plugin false com.dmr.DMRDecode junit junit 4.10 ================================================ FILE: src/main/java/com/dmr/AudioInThread.java ================================================ package com.dmr; import javax.swing.JOptionPane; import java.io.DataOutputStream; import java.io.PipedOutputStream; public class AudioInThread implements Runnable { private boolean run; private boolean audioReady; private boolean gettingAudio; private static int VOLUMEBUFFERSIZE=100; private int volumeBuffer[]=new int[VOLUMEBUFFERSIZE]; private int volumeBufferCounter=0; private static int ISIZE=256; private byte buffer[]=new byte[ISIZE+1]; private PipedOutputStream ps=new PipedOutputStream(); private DataOutputStream outPipe=new DataOutputStream(ps); private AudioMixer audioMixer; // Filter details .. // filtertype = Raised Cosine // samplerate = 48000 // corner = 2400 // beta = 0.2 // impulselen = 81 // racos = sqrt // comp = no // bits = // logmin = private static int NZEROS=80; private double xv[]=new double [NZEROS+1]; private int xvCounter=0; // 0.2 // private static double GAIN=9.868410946e+00; private static double XCOEFFS[]= {+0.0273676736, +0.0190682959, +0.0070661879, -0.0075385898, -0.0231737159, -0.0379433607, -0.0498333862, -0.0569528373, -0.0577853377, -0.0514204905, -0.0377352004, -0.0174982391, +0.0076217868, +0.0351552125, +0.0620353691, +0.0848941519, +0.1004237235, +0.1057694293, +0.0989127431, +0.0790009892, +0.0465831968, +0.0037187043, -0.0460635022, -0.0979622825, -0.1462501260, -0.1847425896, -0.2073523972, -0.2086782295, -0.1845719273, -0.1326270847, -0.0525370892, +0.0537187153, +0.1818868577, +0.3256572849, +0.4770745929, +0.6271117870, +0.7663588857, +0.8857664963, +0.9773779594, +1.0349835419, +1.0546365475, +1.0349835419, +0.9773779594, +0.8857664963, +0.7663588857, +0.6271117870, +0.4770745929, +0.3256572849, +0.1818868577, +0.0537187153, -0.0525370892, -0.1326270847, -0.1845719273, -0.2086782295, -0.2073523972, -0.1847425896, -0.1462501260, -0.0979622825, -0.0460635022, +0.0037187043, +0.0465831968, +0.0790009892, +0.0989127431, +0.1057694293, +0.1004237235, +0.0848941519, +0.0620353691, +0.0351552125, +0.0076217868, -0.0174982391, -0.0377352004, -0.0514204905, -0.0577853377, -0.0569528373, -0.0498333862, -0.0379433607, -0.0231737159, -0.0075385898, +0.0070661879, +0.0190682959, +0.0273676736}; public AudioInThread (DMRDecode theApp) { audioMixer=new AudioMixer(); run=false; gettingAudio=false; setupAudio(); } // Main public void run() { // Run continiously for (;;) { // If the audio device is ready , the program wants to and we aren't already then // get data from the audio device. if ((audioReady==true)&&(run==true)&&(gettingAudio==false)) getSample(); } } private void setupAudio() { try { audioMixer.setDefaultLine(); audioMixer.openLine(); audioMixer.line.start(); audioReady=true; } catch (Exception e) { audioReady=false; String err="setupAudio() : "+e.toString(); JOptionPane.showMessageDialog(null,err,"DMRDecode",JOptionPane.ERROR_MESSAGE); } } // Read in 2 bytes from the audio source combine them together into a single integer // then write that into the sound buffer private void getSample () { // Tell the main thread we getting audio gettingAudio=true; int a,sample,count,total=0; // READ in ISIZE bytes and convert them into ISIZE/2 integers // Doing it this way reduces CPU loading try { while (total fileMgr = Class.forName("com.apple.eio.FileManager"); Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class }); openURL.invoke(null, new Object[] { url }); } else if (osName.startsWith("Windows")){ Runtime.getRuntime().exec( "rundll32 url.dll,FileProtocolHandler " + url); }else { // assume Unix or Linux String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" }; String browser = null; for (int count = 0; count < browsers.length && browser == null; count++){ if (Runtime.getRuntime().exec( new String[] { "which", browsers[count] }) .waitFor() == 0){ browser = browsers[count]; } } if (browser == null) throw new Exception("Could not find web browser"); else Runtime.getRuntime().exec(new String[] { browser, url }); } } catch (Exception e) { JOptionPane.showMessageDialog(null, errMsg + ":\n" + e.getLocalizedMessage()); } } } ================================================ FILE: src/main/java/com/dmr/CSBK.java ================================================ package com.dmr; public class CSBK { boolean lb,pf; private String display[]=new String[3]; // The main decode method public String[] decode (DMRDecode theApp,boolean bits[]) { int csbko,fid; // LB lb=bits[0]; // PF pf=bits[1]; // CSBKO if (bits[2]==true) csbko=32; else csbko=0; if (bits[3]==true) csbko=csbko+16; if (bits[4]==true) csbko=csbko+8; if (bits[5]==true) csbko=csbko+4; if (bits[6]==true) csbko=csbko+2; if (bits[7]==true) csbko++; // FID if (bits[8]==true) fid=128; else fid=0; if (bits[9]==true) fid=fid+64; if (bits[10]==true) fid=fid+32; if (bits[11]==true) fid=fid+16; if (bits[12]==true) fid=fid+8; if (bits[13]==true) fid=fid+4; if (bits[14]==true) fid=fid+2; if (bits[15]==true) fid++; // CSBK Types // 56 - BS_Dwn_Act if (csbko==56) { bs_dwn_act(bits); } // 01 (FID 6) - Connect Plus else if ((csbko==1)&&(fid==6)) { big_m_csbko01(theApp,bits); } // 03 (FID 6) - Connect Plus else if ((csbko==3)&&(fid==6)) { big_m_csbko03(theApp,bits); } // 04 - UU_V_Reg else if (csbko==4) { uu_v_reg(bits); } // 05 - UU_Ans_Rsp else if (csbko==5) { uu_ans_rep(bits); } // 25 (FID 00) - C_ALOHA (Tier III) else if ((csbko==25)&&(fid==0)) { csbko25fid0(theApp,bits); } // TODO : Support CSBKO 26 (FID 00) - C_UDTHD (Tier III) PDU defined in ETSI TS 102 361-4 V1.5.1 (2013-02) but not as a CSBK ! // TODO : Support CSBKO 27 (FID 00) - C_UDTHU (Tier III) PDU defined in ETSI TS 102 361-4 V1.5.1 (2013-02) but not as a CSBK ! // 28 (FID 00) - AHOY (Tier III) else if ((csbko==28)&&(fid==0)) { csbko28fid0(theApp,bits); } // 30 (FID 00) - C_ACKVIT (Tier III) else if ((csbko==30)&&(fid==0)) { csbko30fid0(theApp,bits); } // 31 (FID 00) - C_RAND (Tier III) else if ((csbko==31)&&(fid==0)) { csbko31fid0(theApp,bits); } // 31 (FID 16) - Call Alert else if ((csbko==31)&&(fid==16)) { csbko31fid16(theApp,bits); } // 32 (FID 00) - C_ACKD (Tier III) else if ((csbko==32)&&(fid==0)) { csbko32fid0(theApp,bits); } // 32 (FID 16) - Call Alert Ack else if ((csbko==32)&&(fid==16)) { csbko32fid16(theApp,bits); } // 33 (FID 00) - C_ACKU (Tier III) else if ((csbko==33)&&(fid==0)) { csbko33fid0(theApp,bits); } // TODO : Support CSBKO 34 (FID 00) - P_ACKD (Tier III) PDU not defined in ETSI TS 102 361-4 V1.5.1 (2013-02) // TODO : Support CSBKO 35 (FID 00) - P_ACKU (Tier III) PDU not defined in ETSI TS 102 361-4 V1.5.1 (2013-02) // 36 (FID 16) - Radio Check else if ((csbko==36)&&(fid==16)) { csbko36fid16(theApp,bits); } // 38 - NACK_Rsp else if (csbko==38) { nack_rsp(bits); } // 40 (FID 00) - C_BCAST (Tier III) else if ((csbko==40)&&(fid==0)) { csbko40fid0(theApp,bits); } // 42 (FID 00) - P_MAINT (Tier III) else if ((csbko==42)&&(fid==0)) { csbko42fid0(theApp,bits); } // 46 (FID 00) - P_CLEAR (Tier III) else if ((csbko==46)&&(fid==0)) { csbko46fid0(theApp,bits); } // 47 (FID 00) - P_PROTECT (Tier III) else if ((csbko==47)&&(fid==0)) { csbko47fid0(theApp,bits); } // 48 (FID 00) - PV_GRANT (Tier III) else if ((csbko==48)&&(fid==0)) { csbko48fid0(theApp,bits); } // 49 (FID 00) - TV_GRANT (Tier III) else if ((csbko==49)&&(fid==0)) { csbko49fid0(theApp,bits); } // 50 (FID 00) - BTV_GRANT (Tier III) else if ((csbko==50)&&(fid==0)) { csbko50fid0(theApp,bits); } // 51 (FID 00) - PD_GRANT (Tier III) else if ((csbko==51)&&(fid==0)) { csbko51fid0(theApp,bits); } // 52 (FID 00) - TD_GRANT (Tier III) else if ((csbko==52)&&(fid==0)) { csbko52fid0(theApp,bits); } // 57 (FID 00) - C_MOVE (Tier III) else if ((csbko==57)&&(fid==0)) { csbko57fid0(theApp,bits); } // 59 - Capacity Plus else if ((csbko==59)&&(fid==16)) { big_m_csbko59(theApp,bits); } // 61 - Pre_CSBK else if (csbko==61) { preCSBK(theApp,bits); } // 62 - Capacity Plus else if (csbko==62) { big_m_csbko62(theApp,bits); } else { unknownCSBK(csbko,fid,bits); } return display; } // Handle unknown CSBK types private void unknownCSBK (int csbko,int fid,boolean bits[]) { int a; StringBuilder sb=new StringBuilder(250); sb.append("Unknown CSBK : CSBKO="+Integer.toString(csbko)+" + FID="+Integer.toString(fid)+" "); // Display the binary for (a=16;a<80;a++) { if (bits[a]==true) sb.append("1"); else sb.append("0"); } display[0]=sb.toString(); } // Handle a Preamble CSBK private void preCSBK (DMRDecode theApp,boolean bits[]) { int index; StringBuilder sb=new StringBuilder(250); StringBuilder sc=new StringBuilder(250); Utilities utils=new Utilities(); // 0 if CSBK , 1 if Data boolean dc=bits[16]; // 0 if target is individual and 1 if group boolean gi=bits[17]; // Bits 18,19,20,21,22,23 are reserved // Next 8 bits are the bits to follow int bfol=0; if (bits[24]==true) bfol=128; if (bits[25]==true) bfol=bfol+64; if (bits[26]==true) bfol=bfol+32; if (bits[27]==true) bfol=bfol+16; if (bits[28]==true) bfol=bfol+8; if (bits[29]==true) bfol=bfol+4; if (bits[30]==true) bfol=bfol+2; if (bits[31]==true) bfol++; // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); // Display this sb.append("Preamble CSBK : "); if (dc==false) sb.append(" CSBK content "); else sb.append(" Data content "); sb.append(Integer.toString(bfol)+" Blocks to follow"); display[0]=sb.toString(); sc.append("Target Address : "+Integer.toString(target)); if (gi==true) sc.append(" (Group)"); sc.append(" Source Address : "+Integer.toString(source)); display[1]=sc.toString(); // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsDataUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsDataUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } } // BS Outbound Activation CSBK private void bs_dwn_act (boolean bits[]) { // TODO : Full decoding of bs_dwn_act display[0]="BS Outbound Activation"; } // Unit to Unit Voice Service Request CSBK private void uu_v_reg (boolean bits[]) { // TODO : Full decoding of UU_V_Req display[0]="Unit to Unit Voice Service Request"; } // Unit to Unit Service Answer Response CSBK private void uu_ans_rep (boolean bits[]) { // TODO : Full decoding of UU_Ans_Rsp display[0]="Unit to Unit Service Answer Response"; } // Negative Acknowledge Response CSBK private void nack_rsp (boolean bits[]) { // TODO : Full decoding of NACK_Rsp display[0]="Negative Acknowledge Response"; } // Capacity Plus // A great deal of information on this type of packet was kindly provided by Eric Cottrell on the Radioreference forums // see http://forums.radioreference.com/digital-voice-decoding-software/209318-understanding-capacity-plus-trunking-6.html#post2078924 private void big_m_csbko62 (DMRDecode theApp,boolean bits[]) { int group1,group2,group3,group4,group5,group6,a,lcn; StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); display[0]="Capacity Plus CSBK : CSBKO=62"; // LCN if (bits[20]==true) lcn=8; else lcn=0; if (bits[21]==true) lcn=lcn+4; if (bits[22]==true) lcn=lcn+2; if (bits[23]==true) lcn++; // Group idents // Low group if (bits[32]==true) group1=128; else group1=0; if (bits[33]==true) group1=group1+64; if (bits[34]==true) group1=group1+32; if (bits[35]==true) group1=group1+16; if (bits[36]==true) group1=group1+8; if (bits[37]==true) group1=group1+4; if (bits[38]==true) group1=group1+2; if (bits[39]==true) group1++; // Group 2 if (bits[40]==true) group2=128; else group2=0; if (bits[41]==true) group2=group2+64; if (bits[42]==true) group2=group2+32; if (bits[43]==true) group2=group2+16; if (bits[44]==true) group2=group2+8; if (bits[45]==true) group2=group2+4; if (bits[46]==true) group2=group2+2; if (bits[47]==true) group2++; // Group 3 if (bits[48]==true) group3=128; else group3=0; if (bits[49]==true) group3=group3+64; if (bits[50]==true) group3=group3+32; if (bits[51]==true) group3=group3+16; if (bits[52]==true) group3=group3+8; if (bits[53]==true) group3=group3+4; if (bits[54]==true) group3=group3+2; if (bits[55]==true) group3++; // Group 4 if (bits[56]==true) group4=128; else group4=0; if (bits[57]==true) group4=group4+64; if (bits[58]==true) group4=group4+32; if (bits[59]==true) group4=group4+16; if (bits[60]==true) group4=group4+8; if (bits[61]==true) group4=group4+4; if (bits[62]==true) group4=group4+2; if (bits[63]==true) group4++; // Group 5 if (bits[64]==true) group5=128; else group5=0; if (bits[65]==true) group5=group5+64; if (bits[66]==true) group5=group5+32; if (bits[67]==true) group5=group5+16; if (bits[68]==true) group5=group5+8; if (bits[69]==true) group5=group5+4; if (bits[70]==true) group5=group5+2; if (bits[71]==true) group5++; // Group 6 if (bits[72]==true) group6=128; else group6=0; if (bits[73]==true) group6=group6+64; if (bits[74]==true) group6=group6+32; if (bits[75]==true) group6=group6+16; if (bits[76]==true) group6=group6+8; if (bits[77]==true) group6=group6+4; if (bits[78]==true) group6=group6+2; if (bits[79]==true) group6++; // Display all this // Only show more if we have any activity if ((bits[24]==false)&&(bits[25]==false)&&(bits[26]==false)&&(bits[27]==false)&&(bits[28]==false)&&(bits[29]==false)) { sb1.append("Activity Update : LCN "+Integer.toString(lcn)+" is the Rest Channel"); } else { boolean nf=false; sb1.append("Activity Update : LCN "+Integer.toString(lcn)+" is the rest channel ("); if (bits[24]==true) { if (group1>0) sb1.append("Group "+Integer.toString(group1)+" on LCN 1"); else sb1.append("Activity on LCN 1"); nf=true; } if (bits[25]==true) { if (nf==true) sb1.append(","); if (group2>0) sb1.append("Group "+Integer.toString(group2)+" on LCN 2"); else sb1.append("Activity on LCN 2"); nf=true; } if (bits[26]==true) { if (nf==true) sb1.append(","); if (group3>0) sb1.append("Group "+Integer.toString(group3)+" on LCN 3"); else sb1.append("Activity on LCN 3"); nf=true; } if (bits[27]==true) { if (nf==true) sb1.append(","); if (group4>0) sb1.append("Group "+Integer.toString(group4)+" on LCN 4"); else sb1.append("Activity on LCN 4"); nf=true; } if (bits[28]==true) { if (nf==true) sb1.append(","); if (group5>0) sb1.append("Group "+Integer.toString(group5)+" on LCN 5"); else sb1.append("Activity on LCN 5"); nf=true; } if (bits[29]==true) { if (nf==true) sb1.append(","); if (group6>0) sb1.append("Group "+Integer.toString(group6)+" on LCN 6"); else sb1.append("Activity on LCN 6"); nf=true; } if (nf==true) sb1.append(")"); } display[1]=sb1.toString(); // Display the full binary if in debug mode if (theApp.isDebug()==true) { for (a=16;a<80;a++) { if (bits[a]==true) sb2.append("1"); else sb2.append("0"); } display[2]=sb2.toString(); } } // Connect Plus - CSBKO 03 FID=6 private void big_m_csbko03 (DMRDecode theApp,boolean bits[]) { int a,lcn; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); display[0]="Connect Plus CSBK : CSBKO=3"; // Source ID int source=utils.retAddress(bits,16); // Group address int group=utils.retAddress(bits,40); // LCN if (bits[64]==true) lcn=8; else lcn=0; if (bits[65]==true) lcn=lcn+4; if (bits[66]==true) lcn=lcn+2; if (bits[67]==true) lcn++; // Time Slot // The information on the time slot bit was kindly provided by W8EMX on the Radioreference forums // see http://forums.radioreference.com/digital-voice-decoding-software/213131-understanding-connect-plus-trunking-7.html#post1909226 boolean timeSlot=bits[68]; // Display this sb1.append("Channel Grant : LCN "+Integer.toString(lcn)); if (timeSlot==false) sb1.append(" TS1"); else sb1.append(" TS2"); sb1.append(" Source "+Integer.toString(source)); sb1.append(" Group "+Integer.toString(group)); display[1]=sb1.toString(); // Display the full binary if in debug mode if (theApp.isDebug()==true) { for (a=16;a<80;a++) { if (bits[a]==true) sb2.append("1"); else sb2.append("0"); } display[2]=sb2.toString(); } } // Connect Plus - CSBKO 01 FID=6 private void big_m_csbko01 (DMRDecode theApp,boolean bits[]) { int a,nb1,nb2,nb3,nb4,nb5; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); display[0]="Connect Plus CSBK : CSBKO=1"; sb1.append("Control Channel Neighbour List : "); // The information to decode these packets was kindly provided by inigo88 on the Radioreference forums // see http://forums.radioreference.com/digital-voice-decoding-software/213131-understanding-connect-plus-trunking-6.html#post1866950 // 67 890123 45 678901 23 456789 01 234567 89 012345 6789 0123 4567 8901 2345 6789 // CSBKO=1 + FID=6 00 000001 00 000011 00 000100 00 000101 00 000110 0000 0000 0000 0000 0000 1110 // 1 3 4 5 6 // bits 16,17 have an unknown purpose // bits 18,19,20,21,22,23 make up the first neighbour site ID nb1=utils.retSix(bits,18); // bits 24,25 have an unknown purpose // bits 26,27,28,29,30,31 make up the second neighbour site ID nb2=utils.retSix(bits,26); // bits 32,33 have an unknown purpose // bits 34,35,36,37,38,39 make up the third neighbour site ID nb3=utils.retSix(bits,34); // bits 40,41 have an unknown purpose // bits 42,43,44,45,46,47 make up the fourth neighbour site ID nb4=utils.retSix(bits,42); // bits 48,49 have an unknown purpose // bits 50,51,52,53,54,55 make up the fifth neighbour site ID nb5=utils.retSix(bits,50); // bits 56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 have an unknown purpose // Display this info sb1.append(Integer.toString(nb1)+","+Integer.toString(nb2)+","+Integer.toString(nb3)+","+Integer.toString(nb4)+","+Integer.toString(nb5)+" ("); // Also display as raw binary for now for (a=16;a<80;a++) { if (bits[a]==true) sb1.append("1"); else sb1.append("0"); } sb1.append(")"); display[1]=sb1.toString(); // Display the full binary if in debug mode if (theApp.isDebug()==true) { for (a=16;a<80;a++) { if (bits[a]==true) sb2.append("1"); else sb2.append("0"); } display[2]=sb2.toString(); } } // CSBKO 25 FID 00 C_ALOHA // Bits 16,17,18,19,20,21 Reserved // 22 Infill // 23 Active connection // 24,25,26,27,28 Mask // 29,30 Service Function // 31,32,33,34 NRand_Wait // 35 Reg // 36,37,38,39 Backoff // 40 - 55 System ID // 56 - 79 MS Individual addr private void csbko25fid0 (DMRDecode theApp,boolean bits[]) { StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); Utilities utils=new Utilities(); display[0]="C_ALOHA : CSBKO=25 + FID=0"; // Infill if (bits[22]==true) sb1.append("Infill Radio Site : "); else sb1.append("Not an Infill Radio Site : "); // Active_Connection if (bits[23]==true) sb1.append("TS has Network Connection : "); else sb1.append("TS doesn't have a Network Connection : "); // Mask int mask=0; if (bits[24]==true) mask=16; if (bits[25]==true) mask=mask+8; if (bits[26]==true) mask=mask+4; if (bits[27]==true) mask=mask+2; if (bits[28]==true) mask++; sb1.append("Mask="+Integer.toString(mask)+" : "); // Service Function int sf=0; if (bits[29]==true) sf=2; if (bits[30]==true) sf++; sb1.append("Service Function="+Integer.toString(sf)+" : "); // Reg if (bits[35]==true) sb1.append("TSCC demands MS must register"); display[1]=sb1.toString(); // System Identity Code int sysID=utils.retSixteen(bits,40); sb2.append("System Identity Code="+Integer.toString(sysID)+" : "); // MS Address int addr=utils.retAddress(bits,56); sb2.append("MS Individual Address="+Integer.toString(addr)); display[2]=sb2.toString(); } // C_AHOY // Bits // 16,17,18,19,20,21,22 Service_Options_Mirror // 23 Service_Kind_Flag // 24 Ambient Listening Service // 25 IG // 26,27 Appended Blocks // 28,29,30,31 Service_Kind // 32 - 55 Target Address // 56 - 79 Source Address private void csbko28fid0 (DMRDecode theApp,boolean bits[]) { int index; StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); Utilities utils=new Utilities(); // Service_Options_Mirror int service_options_mirror=utils.retSeven(bits,16); // Service_Kind_Flag boolean service_kind_flag=bits[23]; // Ambient Listening Service boolean ambient_listening_service=bits[24]; // IG boolean ig=bits[25]; // Appended Blocks int appended_blocks=0; if (bits[26]==true) appended_blocks=2; if (bits[27]==true) appended_blocks++; // Service_Kind String service_kind; int sk=utils.retFour(bits,28); if (sk==0) service_kind="Individual Voice Call Service"; else if (sk==1) service_kind="Talkgroup Voice Call Service"; else if (sk==2) service_kind="Individual Packet Data Call Service"; else if (sk==3) service_kind="Packet Data Call Service to a talkgroup"; else if (sk==4) service_kind="Individual Short Data Call Service"; else if (sk==5) service_kind="Talkgroup Short Data Call Service"; else if (sk==6) service_kind="Short Data Polling Service"; else if (sk==7) service_kind="Status Transport Service"; else if (sk==8) service_kind="Call Diversion Service"; else if (sk==9) service_kind="Call Answer Service"; else if (sk==13) service_kind="Supplementary Service"; else if (sk==14) service_kind="Registration/Authentication Service/MS Radio Check"; else if (sk==15) service_kind="Cancel Call Service"; else service_kind="Reserved"; // Target address int targetAddr=utils.retAddress(bits,32); // Source address int sourceAddr=utils.retAddress(bits,56); // Display display[0]="C_AHOY : CSBKO=28 + FID=0 : "+service_kind; if (ambient_listening_service==true) display[0]=display[0]+" : ALS Requested : "; if (appended_blocks>0) display[0]=display[0]+Integer.toString(appended_blocks)+" Blocks Appended : "; display[0]=display[0]+"SOM="+Integer.toString(service_options_mirror); if (service_kind_flag==true) display[0]=display[0]+" : SKF=1"; else display[0]=display[0]+" : SKF=0"; sb1.append("Target : "); if (targetAddr==0xFFFECC) sb1.append("STUNI"); else if (targetAddr==0xFFFECF) sb1.append("KILLII"); else if (targetAddr==0xFFFECD) sb1.append("AUTHI"); else sb1.append(Integer.toString(targetAddr)); if (ig==true) sb1.append (" (TG)"); display[1]=sb1.toString(); sb2.append("Source : "); if (sourceAddr==0xFFFECC) sb2.append("STUNI"); else if (sourceAddr==0xFFFECF) sb2.append("KILLII"); else if (sourceAddr==0xFFFECD) sb2.append("AUTHI"); else sb2.append(Integer.toString(sourceAddr)); display[2]=sb2.toString(); // Record this // Log these users // Target if (targetAddr<0xFFFEC0) { theApp.usersLogged.addUser(targetAddr); index=theApp.usersLogged.findUserIndex(targetAddr); if (index!=-1) { if (ig==true) theApp.usersLogged.setAsGroup(index); } } // Source if (sourceAddr<0xFFFEC0) theApp.usersLogged.addUser(sourceAddr); } // CSBKO 40 FID 00 C_BCAST // Bits 16,17,18,19,20 Announcement type // 21 - 34 Broadcast Parms 1 // 35 Reg // 36,37,38,39 Backoff // 40 - 55 System ID // 56 - 79 Broadcast Parms 2 private void csbko40fid0 (DMRDecode theApp,boolean bits[]) { StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); Utilities utils=new Utilities(); // Announcement Type int at=0; String aType; if (bits[16]==true) at=16; if (bits[17]==true) at=at+8; if (bits[18]==true) at=at+4; if (bits[19]==true) at=at+2; if (bits[20]==true) at++; if (at==0) { aType="Ann-WD_TSCC (Announce/Withdraw TSCC)"; // Parms 1 // Bits 21,22,23,24 are reserved int col_ch1=utils.retFour(bits,25); int col_ch2=utils.retFour(bits,29); boolean aw_flag1=bits[33]; boolean aw_flag2=bits[34]; // Parms 2 int bcast_ch1=utils.retTwelve(bits,56); // T_MS-LINE_TIMER int bcast_ch2=utils.retTwelve(bits,68); // Display if (aw_flag1==true) sb1.append("Withdraw BCAST_CH1 (Colour Code "+Integer.toString(col_ch1)+") from the hunt list : "); else sb1.append("Add BCAST_CH1 (Colour Code "+Integer.toString(col_ch1)+") from the hunt list : "); if (aw_flag2==true) sb1.append("Withdraw BCAST_CH2 (Colour Code "+Integer.toString(col_ch2)+") from the hunt list : "); else sb1.append("Add BCAST_CH2 (Colour Code "+Integer.toString(col_ch2)+") from the hunt list : "); sb2.append("BCAST_CH1="+Integer.toString(bcast_ch1)); sb2.append(" : BCAST_CH2="+Integer.toString(bcast_ch2)); display[1]=sb1.toString(); display[2]=sb2.toString(); } else if (at==1) { aType="CallTimer_Parms (Specify Call Timer Parameters)"; // Parms 1 // T_EMERG_TIMER int t_emerg_timer=utils.retNine(bits,21); // T_PACKET_TIMER int t_packet_timer=utils.retFive(bits,30); // Parms 2 // T_MS-MS_TIMER int t_ms_ms_timer=utils.retTwelve(bits,56); // T_MS-LINE_TIMER int t_ms_line_timer=utils.retTwelve(bits,68); // Display these if (t_emerg_timer==512) { sb1.append("Emergency Call Timer is Infinity : "); } else { sb1.append("T_EMERG_TIMER="+Integer.toString(t_emerg_timer)+" : "); } if (t_packet_timer==31) { sb1.append("Packet Call Timer is Infinity : "); } else { sb1.append("T_PACKET_TIMER="+Integer.toString(t_packet_timer)+" : "); } if (t_ms_ms_timer==4095) { sb1.append("MS to MS Call Timer is Infinity : "); } else { sb1.append("T_MS-MS_TIMER="+Integer.toString(t_ms_ms_timer)+" : "); } if (t_ms_line_timer==4095) { sb1.append("Line Connected Call Timer is Infinity"); } else { sb1.append("T_MS-LINE_TIMER="+Integer.toString(t_ms_line_timer)+" : "); } display[1]=sb1.toString(); } else if (at==2) { aType="Vote_Now (Vote Now Advice)"; // Parms1 contains the most significant 14 bits of the TSCC system identity // Parms2 // Bits 56,57 least significant 2 bits of TSCC system identity is 'Manufacturer Specific' VN_ACTION option selected // Bit 58 active connection // Bits 59,60,61,62,63,64 Reserved // Site_Strategy int site_strat=utils.retThree(bits,65); if (site_strat==0) sb1.append("Radio Site : "); else if (site_strat==1) sb1.append("Infill : "); else if (site_strat==2) sb1.append("Manufacturer specific strategy : "); else sb1.append("Reserved : "); // CH_VOTE int ch_vote=utils.retTwelve(bits,68); sb1.append("CH_VOTE is "+Integer.toString(ch_vote)); display[1]=sb1.toString(); } else if (at==3) { aType="Local_Time (Broadcast Local Time)"; // Parms 1 // B_DAY int b_day=utils.retFive(bits,21); // B_MONTH int b_month=utils.retFour(bits,25); // UTC_OFFSET int utc_offset=utils.retFive(bits,30); // Check there is a date if ((b_day>0)&&(b_month>0)) { sb1.append("Date "+Integer.toString(b_day)+"/"+Integer.toString(b_month)+" "); if (utc_offset==31) sb1.append("UTC Offset is "+Integer.toString(utc_offset)+" hours "); } // Parms 2 // B_HOURS int b_hours=utils.retFive(bits,56); // B_MINS int b_mins=utils.retSix(bits,61); // B_SECS int b_secs=utils.retSix(bits,67); // DAYOF_WEEK int dayof_week=utils.retThree(bits,73); // UTC_OFFSET_FRACTION int utc_offset_fraction=0; if (bits[74]==true) utc_offset_fraction=2; if (bits[75]==true) utc_offset_fraction++; // 76,77,78,79 Reserved if (dayof_week==1) sb1.append("Sunday "); else if (dayof_week==2) sb1.append("Monday "); else if (dayof_week==3) sb1.append("Tuesday "); else if (dayof_week==4) sb1.append("Wednesday "); else if (dayof_week==5) sb1.append("Thursday "); else if (dayof_week==6) sb1.append("Friday "); else if (dayof_week==7) sb1.append("Saturday "); if (b_hours<10) sb1.append("0"); sb1.append(Integer.toString(b_hours)+":"); if (b_mins<10) sb1.append("0"); sb1.append(Integer.toString(b_mins)+":"); if (b_secs<10) sb1.append("0"); sb1.append(Integer.toString(b_secs)); if (utc_offset_fraction==1) sb1.append(" (Add 15 mins)"); else if (utc_offset_fraction==1) sb1.append(" (Add 30 mins)"); else if (utc_offset_fraction==2) sb1.append(" (Add 45 mins)"); display[1]=sb1.toString(); } else if (at==4) { aType="MassReg (Mass_Registration)"; // Parms 1 // 21,22,23,24,25 Reserved // Reg_Window int reg_window=utils.retFour(bits,26); // Aloha Mask int aloha_mask=utils.retFive(bits,30); // Parms 2 int ms_individual_address=utils.retAddress(bits,26); // Display this sb1.append("Reg_Window="+Integer.toString(reg_window)+" : Aloha Mask="+Integer.toString(aloha_mask)+" : MS Individual Address "+Integer.toString(ms_individual_address)); display[1]=sb1.toString(); } else if (at==5) { aType="Chan_Freq (Announce a logical channel/frequency relationship)"; // Nothing describing this is in ETSI TS 102 361-4 V1.5.1 so just show binary parms 1 & 2 instead int a; for (a=21;a<35;a++) { if (bits[a]==false) sb1.append("0"); else sb1.append("1"); } sb1.append(" "); for (a=56;a<80;a++) { if (bits[a]==false) sb1.append("0"); else sb1.append("1"); } display[1]=sb1.toString(); } else if (at==6) { aType="Adjacent_Site (Adjacent Site Information)"; // Parms1 contains the most significant 14 bits of the TSCC system identity // Parms2 // Bits 56,57 least significant 2 bits of TSCC system identity is 'Manufacturer Specific' VN_ACTION option selected // Bit 58 active connection // Bits 59,60,61,62,63,64 Reserved // Site_Strategy int site_strat=utils.retThree(bits,65); if (site_strat==0) sb1.append("Radio Site : "); else if (site_strat==1) sb1.append("Infill : "); else if (site_strat==2) sb1.append("Manufacturer specific strategy : "); else sb1.append("Reserved : "); // CH_ADJ int ch_adj=utils.retTwelve(bits,68); sb1.append("CH_ADJ "+Integer.toString(ch_adj)); display[1]=sb1.toString(); } else if ((at==30)||(at==31)) { aType="Manufacturer Specific ("+Integer.toString(at)+")"; // Display the parms binary int a; for (a=21;a<35;a++) { if (bits[a]==false) sb1.append("0"); else sb1.append("1"); } sb1.append(" "); for (a=56;a<80;a++) { if (bits[a]==false) sb1.append("0"); else sb1.append("1"); } display[1]=sb1.toString(); } else aType="Reserved ("+Integer.toString(at)+")"; // System Identity Code int sysID=utils.retSixteen(bits,40); display[0]="C_BCAST : CSBKO=40 + FID=0 : System ID="+Integer.toString(sysID)+" : "+aType; } // C_ACKVIT // Bits .. // 16,17,18,19,20,21,22 Service_Options_Mirror // 23 Service_Kind_Flag // 24,25 Reserved // 26,27 Appended_Blocks // 28,29,30,31 Service_Kind // 32 - 55 Target Address // 56 - 79 Source Address private void csbko30fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Service Options Mirror int service_options_mirror=utils.retSeven(bits,16); display[0]="C_ACKVIT : CSBKO=30 + FID=0 : Service_Options_Mirror="+Integer.toString(service_options_mirror); // Service_Kind_Flag boolean skf=bits[23]; // Appended_Blocks int ablocks=0; if (bits[26]==true) ablocks=2; if (bits[27]==true) ablocks++; // Service_Kind int service_kind=utils.retFour(bits,28); // Display this if (skf==true) sb1.append("Service_Kind_Flag=1 : "); else sb1.append("Service_Kind_Flag=0 : "); sb1.append("Appended_Blocks="+Integer.toString(ablocks)+" : "); sb1.append("Service_Kind="+Integer.toString(service_kind)); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); // Source theApp.usersLogged.addUser(source); } // C_RAND // Bits .. // 16,17,18,19,20,21,22 Service_Options // 23 Proxy Flag // 24,25 Appended_Supplementary_Data // 26,27 Appended_Short_Data // 28,29,30,31 Service_Kind // 32 - 55 Target Address // 56 - 79 Source Address private void csbko31fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Service Options int service_options=utils.retSeven(bits,16); display[0]="C_RAND : CSBKO=31 + FID=0 : Service_Options="+Integer.toString(service_options); // Proxy Flag boolean pf=bits[23]; // Appended_Supplementary_Data int asd=0; if (bits[24]==true) asd=2; if (bits[24]==true) asd++; // Appended_Short_Data int ashortd=0; if (bits[24]==true) ashortd=2; if (bits[25]==true) ashortd++; // Service_Kind int service_kind=utils.retFour(bits,28); // Display this if (pf==true) sb1.append("Proxy Flag=1 : "); else sb1.append("Proxy Flag=0 : "); sb1.append("Appended_Supplementary_Data="+Integer.toString(asd)+" : "); sb1.append("Appended_Short_Data="+Integer.toString(ashortd)+" : "); sb1.append("Service_Kind="+Integer.toString(service_kind)); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); // Source theApp.usersLogged.addUser(source); } // CSBKO 31 FID 16 Call Alert // The information to decode this was kindly provided by bben95 on the Radioreference forums // http://forums.radioreference.com/digital-voice-decoding-software/191957-java-program-decode-dmr-31.html#post2098983 // 0000000000000000 000000000001011101110010 000000000001011101110001 // 1111222222222233 333333334444444444555555 555566666666667777777777 // 6789012345678901 234567890123456789012345 678901234567890123456789 // is Call alert from 6001 to 6002 private void csbko31fid16 (DMRDecode theApp,boolean bits[]) { int a; Utilities utils=new Utilities(); int to=utils.retAddress(bits,32); int from=utils.retAddress(bits,56); StringBuilder sb1=new StringBuilder(300); display[0]="CSBK : CSBKO=31 + FID=16"; sb1.append("Call Alert from "+Integer.toString(from)+" to "+Integer.toString(to)+" ("); // Also display the unknown part as raw binary for now for (a=16;a<32;a++) { if (bits[a]==true) sb1.append("1"); else sb1.append("0"); } sb1.append(")"); display[1]=sb1.toString(); } // C_ACKD // Bits .. // 16,17,18,19,20,21,22 Response_Info // 23,24,25,26,27,28,29,30 Reason Code // 31 Reserved // 32 - 55 Target Address // 56 - 79 Source Address private void csbko32fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Response_Info int response_info=utils.retSeven(bits,16); display[0]="C_ACKD : CSBKO=32 + FID=0 : Response_Info="+Integer.toString(response_info); // Reason Code int reason_code=utils.retEight(bits,23); int rc_t=(reason_code&192)>>6; if (rc_t==0) sb1.append("NACK : "); else if (rc_t==1) sb1.append("ACK : "); else if (rc_t==2) sb1.append("QACK : "); else if (rc_t==3) sb1.append("WACK : "); if ((reason_code&32)>0) sb1.append("TS to MS : "); else sb1.append("MS to TS : "); int ar=reason_code&31; sb1.append(getAckReason(rc_t,ar)); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); // Source theApp.usersLogged.addUser(source); } // CSBKO 32 FID 16 Call Alert Ack // The information to decode this was kindly provided by bben95 on the Radioreference forums // http://forums.radioreference.com/digital-voice-decoding-software/191957-java-program-decode-dmr-31.html#post2098983 // 1001111100000000 000000000001011101110001 000000000001011101110010 // 1111222222222233 333333334444444444555555 555566666666667777777777 // 6789012345678901 234567890123456789012345 678901234567890123456789 // is Call alert from 6001 to 6002: acknowledged private void csbko32fid16 (DMRDecode theApp,boolean bits[]) { int a; Utilities utils=new Utilities(); int to=utils.retAddress(bits,32); int from=utils.retAddress(bits,56); StringBuilder sb1=new StringBuilder(300); display[0]="CSBK : CSBKO=32 + FID=16"; sb1.append("Call Alert ACK from "+Integer.toString(from)+" to "+Integer.toString(to)+" ("); // Also display the unknown part as raw binary for now for (a=16;a<32;a++) { if (bits[a]==true) sb1.append("1"); else sb1.append("0"); } sb1.append(")"); display[1]=sb1.toString(); } // C_ACKU // Bits .. // 16,17,18,19,20,21,22 Response_Info // 23,24,25,26,27,28,29,30 Reason Code // 31 Reserved // 32 - 55 Target Address // 56 - 79 Source Address private void csbko33fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Response_Info int response_info=utils.retSeven(bits,16); display[0]="C_ACKU : CSBKO=33 + FID=0 : Response_Info="+Integer.toString(response_info); // Reason Code int reason_code=utils.retEight(bits,23); int rc_t=(reason_code&192)>>6; if (rc_t==0) sb1.append("NACK : "); else if (rc_t==1) sb1.append("ACK : "); else if (rc_t==2) sb1.append("QACK : "); else if (rc_t==3) sb1.append("WACK : "); if ((reason_code&32)>0) sb1.append("TS to MS : "); else sb1.append("MS to TS : "); int ar=reason_code&31; sb1.append(getAckReason(rc_t,ar)); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); // Source theApp.usersLogged.addUser(source); } // CSBKO 36 FID 16 Radio Check private void csbko36fid16 (DMRDecode theApp,boolean bits[]) { int a; Utilities utils=new Utilities(); int from=utils.retAddress(bits,32); int to=utils.retAddress(bits,56); StringBuilder sb1=new StringBuilder(300); display[0]="CSBK : CSBKO=36 + FID=16"; sb1.append("Radio Check from "+Integer.toString(from)+" to "+Integer.toString(to)+" ("); // Also display the unknown part as raw binary for now for (a=16;a<32;a++) { if (bits[a]==true) sb1.append("1"); else sb1.append("0"); } sb1.append(")"); display[1]=sb1.toString(); } // P_MAINT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Reserved // 28,29,30 Maint Kind // 31 Reserved // 32 - 55 Target Address // 56 - 79 Source Address private void csbko42fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); // Maint Kind String mks; int maint_kind=utils.retThree(bits,28); if (maint_kind==0) mks="Disconnect. End of payload channel use"; else mks="Reserved"; display[0]="P_MAINT : CSBKO=42 + FID=0 : "+mks; // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb1.append("Target Address : "+Integer.toString(target)); sb1.append(" Source Address : "+Integer.toString(source)); display[1]=sb1.toString(); // Log these users // Target theApp.usersLogged.addUser(target); // Source theApp.usersLogged.addUser(source); } // Capacity Plus // The information on this type of packet was kindly provided by Eric Cottrell on the Radioreference forums // see http://forums.radioreference.com/digital-voice-decoding-software/209318-understanding-capacity-plus-trunking-6.html#post2106396 private void big_m_csbko59 (DMRDecode theApp,boolean bits[]) { StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); display[0]="Capacity Plus CSBK : CSBKO=59 + FID=16: SYSSITESTS"; // Bits 16 & 17 First/Last Block int fl=0; if (bits[16]==true) fl=2; if (bits[17]==true) fl++; sb1.append("FL="+Integer.toString(fl)); // Bit 18 slot if (bits[18]==false) sb1.append(" : TS1"); else sb1.append(" : TS2"); // Bits 19,20,21,22,23 Rest Channel ID int restCh=0; if (bits[19]==true) restCh=16; else if (bits[20]==true) restCh=restCh+8; else if (bits[21]==true) restCh=restCh+4; else if (bits[22]==true) restCh=restCh+2; else if (bits[23]==true) restCh++; sb1.append(" : Rest Channel ID "+Integer.toString(restCh)); // Bit 24 ASYNC if (bits[24]==false) sb1.append(" : Periodic Beacons"); else sb1.append(" : Asynchronous Beacons"); // Bits 25,26,27,28 My Site ID int mySiteID=0; if (bits[25]==true) mySiteID=8; if (bits[26]==true) mySiteID=mySiteID+4; if (bits[27]==true) mySiteID=mySiteID+2; if (bits[28]==true) mySiteID++; sb1.append(" : This Site ID "+Integer.toString(mySiteID)); // Display this display[1]=sb1.toString(); // Bits 29.30,31 Number of neighbour sites int nNos=0; if (bits[29]==true) nNos=4; if (bits[30]==true) nNos=nNos+2; if (bits[31]==true) nNos++; // If more than 6 sites we have a problem that will cause an overflow if (nNos>6) nNos=6; // Display the neighbour site info Utilities utils=new Utilities(); int a,pos=32,nsid,nrst; for (a=0;a0) sb2.append(","); sb2.append("Site #"+Integer.toString(a+1)+" ID "+Integer.toString(nsid)+" Rest Ch "+Integer.toString(nrst)); // Move along pos=pos+8; } // If there is any neighbour site info then display it if (nNos>0) display[2]=sb2.toString(); } // P_CLEAR // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28,29,30 Reserved // 31 IG // 32 - 55 Target Address // 56 - 79 Source Address private void csbko46fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Logical channel number int lochan=utils.retTwelve(bits,16); display[0]="P_Clear : CSBKO=46 + FID=0 from channel "+Integer.toString(lochan); // IG boolean ig=bits[31]; // Target address int targetAddr=utils.retAddress(bits,32); // Display this // Is this an ALLMSI if (targetAddr==0xFFFED4) { sb1.append("Target : ALLMSI"); } else { if (ig==true) sb1.append("Target TG :"); else sb1.append("Target : "); sb1.append(Integer.toString(targetAddr)); } display[1]=sb1.toString(); // Source Address int sourceAddr=utils.retAddress(bits,56); sb2.append("Source Address "+Integer.toString(sourceAddr)); display[2]=sb2.toString(); // Record this // Log these users // Target if (targetAddr!=0xFFFED4) { theApp.usersLogged.addUser(targetAddr); index=theApp.usersLogged.findUserIndex(targetAddr); if (index!=-1) { if (ig==true) theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,lochan); } } // Source theApp.usersLogged.addUser(sourceAddr); index=theApp.usersLogged.findUserIndex(sourceAddr); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("P_CLEAR",targetAddr,sourceAddr,lochan,""); } // P_PROTECT // bits // 16,17,18,19,20,21,22,23,24,25,26,27 Reserved // 28,29,30 Protect_Kind // 31 IG // 32 - 55 Target Address // 56 - 79 Source Address private void csbko47fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); // Protect_Kind String protect_kind; int pk=utils.retThree(bits,28); if (pk==0) protect_kind="DIS_PTT (Disable Target MS or Talkgroup transmission)"; else if (pk==1) protect_kind="EN_PTT (Enable Target MS or Talkgroup transmission)"; else if (pk==2) protect_kind="ILLEGALLY_PARKED (Clear down from the payload channel, MS whose address does not match Source or Target Address)"; else protect_kind="Reserved"; display[0]="P_PROTECT : CSBKO=47 + FID=0 : "+protect_kind; // IG boolean ig=bits[31]; // Target address int targetAddr=utils.retAddress(bits,32); // Display this // Is this an ALLMSI if (targetAddr==0xFFFED4) { sb1.append("Target : ALLMSI"); } else { if (ig==true) sb1.append("Target TG :"); else sb1.append("Target : "); sb1.append(Integer.toString(targetAddr)); } display[1]=sb1.toString(); // Source Address int sourceAddr=utils.retAddress(bits,56); sb2.append("Source Address "+Integer.toString(sourceAddr)); display[2]=sb2.toString(); // Record this // Log these users // Target if (targetAddr!=0xFFFED4) { theApp.usersLogged.addUser(targetAddr); index=theApp.usersLogged.findUserIndex(targetAddr); if (index!=-1) { if (ig==true) theApp.usersLogged.setAsGroup(index); } } // Source theApp.usersLogged.addUser(sourceAddr); index=theApp.usersLogged.findUserIndex(sourceAddr); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("P_PROTECT",targetAddr,sourceAddr,0,protect_kind); } // PV_GRANT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28 TDMA Channel // 29 OVCM // 30 Emergency // 31 Offset // 32 - 55 Target Address // 56 - 79 Source Address private void csbko48fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="Private Voice Channel Grant : CSBKO=48 + FID=0"; // Logical Physical Channel Number int lchannel=utils.retTwelve(bits,16); sb1.append("Payload Channel "+Integer.toString(lchannel)); if (bits[28]==false) sb1.append(" TDMA ch1 "); else sb1.append(" TDMA ch2 "); if (bits[29]==true) sb1.append(": OVCM Call "); if (bits[30]==true) sb1.append(": Emergency Call "); if (bits[31]==false) sb1.append(": Aligned Timing"); else sb1.append(": Offset Timing"); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("PV_GRANT from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Private Voice Channel Grant",target,source,lchannel,display[1]); } // TV_GRANT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28 TDMA Channel // 29 OVCM // 30 Emergency // 31 Offset // 32 - 55 Target Address // 56 - 79 Source Address private void csbko49fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="Talkgroup Voice Channel Grant : CSBKO=49 + FID=0"; // Logical Physical Channel Number int lchannel=utils.retTwelve(bits,16); sb1.append("Payload Channel "+Integer.toString(lchannel)); if (bits[28]==false) sb1.append(" TDMA ch1 "); else sb1.append(" TDMA ch2 "); if (bits[29]==true) sb1.append(": OVCM Call "); if (bits[30]==true) sb1.append(": Emergency Call "); if (bits[31]==false) sb1.append(": Aligned Timing"); else sb1.append(": Offset Timing"); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,lchannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsGroupUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("TV_GRANT from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Talkgroup Voice Channel Grant",target,source,lchannel,display[1]); } // BTV_GRANT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28 TDMA Channel // 29 OVCM // 30 Emergency // 31 Offset // 32 - 55 Target Address // 56 - 79 Source Address private void csbko50fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="Broadcast Talkgroup Voice Channel Grant : CSBKO=50 + FID=0"; // Logical Physical Channel Number int lchannel=utils.retTwelve(bits,16); sb1.append("Payload Channel "+Integer.toString(lchannel)); if (bits[28]==false) sb1.append(" TDMA ch1 "); else sb1.append(" TDMA ch2 "); if (bits[29]==true) sb1.append(": OVCM Call "); if (bits[30]==true) sb1.append(": Emergency Call "); if (bits[31]==false) sb1.append(": Aligned Timing"); else sb1.append(": Offset Timing"); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,lchannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsGroupUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("BTV_GRANT from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Broadcast Talkgroup Voice Channel Grant",target,source,lchannel,display[1]); } // PD_GRANT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28 TDMA Channel // 29 Packet Mode // 30 Emergency // 31 Offset // 32 - 55 Target Address // 56 - 79 Source Address private void csbko51fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="Private Data Channel Grant : CSBKO=51 + FID=0"; // Logical Physical Channel Number int lchannel=utils.retTwelve(bits,16); sb1.append("Payload Channel "+Integer.toString(lchannel)); if (bits[28]==false) sb1.append(" TDMA ch1 "); else sb1.append(" TDMA ch2 "); if (bits[29]==true) sb1.append(": Payload Channel uses 1:1 mode "); else sb1.append(": Payload Channel uses 2:1 mode "); if (bits[30]==true) sb1.append(": Emergency Call "); if (bits[31]==false) sb1.append(": Aligned Timing"); else sb1.append(": Offset Timing"); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("PD_GRANT from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Private Data Channel Grant",target,source,lchannel,display[1]); } // TD_GRANT // Bits .. // 16,17,18,19,20,21,22,23,24,25,26,27 Logical Physical Channel Number // 28 TDMA Channel // 29 Packet Mode // 30 Emergency // 31 Offset // 32 - 55 Target Address // 56 - 79 Source Address private void csbko52fid0 (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="Talkgroup Data Channel Grant : CSBKO=52 + FID=0"; // Logical Physical Channel Number int lchannel=utils.retTwelve(bits,16); sb1.append("Payload Channel "+Integer.toString(lchannel)); if (bits[28]==false) sb1.append(" TDMA ch1 "); else sb1.append(" TDMA ch2 "); if (bits[29]==true) sb1.append(": Payload Channel uses 1:1 mode "); else sb1.append(": Payload Channel uses 2:1 mode "); if (bits[30]==true) sb1.append(": Emergency Call "); if (bits[31]==false) sb1.append(": Aligned Timing"); else sb1.append(": Offset Timing"); display[1]=sb1.toString(); // Target address int target=utils.retAddress(bits,32); // Source address int source=utils.retAddress(bits,56); sb2.append("Target Address : "+Integer.toString(target)); sb2.append(" Source Address : "+Integer.toString(source)); display[2]=sb2.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,lchannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsGroupUser(index); theApp.usersLogged.setChannel(index,lchannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("TD_GRANT from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Talkgroup Data Channel Grant",target,source,lchannel,display[1]); } // C_MOVE // Bits .. // 16,17,18,19,20,21,22,23,24 Reserved // 25,26,27,28,29 Mask // 30,31,32,33,34 Reserved // 35 Reg // 36,37,38,39 Backoff // 40,41,42,43 Reserved // 44 - 55 Physical Channel Number // 56 - 79 MS Individual Address private void csbko57fid0 (DMRDecode theApp,boolean bits[]) { Utilities utils=new Utilities(); StringBuilder sb1=new StringBuilder(250); StringBuilder sb2=new StringBuilder(250); display[0]="C_MOVE : CSBKO=57 + FID=0"; // Mask int mask=utils.retFive(bits,25); sb1.append("Mask="+Integer.toString(mask)+" : "); // Reg if (bits[35]==true) sb1.append("TSCC demands MS must register : "); // Backoff int backoff=utils.retFour(bits,36); sb1.append("Backoff="+Integer.toString(backoff)); display[1]=sb1.toString(); // Physical Channel Number int chanNo=utils.retTwelve(bits,44); sb2.append("Physical Channel Number "+Integer.toString(chanNo)+" : "); // MS Individual Address int msi=utils.retAddress(bits,56); sb2.append("MS Individual Address "+Integer.toString(msi)); display[2]=sb2.toString(); } // Return a ACK reason string private String getAckReason (int ack_type,int ack_num) { // ACK if (ack_type==1) { if (ack_num==0b01100000) return "Message_Accepted"; else if (ack_num==0b01100001) return "Store_Forward"; else if (ack_num==0b01100010) return "Reg_Accepted"; else if (ack_num==0b01100011) return "Accepted for the Status Polling Service"; else if (ack_num==0b01000100) return "MS_Accepted"; else if (ack_num==0b01000101) return "CallBack"; else if (ack_num==0b01000110) return "MS_ALERTING"; else if (ack_num==0b01000111) return "Accepted for the Status Polling Service"; } // NACK else if (ack_type==0) { if (ack_num==0b00100000) return "Not_Supported"; else if (ack_num==0b00100001) return "Perm_User_Refused"; else if (ack_num==0b00100010) return "Temp_User_Refused"; else if (ack_num==0b00100011) return "Transient_Sys_Refused"; else if (ack_num==0b00100100) return "NoregMSaway_Refused"; else if (ack_num==0b00100101) return "MSaway_Refused"; else if (ack_num==0b00100110) return "Div_Cause_Fail"; else if (ack_num==0b00100111) return "SYSbusy_Refused"; else if (ack_num==0b00101000) return "SYS_NotReady"; else if (ack_num==0b00101001) return "Call_Cancel_Refused"; else if (ack_num==0b00101010) return "Reg_Refused"; else if (ack_num==0b00101011) return "Reg_Denied"; else if (ack_num==0b00101100) return "IP_Connection_failed"; else if (ack_num==0b00101101) return "MS_Not_Registered"; else if (ack_num==0b00101110) return "Called_Party_Busy"; else if (ack_num==0b00000000) return "MSNot_Supported"; else if (ack_num==0b00010001) return "LineNot_Supported"; else if (ack_num==0b00010010) return "StackFull_Refused"; else if (ack_num==0b00010011) return "EuipBusy_Refused"; else if (ack_num==0b00010100) return "Recipient_Refused"; else if (ack_num==0b00010101) return "Custom_Refused"; } // QACK else if (ack_type==2) { if (ack_num==0b10100000) return "Queued-for-resource"; else if (ack_num==0b10100001) return "Queued-for-busy"; } // WACK else if (ack_type==3) { if (ack_num==0b11100000) return "Wait"; } return "Unknown (at="+Integer.toString(ack_type)+" + ack_num="+Integer.toString(ack_num)+")"; } } ================================================ FILE: src/main/java/com/dmr/CSVFileFilter.java ================================================ package com.dmr; import java.io.File; public class CSVFileFilter extends javax.swing.filechooser.FileFilter { public boolean accept(File f) { // if it is a directory -- we want to show it so return true. if (f.isDirectory()) return true; // get the extension of the file String extension = getExtension(f); // check to see if the extension is equal to "csv" if (extension.equals("csv")) return true; // default -- fall through. False is return on all // occasions except: // a) the file is a directory // b) the file's extension is what we are looking for. return false; } /** * Again, this is declared in the abstract class The description of this * filter */ public String getDescription() { return "CSV files"; } /** * Method to get the extension of the file, in lowercase */ private String getExtension(File f) { String s=f.getName(); int i=s.lastIndexOf('.'); if (i>0&&i0)&&(jitter<=SYMBOLCENTRE)) i--; else if ((jitter>SYMBOLCENTRE)&&(jittercentre) { // Was the last sample less than the centre ? if (lastSamplecentre) { // Yes we have a zero crossing if (frameSync==false) jitter=i; else processJitter(i); } } // Sample the symbol from its centre if ((i>=SYMBOLCENTRE)&&(i<=SYMBOLCENTRE+1)) { sum=sum+sample; count++; } // Make copy of this sample for later comparison lastSample=sample; } symbol=(sum/count); symbolcnt++; return symbol; } // Add the calculated jitter value to the jitter circular buffer private void processJitter (int jit) { jitterBuffer[jitterCounter]=jit; jitterCounter++; if (jitterCounter==JITTERCOUNTERSIZE) { jitterCounter=0; // Set the jitter to the mode value of the jitter buffer jitter=calcJitterMode(); } } // Calculate which jitter value occurs the most (the mode) and return it private int calcJitterMode() { int a,b,high=0,highMode=0,tmode; for (a=0;ahighMode) { high=a; highMode=tmode; } } return high; } // Grab 144 dibits then check if they have a sync pattern and if they do then process // them accordingly public int getFrameSync () { int t=0,dibit,symbol,synctest_pos=0,syncType; int lmin=0,lmax=0,a,highVol; // Clear the symbol counter symbolcnt=0; while (true) { t++; // Get a symbol from the soundcard symbol=getSymbol(frameSync); // Store this in the rotating symbol buffer addToSymbolBuffer(symbol); // If needed pass the data to the display bar if (enableDisplayBar==true) window.displaySymbol(symbol); // Set the dibit state dibit=symboltoDibit(symbol); // Add the dibit to the circular dibit buffer addToDitbitBuf(dibit); // If we have received 144 dibits then we can check for a valid sync sequence if (t>=144) { // If we don't have frame sync then rotate the symbol buffer // and also find the new minimum and maximum if ((frameSync==false)||((frameSync==true)&&(symbolcnt%144==0))) { // Get the frames 24 sync symbols syncHighLowlBuf=getSyncSymbols(); lmin=1; lmax=-1; for (a=0;a<24;a++) { if (syncHighLowlBuf[a]lmax) lmax=syncHighLowlBuf[a]; } } // Update the volume bar every 25 frames if ((t%3600)==0) { highVol=lineInThread.returnVolumeAverage(); window.updateVolumeBar(highVol); } // Check if a frame has a voice or data sync // If no frame sync do this at any time but if we do have // frame sync then only do this every 144 bits if ((frameSync==false)||((frameSync==true)&&(symbolcnt%144==0))) { // Identify the frame sync type which returns // 0 if unknown // 1 if voice // 2 if data syncType=syncCompare(frameSync); // Embedded signalling frame (BS/MS and Direct) if ((frameSync==true)&&(syncType==0)&&(firstframe==false)&&(embeddedFrameCount<7)) { // Increment the embedded frame counter embeddedFrameCount++; lastsynctype=13; return (13); } // BS Data frame if (syncType==2) { // Clear the embedded frame counter embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=10; mode=0; return (10); } // BS Voice frame else if (syncType==1) { // Clear the embedded frame counter embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=12; mode=0; return (12); } // MS Data frame else if (syncType==4) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=20; mode=1; return (20); } // MS Voice frame else if (syncType==3) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=22; mode=1; return (22); } // RC Sync else if (syncType==5) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=25; mode=1; return (25); } // Direct Voice frame 1 else if (syncType==6) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=30; mode=2; return (30); } // Direct Data frame 1 else if (syncType==7) { carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=31; mode=2; return (31); } // Direct Voice frame 2 else if (syncType==8) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=32; mode=2; return (32); } // Direct Data frame 2 else if (syncType==9) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=33; mode=2; return (33); } // Rest Data else if (syncType==10) { embeddedFrameCount=0; carrier=true; if (frameSync==false) { frameCalcs(lmin,lmax); frameSync=true; } else addToMinMaxBuffer(lmin,lmax); if (lastsynctype==-1) firstframe=true; else firstframe=false; lastsynctype=33; mode=0; return (40); } } } // We had a signal but appear to have lost it if (carrier==true) { // If we have missed 12 frames then something is wrong if (synctest_pos>=(144*12)) { // If in debug mode show that sync has been lost if (debug==true) { StringBuilder l=new StringBuilder(250); l.append(getTimeStamp()+" Sync Lost"); l.append(" : centre="+Integer.toString(centre)); l.append(" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); addLine(l.toString(),Color.BLACK,plainFont); fileWrite(l.toString()); } frameSync=false; noCarrier(); return (-1); } } // If the hunt has gone on for a while then reset everything if (t>32000) { t=0; synctest_pos=0; } else synctest_pos++; } } // Add a dibit to the circular dibit buffer void addToDitbitBuf (int dibit) { dibitCircularBuffer[dibitCircularBufferCounter]=(byte)dibit; dibitCircularBufferCounter++; if (dibitCircularBufferCounter==144) dibitCircularBufferCounter=0; } // Add a symbol to the circular symbol buffer void addToSymbolBuffer (int symbol) { symbolBuffer[symbolBufferCounter]=symbol; symbolBufferCounter++; if (symbolBufferCounter==144) symbolBufferCounter=0; } // No carrier or carrier lost so clear the variables public void noCarrier () { jitter=-1; lastsynctype=-1; carrier=false; max=MAXSTARTVALUE; min=MINSTARTVALUE; centre=0; firstframe=false; errorFreeFrameCount=0; continousBadFrameCount=0; mode=-1; // Update the status bar window.updateSyncLabel(false); window.setCh1Label("Unused",labelQuiteColour); window.setCh2Label("Unused",labelQuiteColour); window.SetColourCodeLabel(-1,labelQuiteColour); window.setSystemLabel("System : Unknown",labelQuiteColour); } // Given a symbol return a dibit int symboltoDibit (int symbol) { // With Sync if (frameSync==true) { if (inverted==false) { // Normal if (symbol>centre) { if (symbol>umid) return 1; else return 0; } else { if (symbolcentre) { if (symbol>umid) return 3; else return 2; } else { if (symbol0) return 1; else return 3; } // Inverted else { if (symbol>0) return 3; else return 1; } } } // Compare the sync sequences held in global arrays with the contents of the dibit circular buffer which returns .. // 00 unknown // 01 BS voice // 02 BS data // 03 MS voice // 04 MS data // 05 RC Sync // 06 Direct Voice 1 // 07 Direct Data 1 // 08 Direct Voice 2 // 09 Direct Data 2 // 10 Rest Data private int syncCompare(boolean sync) { int i,dataSyncBS=0,voiceSyncBS=0,diff,circPos,dataSyncMS=0,voiceSyncMS=0,rcSync=0; int directVoice1=0,directVoice2=0,directData1=0,directData2=0,restData=0; // Allow 5 dibits to be incorrect when syncronised and set the offset if (sync==true) diff=5; else diff=0; circPos=dibitCircularBufferCounter+66; if (circPos>=144) circPos=circPos-144; for (i=0;i<24;i++) { // BS if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_BS[i]) voiceSyncBS++; if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_BS[i]) dataSyncBS++; // MS if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_MS[i]) voiceSyncMS++; if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_MS[i]) dataSyncMS++; if (dibitCircularBuffer[circPos]==DMR_RC_SYNC[i]) rcSync++; // Direct Slot 1 if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_DIRECT1[i]) directVoice1++; if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_DIRECT1[i]) directData1++; // Direct Slot 2 if (dibitCircularBuffer[circPos]==DMR_VOICE_SYNC_DIRECT2[i]) directVoice2++; if (dibitCircularBuffer[circPos]==DMR_DATA_SYNC_DIRECT2[i]) directData2++; // Rest Data if (dibitCircularBuffer[circPos]==DMR_DATA_REST_SYNC_BS[i]) restData++; // Increment the circular buffer counter circPos++; if (circPos==144) circPos=0; } if ((DMR_VOICE_SYNC_BS.length-voiceSyncBS)<=diff) return 1; else if ((DMR_DATA_SYNC_BS.length-dataSyncBS)<=diff) return 2; else if ((DMR_VOICE_SYNC_MS.length-voiceSyncMS)<=diff) return 3; else if ((DMR_DATA_SYNC_MS.length-dataSyncMS)<=diff) return 4; else if ((DMR_RC_SYNC.length-rcSync)<=diff) return 5; else if ((DMR_VOICE_SYNC_DIRECT1.length-directVoice1)<=diff) return 6; else if ((DMR_DATA_SYNC_DIRECT1.length-directData1)<=diff) return 7; else if ((DMR_VOICE_SYNC_DIRECT2.length-directVoice2)<=diff) return 8; else if ((DMR_DATA_SYNC_DIRECT2.length-directData2)<=diff) return 9; else if ((DMR_DATA_REST_SYNC_BS.length-restData)<=diff) return 10; else return 0; } // Extract just the 24 symbols of the sync sequence and return them in an array private int[] getSyncSymbols() { int i,circPos; int syms[]=new int[24]; circPos=symbolBufferCounter+66; if (circPos>=144) circPos=circPos-144; for (i=0;i<24;i++) { syms[i]=symbolBuffer[circPos]; circPos++; if (circPos==144) circPos=0; } return syms; } // Returns the current sync type public int getSyncType() { return this.synctype; } // Adds a line to the display as long as pause isn't enabled public void addLine(final String line, final Color col, final Font font) { if (pauseScreen==true) return; else display_view.add_line(line,col,font); } // Return a time stamp public String getTimeStamp() { Date now=new Date(); DateFormat df=DateFormat.getTimeInstance(); return df.format(now); } // Return a date stamp public String getDateStamp() { Date now=new Date(); DateFormat df=DateFormat.getDateInstance(); return df.format(now); } // Handle an incoming DMR Frame void processFrame () { if (firstframe==true) { // Clear the max min buffer counter so we don't use old values maxminBufferCounter=0; // If debug enabled record obtaining sync if (debug==true) { StringBuilder l=new StringBuilder(250); if (synctype==12) l.append(getTimeStamp()+" DMR BS Voice Sync Acquired"); else if (synctype==10) l.append(getTimeStamp()+" DMR BS Data Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==22) l.append(getTimeStamp()+" DMR MS Voice Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==20) l.append(getTimeStamp()+" DMR MS Data Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==25) l.append(getTimeStamp()+" DMR RC Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==30) l.append(getTimeStamp()+" DMR Direct Voice 1 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==31) l.append(getTimeStamp()+" DMR Direct Data Data 1 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==32) l.append(getTimeStamp()+" DMR Direct Voice 2 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==33) l.append(getTimeStamp()+" DMR Direct Data Data 2 Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); else if (synctype==40) l.append(getTimeStamp()+" DMR BS Data Rest Sync Acquired : centre="+Integer.toString(centre)+" max="+Integer.toString(max)+" min="+Integer.toString(min)+" umid="+Integer.toString(umid)+" lmid="+Integer.toString(lmid)); addLine(l.toString(),Color.BLACK,plainFont); fileWrite(l.toString()); } return; } // Update the sync label window.updateSyncLabel(frameSync); // Deal with the frame if ((synctype==12)||(synctype==22)||(synctype==30)||(synctype==32)) processDMRvoice(); else if ((synctype==10)||(synctype==20)||(synctype==31)||(synctype==33)||(synctype==40)) processDMRdata (); else if (synctype==13) processEmbedded (); } // Handle a DMR Voice Frame void processDMRvoice () { DMRVoice DMRvoice=new DMRVoice(); Font font[]=new Font[10]; Color lcol[]=new Color[10]; String line[]=new String[10]; line=DMRvoice.decode(theApp,dibitFrame); font=DMRvoice.getFonts(); lcol=DMRvoice.getColours(); frameCount++; if (DMRvoice.isError()==false) { badFrameCount++; continousBadFrameCount++; line[0]=getTimeStamp()+" DMR Voice Frame - Error !"; lcol[0]=Color.RED; } else { continousBadFrameCount=0; } if (debug==true) { line[0]=line[0]+dispSymbolsSinceLastFrame(); lcol[8]=Color.BLACK; lcol[9]=Color.BLACK; font[8]=plainFont; font[9]=plainFont; line[8]=returnDibitBufferPercentages(); line[9]=displayDibitBuffer(); } // If this frame contains errors and the user wants to display only good ones // then stop them being shown if ((DMRvoice.isError()==false)&&(displayOnlyGoodFrames==true)) DMRvoice.setShouldDisplay(false); // Display the info if ((DMRvoice.getShouldDisplay()==true)&&(displayVoiceFrames==true)) displayLines(line,lcol,font); } // Handle a DMR Data Frame void processDMRdata () { DMRDataDecode DMRdata=new DMRDataDecode(); Font font[]=new Font[10]; Color lcol[]=new Color[10]; String line[]=new String[10]; line=DMRdata.decode(theApp,dibitFrame); font=DMRdata.getFonts(); lcol=DMRdata.getColours(); frameCount++; if (DMRdata.isError()==false) { badFrameCount++; line[0]=getTimeStamp()+" DMR Data Frame - Error !"; lcol[0]=Color.RED; font[0]=plainFont; line[2]=null; // Record that there has been a frame with an error errorFreeFrameCount=0; continousBadFrameCount++; } else { // Record that there has been an error free frame errorFreeFrameCount++; } if (debug==true) { line[0]=line[0]+dispSymbolsSinceLastFrame(); lcol[8]=Color.BLACK; lcol[9]=Color.BLACK; font[8]=plainFont; font[9]=plainFont; line[8]=returnDibitBufferPercentages(); line[9]=displayDibitBuffer(); } // If this frame contains errors and the user wants to display only good ones // then stop them being shown if ((DMRdata.isError()==false)&&(displayOnlyGoodFrames==true)) DMRdata.setShouldDisplay(false); // Display the info if (DMRdata.getShouldDisplay()==true) displayLines(line,lcol,font); } // Handle an embedded frame void processEmbedded () { DMREmbedded DMRembedded=new DMREmbedded(); Color lcol[]=new Color[10]; Font font[]=new Font[10]; String line[]=new String[10]; line=DMRembedded.decode(theApp,dibitFrame); font=DMRembedded.getFonts(); lcol=DMRembedded.getColours(); frameCount++; if (DMRembedded.isError()==false) { badFrameCount++; line[0]=getTimeStamp()+" DMR Embedded Frame - Error !"; lcol[0]=Color.RED; font[0]=plainFont; line[2]=null; // Record that there has been a frame with an error errorFreeFrameCount=0; continousBadFrameCount++; } else { // Set last sync type to 14 to show this was a good embedded frame lastsynctype=14; continousBadFrameCount=0; } if (debug==true) { line[0]=line[0]+dispSymbolsSinceLastFrame(); lcol[8]=Color.BLACK; lcol[9]=Color.BLACK; font[8]=plainFont; font[9]=plainFont; line[8]=returnDibitBufferPercentages(); line[9]=displayDibitBuffer(); } // If this frame contains errors and the user wants to display only good ones // then stop them being shown if ((DMRembedded.isError()==false)&&(displayOnlyGoodFrames==true)) DMRembedded.setShouldDisplay(false); // Display the info if (DMRembedded.getShouldDisplay()==true) displayLines(line,lcol,font); } // Display a group of lines void displayLines (String line[],Color col[],Font font[]) { int a; int len=line.length; for (a=(len-1);a>=0;a--) { if (line[a]!=null) addLine(line[a],col[a],font[a]); } // Log to disk if needed if (logging==true) { for (a=0;a48000) { closeCaptureFile(); captureMode=false; } } // Write a line to the debug file public void debugDump (String line) { try { FileWriter dfile=new FileWriter("debug.csv",true); dfile.write(line); dfile.write("\r\n"); dfile.flush(); dfile.close(); }catch (Exception e) { System.err.println("Error: " + e.getMessage()); } } // Display the dibit buffer as a string public String displayDibitBuffer () { StringBuilder lb=new StringBuilder(500); int a; for (a=0;a<144;a++) { lb.append(Integer.toString(dibitFrame[a])); } return lb.toString(); } // Return a string showing the percentages of each dibit in the dibit buffer public String returnDibitBufferPercentages () { StringBuilder dline=new StringBuilder(500); int a,c0=0,c1=0,c2=0,c3=0; float mp=(float) (144-24.0); for (a=0;a<144;a++) { // Exclude the sync burst from the percentages if ((a<66)||(a>89)) { if (dibitFrame[a]==0) c0++; if (dibitFrame[a]==1) c1++; if (dibitFrame[a]==2) c2++; if (dibitFrame[a]==3) c3++; } } c0=(int)(((float)c0/mp)*(float)100); c1=(int)(((float)c1/mp)*(float)100); c2=(int)(((float)c2/mp)*(float)100); c3=(int)(((float)c3/mp)*(float)100); // Write this to a line dline.append("Dibit 0="+Integer.toString(c0)+"% "); dline.append("Dibit 1="+Integer.toString(c1)+"% "); dline.append("Dibit 2="+Integer.toString(c2)+"% "); dline.append("Dibit 3="+Integer.toString(c3)+"% "); return dline.toString(); } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug=debug; } // Put the dibits into dibitFrame in the correct order from the circular dibit buffer private void createDibitFrame() { int i,circPos; circPos=dibitCircularBufferCounter-144; if (circPos<0) circPos=144+circPos; for (i=0;i<144;i++) { dibitFrame[i]=dibitCircularBuffer[circPos]; circPos++; if (circPos==144) circPos=0; } } // Set the audio capture mode public void setCapture (boolean c) { if ((captureMode==false)&&(c==true)) { openCaptureFile(); captureCount=0; } else if ((captureMode==true)&&(c==false)) { closeCaptureFile(); captureMode=false; } } // Tell the program if it is in audio capture mode public boolean isCapture (){ return captureMode; } // Open the capture file private void openCaptureFile() { try { captureFile=new FileWriter("capture_dump.csv"); captureMode=true; } catch (Exception e) { captureMode=false; } } // Close the capture file private void closeCaptureFile() { try { captureFile.flush(); captureFile.close(); } catch (Exception e) { JOptionPane.showMessageDialog(null,"Error closing the capture file","DMRDecode", JOptionPane.INFORMATION_MESSAGE); } // We aren't in capture mode any longer captureMode=false; } // Enable or disable the symbol display bar public void setEnableDisplayBar(boolean enableDisplayBar) { this.enableDisplayBar=enableDisplayBar; window.switchDisplayBar(this.enableDisplayBar); } // Tell other classes if the symbol display bar is enabled or disabled public boolean isEnableDisplayBar() { return enableDisplayBar; } // Add a sample to the samples ahead buffer private void addToSamplesAheadBuffer (int sam) { samplesAheadBuffer[samplesAheadCounter]=sam; samplesAheadCounter++; if (samplesAheadCounter==SAMPLESAHEADSIZE) samplesAheadCounter=0; } // Get the oldest sample from the samples ahead buffer private int getOldestSample() { return samplesAheadBuffer[samplesAheadCounter]; } // Get a sample either from the sound card private int getSample (boolean jitmode) { int sample=0; // Get the sample from the sound card via the sound thread try { sample=inPipeData.readInt(); } catch (Exception e) { JOptionPane.showMessageDialog(null,"Error in getSample()","DMRDecode", JOptionPane.INFORMATION_MESSAGE); } // If in capture mode record the sample in the capture file // but don't do this in jitter adjust mode if ((captureMode==true)&&(jitmode==false)) audioDump(sample); // Add this to the circular samples ahead buffer addToSamplesAheadBuffer(sample); // Pull the oldest sample from the circular samples ahead buffer return getOldestSample(); } // The the max and min values to a circular buffer of values private void addToMinMaxBuffer (int tmin,int tmax) { maxBuffer[maxminBufferCounter]=tmax; minBuffer[maxminBufferCounter]=tmin; maxminBufferCounter++; // When the buffer reaches its maximum size use calculate new parameters if (maxminBufferCounter==MAXMINBUFSIZE) { maxminBufferCounter=0; calcAverageMinMax(); } } // Calculate new min and max parameters as averages from the circular buffer private void calcAverageMinMax() { int a,totalmax=0,totalmin=0; for (a=0;a"; xmlfile.write(line); // Invert line=""; xmlfile.write(line); // Enable Symbol Display line=""; xmlfile.write(line); // Display CACH line=""; xmlfile.write(line); // Only display good frames line=""; xmlfile.write(line); // Display IDLE PDUs line=""; xmlfile.write(line); // Display Voice Frames line=""; xmlfile.write(line); // Save the current audio source line=""; xmlfile.write(line); // All done so close the root item // line=""; xmlfile.write(line); // Flush and close the file // xmlfile.flush(); xmlfile.close(); } catch (Exception e) { JOptionPane.showMessageDialog(null,"Error : Unable to create the file DMRDecode_settings.xml\n"+e.toString(),"Rivet", JOptionPane.ERROR_MESSAGE); return false; } return true; } // Read in the DMRDecode_settings.xml file // public void readDefaultSettings() throws SAXException, IOException,ParserConfigurationException { // Create a parser factory and use it to create a parser SAXParserFactory parserFactory=SAXParserFactory.newInstance(); SAXParser parser=parserFactory.newSAXParser(); // This is the name of the file you're parsing String filename="DMRDecode_settings.xml"; // Instantiate a DefaultHandler subclass to handle events saxHandler handler=new saxHandler(); // Start the parser. It reads the file and calls methods of the handler. parser.parse(new File(filename),handler); } public boolean isDisplayVoiceFrames() { return displayVoiceFrames; } public void setDisplayVoiceFrames(boolean displayVoiceFrames) { this.displayVoiceFrames = displayVoiceFrames; } // Change the audio mixer public boolean changeMixer(String mixerName) { // Tell the audio in thread to change its mixer return lineInThread.changeMixer(mixerName); } // This class handles the SAX events public class saxHandler extends DefaultHandler { String value; public void endElement(String namespaceURI,String localName,String qName) throws SAXException { } public void characters(char[] ch,int start,int length) throws SAXException { // Extract the element value as a string // String tval=new String(ch); value=tval.substring(start,(start+length)); } // Handle an XML start element // public void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException { // Check an element has a value // if (attributes.getLength()>0) { // Get the elements value // String aval=attributes.getValue(0); // Debug mode // if (qName.equals("debug")) { if (aval.equals("TRUE")) setDebug(true); else setDebug(false); } // Invert if (qName.equals("invert")) { if (aval.equals("TRUE")) inverted=true; else inverted=false; } // Symbol display if (qName.equals("symbolDisplay")) { if (aval.equals("TRUE")) enableDisplayBar=true; else enableDisplayBar=false; } // Display CACH if (qName.equals("displayCACH")) { if (aval.equals("TRUE")) displayCACH=true; else displayCACH=false; } // Display only good frames if (qName.equals("goodFramesOnly")) { if (aval.equals("TRUE")) displayOnlyGoodFrames=true; else displayOnlyGoodFrames=false; } // Display Idle PDUs if (qName.equals("idlePDU")) { if (aval.equals("TRUE")) displayIdlePDU=true; else displayIdlePDU=false; } // Display Voice Frames if (qName.equals("voiceFrames")) { if (aval.equals("TRUE")) displayVoiceFrames=true; else displayVoiceFrames=false; } // The audio input source if (qName.equals("audioDevice")) { if (lineInThread.changeMixer(aval)==false) { JOptionPane.showMessageDialog(null,"Error changing mixer\n"+lineInThread.getMixerErrorMessage(),"DMRDecode",JOptionPane.ERROR_MESSAGE); } } } } } // Data types are as follows .. // 01 - Unconfirmed // 02 - Confirmed // 03 - Response // 04 - Proprietary // 05 - Status/Precoded // 06 - Raw Short // 07 - Defined Short // 08 - Unified Data Transport // Set the current channels incoming data type // also clear the current channels data block counter public void setCurrentIncomingDataType (int type) { if (currentChannel==1) { incomingDataType[0]=type; dataBlocksReceived[0]=0; } else { incomingDataType[1]=type; dataBlocksReceived[1]=0; } } // Get the current channels incoming data type public int getCurrentIncomingDataType () { if (currentChannel==1) return incomingDataType[0]; else return incomingDataType[1]; } // Set the current channels data blocks to follow public void setCurrentDataBlocksToFollow (int blocks) { if (currentChannel==1) dataBlocksToFollow[0]=blocks; else dataBlocksToFollow[1]=blocks; } // Get the current channels data blocks to follow public int getCurrentDataBlocksToFollow () { if (currentChannel==1) return dataBlocksToFollow[0]; else return dataBlocksToFollow[1]; } // Get the current channels received data blocks counter public int getCurrentDataBlocksReceived () { if (currentChannel==1) return dataBlocksReceived[0]; else return dataBlocksReceived[1]; } // Increment the current channels data block counter public void incrementCurrentDataBlocksReceived () { if (currentChannel==1) dataBlocksReceived[0]++; else dataBlocksReceived[1]++; } // A getter for the mode public int getMode () { return this.mode; } } ================================================ FILE: src/main/java/com/dmr/DMREmbedded.java ================================================ package com.dmr; import java.awt.Color; import java.awt.Font; public class DMREmbedded { private int residueValue; private String line[]=new String[10]; private Font fonts[]=new Font[10]; private Color colours[]=new Color[10]; private boolean resCACH,resEMB; private boolean shouldDisplay=true; private DMRDecode theApp; public String[] decode (DMRDecode TtheApp,byte[] dibit_buf) { String cline; theApp=TtheApp; int mode=theApp.getMode(); // BS only if (mode==0) { DecodeCACH cachdecode=new DecodeCACH(); // CACH decode cline=cachdecode.decode(theApp,dibit_buf); resCACH=cachdecode.isPassErrorCheck(); if (resCACH==true) { line[1]=cline; fonts[1]=theApp.italicFont; colours[1]=Color.BLACK; resEMB=EMBdecode(dibit_buf); // If short LC data is available then display it if (cachdecode.getShortLC()==true) { line[7]=cachdecode.getShortLCline(); fonts[7]=theApp.boldFont; if (cachdecode.getshortLCError()==true) { colours[7]=Color.RED; if (theApp.isDisplayOnlyGoodFrames()==true) line[7]=null; } else colours[7]=Color.BLACK; cachdecode.clearShortLC(); } } if ((resCACH==false)&&(resEMB==false)) theApp.embeddedFrameCount=8; } else { // MS and Direct resEMB=EMBdecode(dibit_buf); } theApp.frameCount++; return line; } // Has there been an error public boolean isError() { if ((resCACH==false)||(resEMB==false)) return false; else return true; } // Error check and decode the EMB private boolean EMBdecode(byte[] dibit_buf) { int a,r,cc,lcss; boolean pi; boolean EMDdata[]=new boolean[16]; // Convert from dibits into boolean // The EMB is broken into 2 parts either side of the embedded // these need reuniting into a single 20 bit boolean array r=0; for (a=66;a<70;a++) { if (dibit_buf[a]==0) { EMDdata[r]=false; EMDdata[r+1]=false; } else if (dibit_buf[a]==1) { EMDdata[r]=false; EMDdata[r+1]=true; } else if (dibit_buf[a]==2) { EMDdata[r]=true; EMDdata[r+1]=false; } else if (dibit_buf[a]==3) { EMDdata[r]=true; EMDdata[r+1]=true; } r=r+2; } for (a=86;a<90;a++) { if (dibit_buf[a]==0) { EMDdata[r]=false; EMDdata[r+1]=false; } else if (dibit_buf[a]==1) { EMDdata[r]=false; EMDdata[r+1]=true; } else if (dibit_buf[a]==2) { EMDdata[r]=true; EMDdata[r+1]=false; } else if (dibit_buf[a]==3) { EMDdata[r]=true; EMDdata[r+1]=true; } r=r+2; } // Error check the EMB // If it passes this is a Voice Burst with Embedded Signalling if (QuadResidue1676(EMDdata)==true) { StringBuilder sb=new StringBuilder(250); line[0]=theApp.getTimeStamp()+" DMR Voice Frame with Embedded Signalling"; colours[0]=Color.BLACK; fonts[0]=theApp.boldFont; // Colour code if (EMDdata[0]==true) cc=8; else cc=0; if (EMDdata[1]==true) cc=cc+4; if (EMDdata[2]==true) cc=cc+2; if (EMDdata[3]==true) cc=cc+1; // Update the colour code display if (theApp!=null) theApp.setColourCode(cc); // PI pi=EMDdata[4]; // LCSS if (EMDdata[5]==true) lcss=2; else lcss=0; if (EMDdata[6]==true) lcss++; // Display the colour code sb.append("EMB : Colour Code "+Integer.toString(cc)); // PI if (pi==true) sb.append(" : PI=1"); // LCSS if (lcss==0) sb.append(" : Single fragment LC "); else if (lcss==1) sb.append(" : First fragment of LC "); else if (lcss==2) sb.append(" : Last fragment of LC"); else if (lcss==3) sb.append(" : Continuation fragment of LC"); line[2]=sb.toString(); // Add this to the embedded data class theApp.embedded_lc.addData(dibit_buf,lcss); // Is embedded data ready if (theApp.embedded_lc.getDataReady()==true) { String elines[]=theApp.embedded_lc.getLines(); // Display the embedded LC along with a timestamp line[3]=theApp.getTimeStamp()+" "+elines[0]; } // Pass on voice data VoiceData voicedata=new VoiceData(); voicedata.handleVoice(theApp,dibit_buf); // If the user doesn't want to see voice frames set shouldDisplay to false if (theApp.isDisplayVoiceFrames()==false) shouldDisplay=false; // Return all done return true; } else { // Is this a Data Frame with Embedded signalling // See if its has a slot type field that passes its error check SlotType slottype=new SlotType(); boolean SLOT_TYPEres,BPTCres=false; line[0]=theApp.getTimeStamp()+" DMR Data Frame with Embedded Signalling"; colours[0]=Color.BLACK; fonts[0]=theApp.boldFont; line[2]=slottype.decode(theApp,dibit_buf); SLOT_TYPEres=slottype.isPassErrorCheck(); // If the slot type is OK try to decode the rest if (SLOT_TYPEres==true) { int dataType=slottype.returnDataType(); // PI Header if (dataType==0) { BPTC19696 bptc19696=new BPTC19696(); if (bptc19696.decode(dibit_buf)==true) { BPTCres=true; boolean bits[]=bptc19696.dataOut(); // Display the PI header bits as raw binary StringBuilder sb=new StringBuilder(); int ai; for (ai=0;ai0) d[0]=true; else d[0]=false; if ((a&32)>0) d[1]=true; else d[1]=false; if ((a&16)>0) d[2]=true; else d[2]=false; if ((a&8)>0) d[3]=true; else d[3]=false; if ((a&4)>0) d[4]=true; else d[4]=false; if ((a&2)>0) d[5]=true; else d[5]=false; if ((a&1)>0) d[6]=true; else d[6]=false; // Shift the value 9 times to the left value[a]=a<<9; // Calculate the parity bits p[0]=d[1]^d[2]^d[3]^d[4]; p[1]=d[2]^d[3]^d[4]^d[5]; p[2]=d[0]^d[3]^d[4]^d[5]^d[6]; p[3]=d[2]^d[3]^d[5]^d[6]; p[4]=d[1]^d[2]^d[6]; p[5]=d[0]^d[1]^d[4]; p[6]=d[0]^d[1]^d[2]^d[5]; p[7]=d[0]^d[1]^d[2]^d[3]^d[6]; p[8]=d[0]^d[2]^d[4]^d[5]^d[6]; // Add these to the lower bits of the valid words if (p[0]==true) value[a]=value[a]+256; if (p[1]==true) value[a]=value[a]+128; if (p[2]==true) value[a]=value[a]+64; if (p[3]==true) value[a]=value[a]+32; if (p[4]==true) value[a]=value[a]+16; if (p[5]==true) value[a]=value[a]+8; if (p[6]==true) value[a]=value[a]+4; if (p[7]==true) value[a]=value[a]+2; if (p[8]==true) value[a]=value[a]+1; } // Just something to break on ! return true; } // Check the EMB against a precomputed list of correct words boolean QuadResidue1676 (boolean[] word) { int a; // A complete list of valid slot type words // This was generated by the calcQuadResidue1676() method final int[]ResidueNums={0,627,1253,1686,2505,3002,3372,3935,4578,5009,5383,6004,6187, 6744,7374,7869,8631,9156,9554,10017,10366,10765,11419,12008,12373,12838,13488, 14019,14748,15343,15737,16138,16670,17261,17915,18312,18647,19108,19506,20033, 20732,21135,21529,22122,22837,23366,24016,24483,24745,25306,25676,26175,26976, 27411,28037,28662,29003,29496,30126,30685,30850,31473,31847,32276,32847,33340, 33962,34521,35206,35829,36195,36624,37293,37854,38216,38715,39012,39447,40065, 40690,41464,41867,42269,42862,43057,43586,44244,44711,45082,45673,46335,46732, 47571,48032,48438,48965,49489,49954,50612,51143,51352,51947,52349,52750,53427, 53952,54358,54821,55674,56073,56735,57324,57574,58005,58371,58992,59695,60252, 60874,61369,61700,62327,62945,63378,63693,64190,64552,65115}; // Convert the boolean array into an integer if (word[15]==true) residueValue=1; else residueValue=0; if (word[14]==true) residueValue=residueValue+2; if (word[13]==true) residueValue=residueValue+4; if (word[12]==true) residueValue=residueValue+8; if (word[11]==true) residueValue=residueValue+16; if (word[10]==true) residueValue=residueValue+32; if (word[9]==true) residueValue=residueValue+64; if (word[8]==true) residueValue=residueValue+128; if (word[7]==true) residueValue=residueValue+256; if (word[6]==true) residueValue=residueValue+512; if (word[5]==true) residueValue=residueValue+1024; if (word[4]==true) residueValue=residueValue+2048; if (word[3]==true) residueValue=residueValue+4096; if (word[2]==true) residueValue=residueValue+8192; if (word[1]==true) residueValue=residueValue+16384; if (word[0]==true) residueValue=residueValue+32768; // Run through the possible values and we have a match return true for (a=0;a<128;a++) { if (residueValue==ResidueNums[a]) return true; } // No matches so we must have a problem and so should return false return false; } // Return the fonts in use public Font[] getFonts() { return fonts; } // Return the colours in use public Color[] getColours() { return colours; } // Tells the main program if this PDU should be displayed public boolean getShouldDisplay() { return shouldDisplay; } public void setShouldDisplay (boolean b) { shouldDisplay=b; } } ================================================ FILE: src/main/java/com/dmr/DMRVoice.java ================================================ package com.dmr; import java.awt.Color; import java.awt.Font; public class DMRVoice { private String line[]=new String[10]; private boolean res; private Font fonts[]=new Font[10]; private Color colours[]=new Color[10]; private boolean shouldDisplay=true; private DMRDecode theApp; public String[] decode (DMRDecode tTheApp,byte[] dibit_buf) { theApp=tTheApp; int mode=tTheApp.getMode(); line[0]=theApp.getTimeStamp()+" DMR Voice Frame "; if (mode==0) line[0]=line[0]+" (BS)"; else if (mode==1) line[0]=line[0]+" (MS)"; else if (mode==2) line[0]=line[0]+" (Direct)"; fonts[0]=theApp.boldFont; colours[0]=Color.BLACK; // BS only if (mode==0) { DecodeCACH cachdecode=new DecodeCACH(); // CACH decode String cline=cachdecode.decode(theApp,dibit_buf); res=cachdecode.isPassErrorCheck(); if (res==true) { line[1]=cline; // If short LC data is available then display it if (cachdecode.getShortLC()==true) { line[7]=cachdecode.getShortLCline(); fonts[7]=theApp.boldFont; if (cachdecode.getshortLCError()==true) { colours[7]=Color.RED; if (theApp.isDisplayOnlyGoodFrames()==true) line[7]=null; } else colours[7]=Color.BLACK; cachdecode.clearShortLC(); } } } else { res=true; } // Pass on voice data VoiceData voicedata=new VoiceData(); voicedata.handleVoice(tTheApp,dibit_buf); theApp.frameCount++; return line; } public boolean isError() { return res; } // Return the fonts in use public Font[] getFonts() { return fonts; } // Return the colours in use public Color[] getColours() { return colours; } // Tells the main program if this PDU should be displayed public boolean getShouldDisplay() { return shouldDisplay; } public void setShouldDisplay (boolean b) { shouldDisplay=b; } } ================================================ FILE: src/main/java/com/dmr/DecodeCACH.java ================================================ package com.dmr; public class DecodeCACH { private StringBuilder line=new StringBuilder(250); private String shortLCline; private boolean at; private boolean channel; private int lcss; private boolean passErrorCheck=false; private boolean haveShortLC=false; private boolean shortLCError=false; private int errorRes; private DMRDecode theApp; public String decode (DMRDecode TtheApp,byte[] dibit_buf) { theApp=TtheApp; line.append("CACH : TACT "); // CACH decode passErrorCheck=mainDecode(dibit_buf); // Only pass the decoded data back if the user wants to display CACH info if (theApp.isDisplayCACH()==true) return line.toString(); else return null; } // De-interleave , CRC check and decode the CACH // With code added to work out which interleave sequence to use private boolean mainDecode (byte[] dibit_buf) { int a,r=0,fragType=-1; boolean rawdataCACH[]=new boolean[24]; boolean dataCACH[]=new boolean[24]; final int[]interleaveCACH={0,4,8,12,14,18,22,1,2,3,5,6,7,9,10,11,13,15,16,17,19,20,21,23}; // Convert from dibits into boolean for (a=0;a<12;a++) { if (dibit_buf[a]==0) { rawdataCACH[r]=false; rawdataCACH[r+1]=false; } else if (dibit_buf[a]==1) { rawdataCACH[r]=false; rawdataCACH[r+1]=true; } else if (dibit_buf[a]==2) { rawdataCACH[r]=true; rawdataCACH[r+1]=false; } else if (dibit_buf[a]==3) { rawdataCACH[r]=true; rawdataCACH[r+1]=true; } r=r+2; } // De-interleave for (a=0;a<24;a++) { r=interleaveCACH[a]; dataCACH[a]=rawdataCACH[r]; } // Convert the first 7 bits (TACT + 3 parity bits) into an integer if (dataCACH[0]==true) errorRes=64; else errorRes=0; if (dataCACH[1]==true) errorRes=errorRes+32; if (dataCACH[2]==true) errorRes=errorRes+16; if (dataCACH[3]==true) errorRes=errorRes+8; if (dataCACH[4]==true) errorRes=errorRes+4; if (dataCACH[5]==true) errorRes=errorRes+2; if (dataCACH[6]==true) errorRes++; if (errorCheckHamming743(errorRes)==false) return false; // Decode the TACT at=dataCACH[0]; channel=dataCACH[1]; if (dataCACH[2]==true) lcss=2; else lcss=0; if (dataCACH[3]==true) lcss++; // Display TACT info if (at==true) line.append(" AT=1"); if (channel==false) { line.append(" Ch 1"); theApp.currentChannel=1; } else { line.append(" Ch 2"); theApp.currentChannel=2; } if (lcss==0) line.append(" First fragment of CSBK "); else if (lcss==1) line.append(" First fragment of LC "); else if (lcss==2) line.append(" Last fragment of LC "); else if (lcss==3) line.append(" Continuation fragment of LC "); // If this is an short LC message pass the data on to the ShortLC object if (lcss==3) fragType=1; else if (lcss==2) fragType=2; else if (lcss==1) fragType=0; // Below is commented out as the code contains a known bug // Also other things need fixing first if (fragType!=-1) theApp.short_lc.addData(dataCACH,fragType); // Is short LC data ready ? if (theApp.short_lc.isDataReady()==true) { // See if the short LC passed its error checks if (theApp.short_lc.isCRCgood()==true) { shortLCError=false; shortLCline=theApp.getTimeStamp()+" Short LC : "+theApp.short_lc.getLine(); } else { shortLCError=true; shortLCline=theApp.getTimeStamp()+" Bad Short LC !"; } theApp.short_lc.clrDataReady(); haveShortLC=true; } else haveShortLC=false; return true; } // Error check the CACH TACT public boolean errorCheckHamming743(int tact) { // An array of valid Hamming words which was generated by calcHamming() final int[]Hamming743={0,11,22,29,39,44,49,58,69,78,83,88,98,105,116,127}; int a; for (a=0;a<16;a++) { // If we have a match return true if (tact==Hamming743[a]) return true; } // If no match there is a problem so return a false return false; } // Generate a list of valid Hamming words // Isn't normally called but leave in for now public boolean calcHamming () { boolean d1,d2,d3,d4,h2,h1,h0; int a; int valid[]=new int[16]; // Run through all possible values for (a=0;a<16;a++) { // Covert from an integer to boolean if ((a&8)>0) d1=true; else d1=false; if ((a&4)>0) d2=true; else d2=false; if ((a&2)>0) d3=true; else d3=false; if ((a&1)>0) d4=true; else d4=false; // Calculate the parity bits h2=d1^d2^d3; h1=d2^d3^d4; h0=d1^d2^d4; // Convert this back into a 7 bit integer if (d1==true) valid[a]=64; else valid[a]=0; if (d2==true) valid[a]=valid[a]+32; if (d3==true) valid[a]=valid[a]+16; if (d4==true) valid[a]=valid[a]+8; if (h2==true) valid[a]=valid[a]+4; if (h1==true) valid[a]=valid[a]+2; if (h0==true) valid[a]=valid[a]+1; } // Just something to break on return true; } // Let the main program know if there is an error in the frame public boolean isPassErrorCheck() { return passErrorCheck; } // Let the main program know the hamming word in case of an error public int getErrorRes() { return errorRes; } // Tell the user we have a Short LC public boolean getShortLC() { return haveShortLC; } // Clear the Short LC variables public void clearShortLC() { haveShortLC=false; } // Return the decoded short LC public String getShortLCline() { return shortLCline; } public boolean getshortLCError() { return shortLCError; } } ================================================ FILE: src/main/java/com/dmr/DisplayBar.java ================================================ package com.dmr; import java.awt.*; import javax.swing.*; import javax.swing.border.Border; public class DisplayBar extends JPanel { public static final long serialVersionUID=1; private Border loweredbevel=BorderFactory.createLoweredBevelBorder(); static final int BUFFERMAX=75; private int bufferCounter=0; private int symbolBuffer[]=new int[BUFFERMAX]; private int max,min,umid,lmid,fullRange; private boolean displayActive=false,enableDisplay=false; public DisplayBar () { this.setBorder(loweredbevel); } // Draw the display in the JPanel @Override public void paintComponent(Graphics g) { int a,val; int height=this.getHeight(); int width=this.getWidth(); double modFactor=((float)height/(float)fullRange); // Repaint the background super.paintComponent(g); // If the display isn't active then don't go any further if ((displayActive==false)||(enableDisplay==false)) return; // Draw the centre line g.drawLine(0,(height/2),width,(height/2)); // Draw the lmid line val=lmid+Math.abs(min); val=(int)((float)val*modFactor); g.drawLine(0,val,width,val); // Draw the lmid line val=umid+Math.abs(min); val=(int)((float)val*modFactor); g.drawLine(0,val,width,val); // Draw the symbol points for (a=0;a devices; // Constructor public DisplayFrame(String title,DMRDecode theApp) { setTitle(title); this.theApp=theApp; setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().setBackground(Color.WHITE); // Menu setup setJMenuBar(menuBar); // Main JMenu mainMenu=new JMenu("Main"); mainMenu.add(capture_item=new JRadioButtonMenuItem("Capture",theApp.isCapture())); // Disable the capture radio button capture_item.setEnabled(false); capture_item.addActionListener(this); mainMenu.add(debug_item=new JRadioButtonMenuItem("Debug Mode",theApp.isDebug())); // Disable the debug radio button debug_item.setEnabled(false); debug_item.addActionListener(this); mainMenu.add(inverted_item=new JRadioButtonMenuItem("Invert Signal",theApp.inverted)); inverted_item.addActionListener(this); mainMenu.add(quick_log=new JRadioButtonMenuItem("Quick Log",theApp.isQuickLog())); quick_log.addActionListener(this); mainMenu.add(save_settings=new JMenuItem("Save Settings")); save_settings.addActionListener(this); mainMenu.add(save_to_file=new JRadioButtonMenuItem("Save to File",theApp.getLogging())); save_to_file.addActionListener(this); mainMenu.add(exit_item=new JMenuItem("Exit")); exit_item.addActionListener(this); menuBar.add(mainMenu); // Audio JMenu audioMenu=new JMenu("Audio"); audioDevicesMenu=buildAudioDevices(); audioMenu.add(audioDevicesMenu); audioDevicesMenu.updateUI(); menuBar.add(audioMenu); // Info JMenu infoMenu=new JMenu("Info"); infoMenu.add(view_display_bar=new JRadioButtonMenuItem("Enable Symbol Display",theApp.isEnableDisplayBar())); view_display_bar.addActionListener(this); infoMenu.add(error_rate=new JMenuItem("Error Check Info")); error_rate.addActionListener(this); menuBar.add(infoMenu); // View JMenu viewMenu=new JMenu("View"); viewMenu.add(copy_screen=new JMenuItem("Copy All to the Clipboard")); copy_screen.addActionListener(this); viewMenu.add(clear_screen=new JMenuItem("Clear Screen")); clear_screen.addActionListener(this); viewMenu.addSeparator(); viewMenu.add(view_cach=new JRadioButtonMenuItem("Display CACH",theApp.isDisplayCACH())); view_cach.addActionListener(this); viewMenu.add(view_onlygood=new JRadioButtonMenuItem("Display Good Frames Only",theApp.isDisplayOnlyGoodFrames())); view_onlygood.addActionListener(this); viewMenu.add(view_idle=new JRadioButtonMenuItem("Display Idle PDU",theApp.isDisplayIdlePDU())); view_idle.addActionListener(this); viewMenu.add(view_voice=new JRadioButtonMenuItem("Display Voice Frames",theApp.isDisplayVoiceFrames())); view_voice.addActionListener(this); menuBar.add(viewMenu); // Help JMenu helpMenu=new JMenu("Help"); helpMenu.add(about_item=new JMenuItem("About")); about_item.addActionListener(this); helpMenu.add(linkedin_item=new JMenuItem("Connect on Linkedin")); linkedin_item.addActionListener(this); helpMenu.add(download_item=new JMenuItem("Download the latest build of DMRDecode")); download_item.addActionListener(this); helpMenu.add(twitter_item=new JMenuItem("Follow DMRDecode Progress on Twitter")); twitter_item.addActionListener(this); helpMenu.add(help_item=new JMenuItem("Help")); help_item.addActionListener(this); menuBar.add(helpMenu); // Setup the status bar getContentPane().add(statusBar, java.awt.BorderLayout.SOUTH); statusBar.setLoggingStatus("Not Logging"); statusBar.setApp(theApp); // Setup the display bar getContentPane().add(displayBar, java.awt.BorderLayout.WEST); // Add the vertical scrollbar add(vscrollbar,BorderLayout.EAST); // Add a listener for this vscrollbar.addAdjustmentListener(new MyAdjustmentListener()); // Read in the default settings file try { theApp.readDefaultSettings(); // Update the menus menuItemUpdate(); } catch (Exception e) { String err=e.toString(); // Can't find the default settings file // System.out.println("\nInformative : Unable to read the file DMRDecode_settings.xml "+err); } } // Handle messages from the scrollbars class MyAdjustmentListener implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { // Vertical scrollbar if (e.getSource()==vscrollbar) { theApp.vertical_scrollbar_value=e.getValue(); repaint(); } } } // Handle all menu events public void actionPerformed (ActionEvent event) { String event_name=event.getActionCommand(); // About if (event_name=="About") { String line=theApp.program_version+"\r\n"+"Ian Wraith (ianwraith@gmail.com)\r\nalso big thanks to Chris Sams for the CPU utilization fix " + "\r\nplus thanks to Andy W for the mixer and audio code" + "\r\nalso to the author of DSD from whom I took most of the demodulation code."; JOptionPane.showMessageDialog(null,line,"DMRDecode", JOptionPane.INFORMATION_MESSAGE); } // Capture if (event_name=="Capture") { if (theApp.isCapture()==false) theApp.setCapture(true); else theApp.setCapture(false); } // Clear Screen if (event_name=="Clear Screen") theApp.clearScreen(); // Copy to the clip board if (event_name=="Copy All to the Clipboard") setClipboard(theApp.getAllText()); // Debug Mode if (event_name=="Debug Mode") { if (theApp.isDebug()==false) theApp.setDebug(true); else theApp.setDebug(false); } // Invert signal if (event_name=="Invert Signal") { if (theApp.inverted==false) theApp.inverted=true; else theApp.inverted=false; } // Quick Log if (event_name=="Quick Log") { if (theApp.isQuickLog()==false) { quickLogDialogBox(); } else { closeQuickLogFile(); } } // Save to File if (event_name=="Save to File") { if (theApp.getLogging()==false) { if (saveDialogBox()==false) { // Restart the audio in thread theApp.lineInThread.startAudio(); menuItemUpdate(); return; } theApp.setLogging(true); statusBar.setLoggingStatus("Logging"); } else { closeLogFile(); } // Restart the audio in thread theApp.lineInThread.startAudio(); } // Save the current settings if (event_name=="Save Settings") theApp.saveCurrentSettings(); // Twitter if (event_name=="Follow DMRDecode Progress on Twitter") { BareBonesBrowserLaunch.openURL("https://twitter.com/#!/IanWraith"); } // Latest build download if (event_name=="Download the latest build of DMRDecode") { BareBonesBrowserLaunch.openURL("http://borg.shef.ac.uk/dmrdecode/"); } // Error rate info if (event_name=="Error Check Info") { errorDialogBox(); } // Enable/Disable the symbol display if (event_name=="Enable Symbol Display") { boolean estate=theApp.isEnableDisplayBar(); if (estate==true) estate=false; else estate=true; theApp.setEnableDisplayBar(estate); } // Exit if (event_name=="Exit") { // If logging close the file if (theApp.getLogging()==true) closeLogFile(); // Close the audio down // theApp.lineInThread.shutDownAudio(); // Stop the program // System.exit(0); } // Help if (event_name=="Help") { BareBonesBrowserLaunch.openURL("https://github.com/IanWraith/DMRDecode/wiki"); } // Display CACH if (event_name=="Display CACH") { if (theApp.isDisplayCACH()==true) theApp.setDisplayCACH(false); else theApp.setDisplayCACH(true); // If logging update the log of this change in filter settings if (theApp.getLogging()==true) { if (theApp.isDisplayCACH()==false) theApp.fileWrite("Filter settings changed so CACH data isn't displayed"); else theApp.fileWrite("Filter settings changed so that CACH data is displayed"); } } // Display Idle PDU if (event_name=="Display Idle PDU") { if (theApp.isDisplayIdlePDU()==true) theApp.setDisplayIdlePDU(false); else theApp.setDisplayIdlePDU(true); // If logging update the log of this change in filter settings if (theApp.getLogging()==true) { if (theApp.isDisplayIdlePDU()==false) theApp.fileWrite("Filter settings changed so Idle PDUs aren't displayed"); else theApp.fileWrite("Filter settings changed so that Idle PDUs are displayed"); } } // Display only good frames if (event_name=="Display Good Frames Only") { if (theApp.isDisplayOnlyGoodFrames()==true) theApp.setDisplayOnlyGoodFrames(false); else theApp.setDisplayOnlyGoodFrames(true); // If logging update the log of this change in filter settings if (theApp.getLogging()==true) { if (theApp.isDisplayOnlyGoodFrames()==false) theApp.fileWrite("Filter settings changed so that frames with errors are displayed"); else theApp.fileWrite("Filter settings changed so that only frames without errors are displayed"); } } // Display Voice Frames if (event_name=="Display Voice Frames") { if (theApp.isDisplayVoiceFrames()==true) theApp.setDisplayVoiceFrames(false) ; else theApp.setDisplayVoiceFrames(true); // If logging update the log of this change in filter settings if (theApp.getLogging()==true) { if (theApp.isDisplayVoiceFrames()==false) theApp.fileWrite("Filter settings changed so voice frames aren't displayed"); else theApp.fileWrite("Filter settings changed so that voice frames are displayed"); } } // Change mixer if (event_name.equalsIgnoreCase("mixer")){ changeMixer(((JRadioButtonMenuItem)event.getSource()).getText()); } // Linkedin if (event_name=="Connect on Linkedin") { BareBonesBrowserLaunch.openURL("https://uk.linkedin.com/pub/dir/Ian/Wraith"); } menuItemUpdate(); } // Update all the menu items public void menuItemUpdate () { inverted_item.setSelected(theApp.inverted); //debug_item.setSelected(theApp.isDebug()); save_to_file.setSelected(theApp.getLogging()); quick_log.setSelected(theApp.isQuickLog()); view_cach.setSelected(theApp.isDisplayCACH()); view_idle.setSelected(theApp.isDisplayIdlePDU()); view_voice.setSelected(theApp.isDisplayVoiceFrames()); view_onlygood.setSelected(theApp.isDisplayOnlyGoodFrames()); view_display_bar.setSelected(theApp.isEnableDisplayBar()); //capture_item.setSelected(theApp.isCapture()); // Audio sources MenuElement[] devs=audioDevicesMenu.getSubElements(); if (devs.length>0){ for (MenuElement m : devs[0].getSubElements()){ if (((JRadioButtonMenuItem)m).getText().equals(theApp.lineInThread.getMixerName())){ ((JRadioButtonMenuItem)m).setSelected(true); break; } } } } // Display a dialog box so the user can select a location and name for a log file public boolean saveDialogBox () { if (theApp.getLogging()==true) return false; String file_name; Boolean append=true; // Bring up a dialog box that allows the user to select the name // of the saved file JFileChooser fc=new JFileChooser(); // The dialog box title // fc.setDialogTitle("Select the log file name"); // Start in current directory fc.setCurrentDirectory(new File(".")); // Don't all types of file to be selected // fc.setAcceptAllFileFilterUsed(false); // Only show .txt files // fc.setFileFilter(new TextfileFilter()); // Show save dialog; this method does not return until the // dialog is closed int returnval=fc.showSaveDialog(this); // If the user has selected cancel then quit if (returnval==JFileChooser.CANCEL_OPTION) return false; // Get the file name an path of the selected file file_name=fc.getSelectedFile().getPath(); // Does the file name end in .txt ? // // If not then automatically add a .txt ending // int last_index=file_name.lastIndexOf(".txt"); if (last_index!=(file_name.length()-4)) file_name=file_name + ".txt"; // Create a file with this name // File tfile=new File(file_name); // If the file exists ask the user if they want to overwrite it if (tfile.exists()) { // TODO : Fix the wording of the open log file dialog box e.g Have the buttons labelled "Overwrite" and "Append" int response=JOptionPane.showConfirmDialog(null, "This log file already exists : What do you wish to do ?\nClick Yes to overwrite it\nClick No to append data to it\nClick Cancel to quit", "Confirm Overwrite", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (response==JOptionPane.CANCEL_OPTION) return false; else if (response==JOptionPane.YES_OPTION) append=false; } // Open the file try { // If append==true then the data written is appended to this file theApp.file=new FileWriter(tfile,append); // Clear all logged info theApp.usersLogged.clearAll(); // Write the program version as the first line of the log String fline="\r\n##########################################################\r\n\r\n"+theApp.program_version+"\r\n"; theApp.file.write(fline); // Display the state of the filters at the start of the log if (theApp.isDisplayCACH()==false) theApp.file.write("You have selected not to display CACH data\r\n"); if (theApp.isDisplayOnlyGoodFrames()==true) theApp.file.write("You have selected only to display frames without errors\r\n"); if (theApp.isDisplayIdlePDU()==false) theApp.file.write("You have selected not to display Idle PDUs\r\n"); if (theApp.isDisplayVoiceFrames()==false) theApp.file.write("You have selected not to display voice frames\r\n"); } catch (Exception e) { System.out.println("\nError opening the logging file"); return false; } theApp.setLogging(true); return true; } // Close the log file public void closeLogFile() { int a,count; String line; theApp.setLogging(false); statusBar.setLoggingStatus("Not Logging"); try { // Display users count=theApp.usersLogged.returnUserCounter(); // No users if (count==0) { theApp.file.write("\r\n\r\nNo users were logged"); } else { line="\r\n\r\nThe following "+Integer.toString(count)+" users were logged .."; theApp.file.write(line); // Sort the users theApp.usersLogged.sortByIdent(); // Run through each user for (a=0;a deviceList=getCompatibleDevices(); int i; for (i=0; i getCompatibleDevices(){ devices=new ArrayList(); //list the available mixers Mixer.Info mixers[]=AudioSystem.getMixerInfo(); int i; //iterate the mixers and display TargetLines for (i=0;i0){ int x; for (x=0;x0) i=displayCounter-1; else i=DISPLAYCOUNT-1; Graphics2D g2D=(Graphics2D)g; // Draw in the lines on the screen // taking account of the fact that the data is stored in a circular buffer // we need to display the oldest line stored first and then go backwards from // that point onwards while(count=DISPLAYCOUNT) i=0; count++; } return buffer.toString(); } } ================================================ FILE: src/main/java/com/dmr/EmbeddedLC.java ================================================ package com.dmr; public class EmbeddedLC { private boolean rawLC[]=new boolean[128]; private int currentState=-1; private boolean dataReady=false; private String lines[]=new String[3]; private boolean lcData[]=new boolean[72]; // Add LC data (which may consist of 4 blocks) to the data store public void addData (byte[] dibit_buf,int type) { int a,r=0; boolean rawdata[]=new boolean[32]; // Convert from dibits into boolean for (a=70;a<86;a++) { if (dibit_buf[a]==0) { rawdata[r]=false; rawdata[r+1]=false; } else if (dibit_buf[a]==1) { rawdata[r]=false; rawdata[r+1]=true; } else if (dibit_buf[a]==2) { rawdata[r]=true; rawdata[r+1]=false; } else if (dibit_buf[a]==3) { rawdata[r]=true; rawdata[r+1]=true; } r=r+2; } // Is this the first block of a 4 block embedded LC ? if (type==1) { for (a=0;a<32;a++) { rawLC[a]=rawdata[a]; } // Show we are ready for the next LC block currentState=0; } // Is this the 2nd block of a 4 block embedded LC ? else if ((type==3)&&(currentState==0)) { for (a=0;a<32;a++) { rawLC[a+32]=rawdata[a]; } // Show we are ready for the next LC block currentState=1; } // Is this the 3rd block of a 4 block embedded LC ? else if ((type==3)&&(currentState==1)) { for (a=0;a<32;a++) { rawLC[a+64]=rawdata[a]; } // Show we are ready for the final LC block currentState=2; } // Is this the final block of a 4 block embedded LC ? else if ((type==2)&&(currentState==2)) { for (a=0;a<32;a++) { rawLC[a+96]=rawdata[a]; } // Process the complete data block if (processMultiBlockEmbeddedLC()==false) { dataReady=true; lines[0]="Bad Embedded Multi Block LC"; } } // Is this a single block embedded LC else if (type==0) processSingleBlockEmbeddedLC(rawdata); } // Unpack and error check a embedded LC private boolean processMultiBlockEmbeddedLC() { int a,b=0,crc; StringBuilder sb=new StringBuilder(250); boolean data[]=new boolean[128]; boolean row[]=new boolean[16]; sb.append("Embedded Multi Block LC : "); // The data is unpacked downwards in columns for (a=0;a<128;a++) { data[b]=rawLC[a]; b=b+16; if (b>112) b=b-112; } // Hamming (16,11,4) check each row except the last one for (a=0;a<=96;a=a+16) { for (b=0;b<16;b++) { row[b]=data[a+b]; } if (hamming16114(row)==false) return false; } // We have passed the Hamming check so extract the actual payload b=0; for (a=0;a<11;a++) { lcData[b]=data[a]; b++; } for (a=16;a<27;a++) { lcData[b]=data[a]; b++; } for (a=32;a<42;a++) { lcData[b]=data[a]; b++; } for (a=48;a<58;a++) { lcData[b]=data[a]; b++; } for (a=64;a<74;a++) { lcData[b]=data[a]; b++; } for (a=80;a<90;a++) { lcData[b]=data[a]; b++; } for (a=96;a<106;a++) { lcData[b]=data[a]; b++; } // Extract the 5 bit CRC if (data[42]==true) crc=16; else crc=0; if (data[58]==true) crc=crc+8; if (data[74]==true) crc=crc+4; if (data[90]==true) crc=crc+2; if (data[106]==true) crc++; // Now CRC check this crc tCRC=new crc(); if (tCRC.crcFiveBit(lcData,crc)==false) return false; // Display what we have in binary form for (a=0;a<72;a++) { if (lcData[a]==false) sb.append("0"); else sb.append("1"); } // Convert from StringBuilder to a String lines[0]=sb.toString(); dataReady=true; return true; } // A Hamming (16,11,4) Check private boolean hamming16114 (boolean d[]) { boolean c[]=new boolean[5]; // Calculate the checksum this column should have c[0]=d[0]^d[1]^d[2]^d[3]^d[5]^d[7]^d[8]; c[1]=d[1]^d[2]^d[3]^d[4]^d[6]^d[8]^d[9]; c[2]=d[2]^d[3]^d[4]^d[5]^d[7]^d[9]^d[10]; c[3]=d[0]^d[1]^d[2]^d[4]^d[6]^d[7]^d[10]; c[4]=d[0]^d[2]^d[5]^d[6]^d[8]^d[9]^d[10]; // Compare these with the actual bits if ((c[0]==d[11])&&(c[1]==d[12])&&(c[2]==d[13])&&(c[3]==d[14])&&(c[4]==d[15])) return true; else return false; } // Deal with a single block embedded LC private void processSingleBlockEmbeddedLC (boolean data[]) { boolean isnull=true; StringBuilder sb=new StringBuilder(250); StringBuilder bin=new StringBuilder(250); int a; sb.append("Embedded Single Block LC : "); // Check if this message is all 0's as if it is then it is a null for (a=0;a<32;a++) { if (data[a]==true) { bin.append("1"); isnull=false; } else { bin.append("0"); } } // Is this message a null short LC if (isnull==true) { sb.append("Null"); } else { sb.append(bin); } lines[0]=sb.toString(); dataReady=true; } // Tell the main program if we have data to return public boolean getDataReady () { return dataReady; } // Return the display lines to the main program public String[] getLines () { // Make a copy of the objects lines String clines[]=new String[3]; clines[0]=lines[0]; clines[1]=lines[1]; clines[2]=lines[2]; // Clear all the lines lines[0]=null; lines[1]=null; lines[2]=null; // Clear the dataReady boolean so the program doesn't think there is more data dataReady=false; // Prepare for more data currentState=-1; // Return the copy of the lines return clines; } } ================================================ FILE: src/main/java/com/dmr/FullLinkControl.java ================================================ package com.dmr; public class FullLinkControl { boolean pf; private String display[]=new String[3]; // The main decode method public String[] decode (DMRDecode theApp,boolean bits[]) { int flco,fid,service; // PF pf=bits[0]; // Bit 1 is reserved pf=bits[1]; // FLCO if (bits[2]==true) flco=32; else flco=0; if (bits[3]==true) flco=flco+16; if (bits[4]==true) flco=flco+8; if (bits[5]==true) flco=flco+4; if (bits[6]==true) flco=flco+2; if (bits[7]==true) flco++; // FID if (bits[8]==true) fid=128; else fid=0; if (bits[9]==true) fid=fid+64; if (bits[10]==true) fid=fid+32; if (bits[11]==true) fid=fid+16; if (bits[12]==true) fid=fid+8; if (bits[13]==true) fid=fid+4; if (bits[14]==true) fid=fid+2; if (bits[15]==true) fid++; // Service options if (bits[16]==true) service=128; else service=0; if (bits[17]==true) service=service+64; if (bits[18]==true) service=service+32; if (bits[19]==true) service=service+16; if (bits[20]==true) service=service+8; if (bits[21]==true) service=service+4; if (bits[22]==true) service=service+2; if (bits[23]==true) service++; // PDU types if (flco==0) group_v_ch_usr(theApp,bits); else if (flco==3) uu_v_ch_usr(theApp,bits); else if (flco==4) big_m_flco4(theApp,bits); else if (flco==48) td_lc(theApp,bits); else unknown_flc(flco,fid,bits); return display; } // Group Voice Channer User LC void group_v_ch_usr (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb=new StringBuilder(250); display[0]="Group Voice Channel User LC"; // Service Options display[1]=utils.decodeServiceOptions(bits,16); // Group address int group=utils.retAddress(bits,24); // Source address int source=utils.retAddress(bits,48); sb.append("Group Address : "+Integer.toString(group)); sb.append(" Source Address : "+Integer.toString(source)); display[2]=sb.toString(); // Log these users // Group if (theApp.usersLogged.addUser(group)==true) { index=theApp.usersLogged.findUserIndex(group); if (index!=-1) { theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsGroupUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("Group Call to Group "); lab.append(Integer.toString(group)); lab.append(" from "); lab.append(Integer.toString(source)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Group Voice Call to Group",group,source,theApp.currentChannel,display[1]); } // Unit to Unit Voice Channel User LC void uu_v_ch_usr (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb=new StringBuilder(250); display[0]="Unit to Unit Voice Channel User LC"; // Service Options display[1]=utils.decodeServiceOptions(bits,16); // Target address int target=utils.retAddress(bits,24); // Source address int source=utils.retAddress(bits,48); sb.append("Target Address : "+Integer.toString(target)); sb.append(" Source Address : "+Integer.toString(source)); display[2]=sb.toString(); // Log these users // Target theApp.usersLogged.addUser(target); index=theApp.usersLogged.findUserIndex(target); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsUnitUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("Unit to Unit Call from "); lab.append(Integer.toString(source)); lab.append(" to "); lab.append(Integer.toString(target)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Unit to Unit Voice Call",target,source,theApp.currentChannel,display[1]); } // Terminator Data Link Control PDU void td_lc (DMRDecode theApp,boolean bits[]) { int index; Utilities utils=new Utilities(); StringBuilder sb=new StringBuilder(250); display[0]="Terminator Data Link Control PDU"; // Destination LLID int dllid=utils.retAddress(bits,16); // Source LLID int sllid=utils.retAddress(bits,40); sb.append("Destination Logical Link ID : "+Integer.toString(dllid)); sb.append(" Source Logical Link ID : "+Integer.toString(sllid)); display[1]=sb.toString(); // Log these users // Destination theApp.usersLogged.addUser(dllid); index=theApp.usersLogged.findUserIndex(dllid); if (index!=-1) theApp.usersLogged.setAsDataUser(index); // Source theApp.usersLogged.addUser(sllid); index=theApp.usersLogged.findUserIndex(sllid); if (index!=-1) theApp.usersLogged.setAsDataUser(index); // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("Data Call from "); lab.append(Integer.toString(sllid)); lab.append(" to "); lab.append(Integer.toString(dllid)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Terminator Data Link Control PDU",dllid,sllid,theApp.currentChannel,""); } // CP FLCO=4 void big_m_flco4 (DMRDecode theApp,boolean bits[]) { int group,source,a,lcn,index; StringBuilder sb1=new StringBuilder(300); StringBuilder sb2=new StringBuilder(300); display[0]="Capacity Plus Full Link Control LC : FLCO=4"; // Group if (bits[40]==true) group=127; else group=0; if (bits[41]==true) group=group+64; if (bits[42]==true) group=group+32; if (bits[43]==true) group=group+16; if (bits[44]==true) group=group+8; if (bits[45]==true) group=group+4; if (bits[46]==true) group=group+2; if (bits[47]==true) group++; // Bits 48 - 51 ?? // LCN if (bits[52]==true) lcn=8; else lcn=0; if (bits[53]==true) lcn=lcn+4; if (bits[54]==true) lcn=lcn+2; if (bits[55]==true) lcn++; // Source if (bits[56]==true) source=32768; else source=0; if (bits[57]==true) source=source+16384; if (bits[58]==true) source=source+8192; if (bits[59]==true) source=source+4096; if (bits[60]==true) source=source+2048; if (bits[61]==true) source=source+1024; if (bits[62]==true) source=source+512; if (bits[63]==true) source=source+256; if (bits[64]==true) source=source+128; if (bits[65]==true) source=source+64; if (bits[66]==true) source=source+32; if (bits[67]==true) source=source+16; if (bits[68]==true) source=source+8; if (bits[69]==true) source=source+4; if (bits[70]==true) source=source+2; if (bits[71]==true) source++; // Make up the 2nd line sb1.append("Group Address "+Integer.toString(group)+" Source Address "+Integer.toString(source)+" LCN "+Integer.toString(lcn)); display[1]=sb1.toString(); // Log these users // Group if (theApp.usersLogged.addUser(group)==true) { index=theApp.usersLogged.findUserIndex(group); if (index!=-1) { theApp.usersLogged.setAsGroup(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } } // Source theApp.usersLogged.addUser(source); index=theApp.usersLogged.findUserIndex(source); if (index!=-1) { theApp.usersLogged.setAsGroupUser(index); theApp.usersLogged.setChannel(index,theApp.currentChannel); } // Display the full binary on the bottom line if in debug mode if (theApp.isDebug()==true) { for (a=16;a<72;a++) { if (bits[a]==true) sb2.append("1"); else sb2.append("0"); } display[2]=sb2.toString(); } // Display this in a label on the status bar StringBuilder lab=new StringBuilder(250); lab.append("CP Group Call to Group "); lab.append(Integer.toString(group)); lab.append(" from "); lab.append(Integer.toString(source)); if (theApp.currentChannel==1) theApp.setCh1Label(lab.toString(),theApp.labelBusyColour); else theApp.setCh2Label(lab.toString(),theApp.labelBusyColour); // Quick log if (theApp.isQuickLog()==true) theApp.quickLogData("Capacity Plus Full Link Control LC",group,source,theApp.currentChannel,""); } // Handle unknown Full Link Control types private void unknown_flc (int flco,int fid,boolean bits[]) { int a; StringBuilder sb=new StringBuilder(300); sb.append("Unknown Full Link Control LC : FLCO="+Integer.toString(flco)+" + FID="+Integer.toString(fid)+" "); // Display the binary for (a=16;a<72;a++) { if (bits[a]==true) sb.append("1"); else sb.append("0"); } display[0]=sb.toString(); } } ================================================ FILE: src/main/java/com/dmr/JStatusBar.java ================================================ package com.dmr; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import javax.swing.border.Border; public class JStatusBar extends JPanel { public static final long serialVersionUID = 1; private JLabel logMode=new JLabel(); private JLabel syncLabel=new JLabel(); private JLabel ch1Label=new JLabel(); private JLabel ch2Label=new JLabel(); private JLabel colourCodeLabel=new JLabel(); private JLabel systemLabel=new JLabel(); private JProgressBar volumeBar=new JProgressBar(0,100); private Border loweredbevel=BorderFactory.createLoweredBevelBorder(); private JButton pauseButton=new JButton("Pause"); private DMRDecode TtheApp; public JStatusBar() { logMode.setHorizontalAlignment(SwingConstants.LEFT); logMode.updateUI(); logMode.setBorder(loweredbevel); syncLabel.setHorizontalAlignment(SwingConstants.LEFT); syncLabel.setBorder(loweredbevel); syncLabel.updateUI(); ch1Label.setHorizontalAlignment(SwingConstants.LEFT); ch1Label.setBorder(loweredbevel); ch1Label.updateUI(); ch2Label.setHorizontalAlignment(SwingConstants.LEFT); ch2Label.setBorder(loweredbevel); ch2Label.updateUI(); colourCodeLabel.setHorizontalAlignment(SwingConstants.LEFT); colourCodeLabel.setBorder(loweredbevel); colourCodeLabel.updateUI(); systemLabel.setHorizontalAlignment(SwingConstants.LEFT); systemLabel.setBorder(loweredbevel); systemLabel.updateUI(); pauseButton.addActionListener(new ButtonListener()); // Give the volume progress bar a border // volumeBar.setBorder(loweredbevel); // Ensure the elements of the status bar are displayed from the left this.setLayout(new FlowLayout(FlowLayout.LEFT)); this.add(pauseButton,BorderLayout.CENTER); this.add(volumeBar,BorderLayout.CENTER); this.add(syncLabel,BorderLayout.CENTER); this.add(logMode,BorderLayout.CENTER); this.add(colourCodeLabel,BorderLayout.CENTER); this.add(ch1Label,BorderLayout.CENTER); this.add(ch2Label,BorderLayout.CENTER); this.add(systemLabel,BorderLayout.CENTER); } // Sets the logging label text public void setLoggingStatus(String text) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } logMode.setText(text); } // Sets the sync mode label public void setSyncLabel (boolean syn) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } // Have sync if (syn==true) { syncLabel.setText("SYNC"); syncLabel.setForeground(Color.GREEN); } else { syncLabel.setText("NO SYNC"); syncLabel.setForeground(Color.RED); } } // Set the volume bar display public void setVolumeBar(int val) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } if (val<40){ volumeBar.setForeground(Color.yellow); }else if((val>40)&&(val<70)){ volumeBar.setForeground(Color.green); }else { volumeBar.setForeground(Color.red); //greater 100 reset vol bar to 100 val=100; } // Set the class value // volumeBar.setValue(val); } public void setCh1Label (String label,Color c) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } label="Ch 1 : "+label; ch1Label.setText(label); ch1Label.setForeground(c); } public void setCh2Label (String label,Color c) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } label="Ch 2 : "+label; ch2Label.setText(label); ch2Label.setForeground(c); } public void setColourCodeLabel (int cc,Color col) { if (TtheApp!=null) { if (TtheApp.isPauseScreen()==true) return; } String label; if (cc==-1) label="Colour Code : Unknown"; else label="Colour Code : "+Integer.toString(cc); colourCodeLabel.setText(label); colourCodeLabel.setForeground(col); } public void setSystemLabel (String text,Color col) { systemLabel.setText(text); systemLabel.setForeground(col); } public void setApp (DMRDecode theApp) { TtheApp=theApp; } // This class listens for button events class ButtonListener implements ActionListener { ButtonListener() { } public void actionPerformed(ActionEvent e) { // The user wants to pause the display if (e.getActionCommand().equals("Pause")) { pauseButton.setText("Restart"); if (TtheApp!=null) TtheApp.setPauseScreen(true); } // The user wants to restart the display if (e.getActionCommand().equals("Restart")) { pauseButton.setText("Pause"); if (TtheApp!=null) TtheApp.setPauseScreen(false); } } } } ================================================ FILE: src/main/java/com/dmr/ShortLC.java ================================================ package com.dmr; public class ShortLC { private boolean dataReady; private String line; private boolean rawData[]=new boolean[69]; private boolean crcResult=false; private int currentState=-1; private DMRDecode TtheApp; // Add data to the Short LC data buffer // Type 0 if First fragment of LC // Type 1 if Continuation fragment of LC // Type 2 if Last fragment of LC public void addData (boolean[] CACHbuf,int type) { int a,b=7,rawCounter=0; dataReady=false; // First fragment ? // If so reset the the counters if (type==0) { rawCounter=0; // Ensure nothing else has arrived before if (currentState!=-1) return; // Set the current state to 0 to indicate a first fragment has arrived currentState=0; // Clear the display line line=""; } // Continuation fragments else if (type==1) { if (currentState==0) { rawCounter=17; currentState=1; } else if (currentState==1) { rawCounter=34; currentState=2; } else if (currentState==-1) { rawCounter=0; return; } } // Last fragment else if (type==2) { // Ensure that a first fragment and two continuation fragments have arrived if (currentState!=2) { currentState=-1; rawCounter=0; return; } else rawCounter=51; } // Add the data for (a=rawCounter;a<(rawCounter+17);a++) { // Ignore the TACT rawData[a]=CACHbuf[b]; b++; } // Has the fragment ended ? if (type==2) { decode(); currentState=-1; rawCounter=0; } } // Make the text string available public String getLine() { return line; } // Tell the main object if decoded data is available public boolean isDataReady() { return dataReady; } // Tell the main object if the CRC and Hamming checks are OK public boolean isCRCgood() { return crcResult; } // Clear the data ready boolean public void clrDataReady() { dataReady=false; } // Deinterleave and error check the short LC public void decode() { crcResult=false; if (shortLCHamming(rawData)==true) { boolean shortLC[]=deInterleaveShortLC(rawData); if (shortLCcrc(shortLC)==true) { line=decodeShortLC(shortLC); crcResult=true; } } else line=""; dataReady=true; } // Deinterleave a Short LC from 4 CACH bursts private boolean[] deInterleaveShortLC (boolean raw[]) { int a,pos; final int sequence[]={ 0,4,8,12,16,20,24,28,32,36,40,44, 1,5,9,13,17,21,25,29,33,37,41,45, 2,6,10,14,18,22,26,30,34,38,42,46}; boolean[] deinter=new boolean[36]; for (a=0;a<36;a++) { pos=sequence[a]; deinter[a]=raw[pos]; } return deinter; } // Hamming check 3 of the 4 CACH rows private boolean shortLCHamming (boolean raw[]) { int a,pos; final int sequence1[]={0,4,8,12,16,20,24,28,32,36,40,44}; final int sequence2[]={1,5,9,13,17,21,25,29,33,37,41,45}; final int sequence3[]={2,6,10,14,18,22,26,30,34,38,42,46}; final int ham1[]={48,52,56,60,64}; final int ham2[]={49,53,57,61,65}; final int ham3[]={50,54,58,62,66}; boolean[] d=new boolean[12]; boolean[] p=new boolean[5]; boolean[] c=new boolean[5]; // Row 1 for (a=0;a<12;a++) { pos=sequence1[a]; d[a]=raw[pos]; if (a<5) { pos=ham1[a]; p[a]=raw[pos]; } } c[0]=d[0]^d[1]^d[2]^d[3]^d[6]^d[7]^d[9]; c[1]=d[0]^d[1]^d[2]^d[3]^d[4]^d[7]^d[8]^d[10]; c[2]=d[1]^d[2]^d[3]^d[4]^d[5]^d[8]^d[9]^d[11]; c[3]=d[0]^d[1]^d[4]^d[5]^d[7]^d[10]; c[4]=d[0]^d[1]^d[2]^d[5]^d[6]^d[8]^d[11]; for (a=0;a<5;a++) { if (c[a]!=p[a]) return false; } // Row 2 for (a=0;a<12;a++) { pos=sequence2[a]; d[a]=raw[pos]; if (a<5) { pos=ham2[a]; p[a]=raw[pos]; } } c[0]=d[0]^d[1]^d[2]^d[3]^d[6]^d[7]^d[9]; c[1]=d[0]^d[1]^d[2]^d[3]^d[4]^d[7]^d[8]^d[10]; c[2]=d[1]^d[2]^d[3]^d[4]^d[5]^d[8]^d[9]^d[11]; c[3]=d[0]^d[1]^d[4]^d[5]^d[7]^d[10]; c[4]=d[0]^d[1]^d[2]^d[5]^d[6]^d[8]^d[11]; for (a=0;a<5;a++) { if (c[a]!=p[a]) return false; } // Row 3 for (a=0;a<12;a++) { pos=sequence3[a]; d[a]=raw[pos]; if (a<5) { pos=ham3[a]; p[a]=raw[pos]; } } c[0]=d[0]^d[1]^d[2]^d[3]^d[6]^d[7]^d[9]; c[1]=d[0]^d[1]^d[2]^d[3]^d[4]^d[7]^d[8]^d[10]; c[2]=d[1]^d[2]^d[3]^d[4]^d[5]^d[8]^d[9]^d[11]; c[3]=d[0]^d[1]^d[4]^d[5]^d[7]^d[10]; c[4]=d[0]^d[1]^d[2]^d[5]^d[6]^d[8]^d[11]; for (a=0;a<5;a++) { if (c[a]!=p[a]) return false; } // All done so must have passed return true; } // Test if the short LC passes its CRC8 test private boolean shortLCcrc (boolean dataBits[]) { int a; crc tCRC=new crc(); tCRC.setCrc8Value(0); for (a=0;a0) d[0]=true; else d[0]=false; if ((a&64)>0) d[1]=true; else d[1]=false; if ((a&32)>0) d[2]=true; else d[2]=false; if ((a&16)>0) d[3]=true; else d[3]=false; if ((a&8)>0) d[4]=true; else d[4]=false; if ((a&4)>0) d[5]=true; else d[5]=false; if ((a&2)>0) d[6]=true; else d[6]=false; if ((a&1)>0) d[7]=true; else d[7]=false; // Shift the value 12 times to the left value[a]=a<<12; // Calculate the parity bits p[0]=d[1]^d[4]^d[5]^d[6]^d[7]; p[1]=d[1]^d[2]^d[4]; p[2]=d[0]^d[2]^d[3]^d[5]; p[3]=d[0]^d[1]^d[3]^d[4]^d[6]; p[4]=d[0]^d[1]^d[2]^d[4]^d[5]^d[7]; p[5]=d[0]^d[2]^d[3]^d[4]^d[7]; p[6]=d[3]^d[6]^d[7]; p[7]=d[0]^d[1]^d[5]^d[6]; p[8]=d[0]^d[1]^d[2]^d[6]^d[7]; p[9]=d[2]^d[3]^d[4]^d[5]^d[6]; p[10]=d[0]^d[3]^d[4]^d[5]^d[6]^d[7]; p[11]=d[1]^d[2]^d[3]^d[5]^d[7]; // Add these to the lower bits of the valid words if (p[0]==true) value[a]=value[a]+2048; if (p[1]==true) value[a]=value[a]+1024; if (p[2]==true) value[a]=value[a]+512; if (p[3]==true) value[a]=value[a]+256; if (p[4]==true) value[a]=value[a]+128; if (p[5]==true) value[a]=value[a]+64; if (p[6]==true) value[a]=value[a]+32; if (p[7]==true) value[a]=value[a]+16; if (p[8]==true) value[a]=value[a]+8; if (p[9]==true) value[a]=value[a]+4; if (p[10]==true) value[a]=value[a]+2; if (p[11]==true) value[a]=value[a]+1; } // Just something to break on ! return true; } // Check if a 20 bit boolean array has the collect Golay (20,8) coding private boolean checkGolay208 (boolean[] word) { int a,golayValue; // A complete list of valid slot type words // This was generated by the calcGolay208 () method final int[]GolayNums={0, 6379, 10558, 12757, 19095, 21116, 25513, 31554, 36294, 38189, 42232, 48147, 51025, 57274, 61039, 63108, 66407, 72588, 76377, 78514, 84464, 86299, 90318, 96293, 102049, 104010, 108447, 114548, 115766, 122077, 126216, 128483, 132813, 138790, 143347, 145176, 150618, 152753, 157028, 163215, 166667, 168928, 172597, 178910, 180636, 186743, 190626, 192585, 198058, 204097, 208020, 210047, 216893, 219094, 222723, 229096, 231532, 233607, 237906, 244153, 246523, 252432, 256965, 258862, 265625, 267634, 271527, 277580, 280334, 286693, 290352, 292571, 295007, 301236, 305505, 307594, 314056, 315939, 320502, 326429, 331518, 333333, 337856, 343851, 345193, 351362, 355671, 357820, 361272, 367571, 371206, 373485, 379311, 381252, 385169, 391290, 396116, 398271, 402026, 408193, 410051, 416040, 420093, 421910, 427666, 433785, 438188, 440135, 445445, 447726, 451899, 458192, 460851, 463064, 467213, 473574, 475812, 481871, 486298, 488305, 493045, 498974, 502987, 504864, 511842, 513929, 517724, 523959, 525274, 531249, 535268, 537103, 543053, 545190, 548979, 555160, 560668, 562935, 567074, 573385, 574603, 580704, 585141, 587102, 590013, 596054, 600451, 602472, 608810, 611009, 615188, 621567, 626043, 628112, 631877, 638126, 641004, 646919, 650962, 652857, 656663, 663036, 666665, 668866, 675712, 677739, 681662, 687701, 690385, 692282, 696815, 702724, 705094, 711341, 715640, 717715, 722544, 728731, 733006, 735141, 740583, 742412, 746969, 752946, 756662, 758621, 762504, 768611, 770337, 776650, 780319, 782580, 790083, 792232, 796541, 802710, 804052, 810047, 814570, 816385, 820101, 826222, 830139, 832080, 837906, 840185, 843820, 850119, 855332, 857551, 861210, 867569, 870323, 876376, 880269, 882278, 884962, 890889, 895452, 897335, 903797, 905886, 910155, 916384, 919694, 921701, 926128, 932187, 934425, 940786, 944935, 947148, 951624, 957859, 961654, 963741, 970719, 972596, 976609, 982538, 986089, 987906, 991959, 997948, 999806, 1005973, 1009728, 1011883, 1017391, 1023684, 1027857, 1030138, 1035448, 1037395, 1041798, 1047917}; // Convert the boolean array into an integer if (word[19]==true) golayValue=1; else golayValue=0; if (word[18]==true) golayValue=golayValue+2; if (word[17]==true) golayValue=golayValue+4; if (word[16]==true) golayValue=golayValue+8; if (word[15]==true) golayValue=golayValue+16; if (word[14]==true) golayValue=golayValue+32; if (word[13]==true) golayValue=golayValue+64; if (word[12]==true) golayValue=golayValue+128; if (word[11]==true) golayValue=golayValue+256; if (word[10]==true) golayValue=golayValue+512; if (word[9]==true) golayValue=golayValue+1024; if (word[8]==true) golayValue=golayValue+2048; if (word[7]==true) golayValue=golayValue+4096; if (word[6]==true) golayValue=golayValue+8192; if (word[5]==true) golayValue=golayValue+16384; if (word[4]==true) golayValue=golayValue+32768; if (word[3]==true) golayValue=golayValue+65536; if (word[2]==true) golayValue=golayValue+131072; if (word[1]==true) golayValue=golayValue+262144; if (word[0]==true) golayValue=golayValue+524288; // Run through the possible values and we have a match return true for (a=0;a<256;a++) { if (golayValue==GolayNums[a]) return true; } // No matches so we must have a problem and so should return false return false; } // Let the main program know if there is an error in the frame public boolean isPassErrorCheck() { return passErrorCheck; } // Return the data type public int returnDataType () { return dataType; } } ================================================ FILE: src/main/java/com/dmr/SocketOut.java ================================================ package com.dmr; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SocketOut implements Runnable { private boolean ready; private static int PORT=17887; private static int MAXCONNS=10; private ServerSocket serversocket; private Socket socket[]=new Socket[MAXCONNS]; private boolean socketStatus[]=new boolean[MAXCONNS]; private PrintWriter socketPrintWriter[]=new PrintWriter[MAXCONNS]; public SocketOut (DMRDecode theApp) { ready=false; } // Main public void run() { int next; // Run continously for (;;) { // Wait for a socket connection if the listening socket has been setup // and there is a free socket available if(ready == false){ try{ Thread.sleep(1000); }catch(InterruptedException ie){ } continue; } if ((ready==true)&&(checkForFreeSockets()==true)) { // Get the index of the next available socket next=nextFreeSocket(); // Wait for a connection and when it arrives use the free socket waitForConnection(next); } } } // Setup a listening TCP/IP socket public boolean setupSocket() { try { serversocket=new ServerSocket(PORT); } catch (Exception e) { return false; } ready=true; return true; } // Wait for a connection to the next free socket private void waitForConnection(int n) { try { // Wait for the connection socket[n]=serversocket.accept(); // Assign a PrintWriter to this socket socketPrintWriter[n]=new PrintWriter(new OutputStreamWriter(socket[n].getOutputStream(),"8859_1")); // Send "OK" to the connected client socketPrintWriter[n].println("OK"); socketPrintWriter[n].flush(); } catch (Exception e) { return; } socketStatus[n]=true; } // Return the next free socket private int nextFreeSocket() { int a; for (a=0;a0&&i0) out[a]=true; else out[a]=false; if ((tribit[b]&2)>0) out[a+1]=true; else out[a+1]=false; if ((tribit[b]&1)>0) out[a+2]=true; else out[a+2]=false; // Increment the bit counter b++; } return out; } } ================================================ FILE: src/main/java/com/dmr/UsersLogged.java ================================================ package com.dmr; public class UsersLogged { private static final int MAX=8192; private int userCounter=0; private int ident[]=new int[MAX]; private boolean group[]=new boolean[MAX]; private boolean dataUser[]=new boolean[MAX]; private boolean groupCallUser[]=new boolean[MAX]; private boolean unitCallUser[]=new boolean[MAX]; private boolean usedChannel1[]=new boolean[MAX]; private boolean usedChannel2[]=new boolean[MAX]; // Adds a user and returns TRUE if this has been done public boolean addUser (int tident) { int a; // Check the buffer isn't full if (userCounter==(MAX-1)) return false; // Check if the user already exists for (a=0;aident[j+1]) { // Ident // temp=ident[j]; ident[j]=ident[j+1]; ident[j+1]=temp; // Group/User btemp=group[j]; group[j]=group[j+1]; group[j+1]=btemp; // Data User btemp=dataUser[j]; dataUser[j]=dataUser[j+1]; dataUser[j+1]=btemp; // Group Call User btemp=groupCallUser[j]; groupCallUser[j]=groupCallUser[j+1]; groupCallUser[j+1]=btemp; // Unit Call User btemp=unitCallUser[j]; unitCallUser[j]=unitCallUser[j+1]; unitCallUser[j+1]=btemp; // Used Channel 1 btemp=usedChannel1[j]; usedChannel1[j]=usedChannel1[j+1]; usedChannel1[j+1]=btemp; // Used Channel 2 btemp=usedChannel2[j]; usedChannel2[j]=usedChannel2[j+1]; usedChannel2[j+1]=btemp; flag=true; } if (flag==false) break; } } // Return a formatted info line public String returnInfo (int index) { int items=0; String l=Integer.toString(ident[index]); if (group[index]==true) l=l+" GROUP"; if (dataUser[index]==true) { items++; l=l+" Data "; } if (groupCallUser[index]==true) { if (items>0) l=l+"+"; items++; l=l+" Group Calls "; } if (unitCallUser[index]==true) { if (items>0) l=l+"+"; items++; l=l+" Unit to Unit Calls "; } if (items==1) l=l+"only"; // Channels if ((usedChannel1[index]==true)&&(usedChannel2[index]==false)) l=l+" (Only used channel 1)"; else if ((usedChannel2[index]==true)&&(usedChannel1[index]==false)) l=l+" (Only used channel 2)"; else if ((usedChannel1[index]==true)&&(usedChannel2[index]==true)) l=l+" (Used both channels)"; // All done return l; } // Clear all stored records public void clearAll() { userCounter=0; } } ================================================ FILE: src/main/java/com/dmr/Utilities.java ================================================ package com.dmr; public class Utilities { // Given a MFID as an int return the manufacturers name as a String public String returnMFIDName (int mfid) { if (mfid==0x04) return "Fylde Micro"; else if (mfid==0x05) return "PROD-EL SPA"; else if (mfid==0x06) return "Trident Datacom"; else if (mfid==0x07) return "RADIODATA"; else if ((mfid==0x08)||(mfid==0x68)) return "HYT"; else if (mfid==0x10) return "Motorola"; else if ((mfid==0x13)||(mfid==0x1c)) return "EMC SPA"; else if ((mfid==0x33)||(mfid==0x3c)) return "Radio Activity Srl"; else if (mfid==0x58) return "Tait"; else if (mfid==0x77) return "Vertex Standard"; else return "Unknown"; } // Return a 24 bit address public int retAddress (boolean bits[],int offset) { int addr=0,a,b,c; for (a=0;a<24;a++) { b=(24-a)-1; c=(int)Math.pow(2.0,b); if (bits[a+offset]==true) addr=addr+c; } return addr; } // Return an 16 bit byte from a boolean array public int retSixteen (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=32768; if (bits[offset+1]==true) b=b+16384; if (bits[offset+2]==true) b=b+8192; if (bits[offset+3]==true) b=b+4096; if (bits[offset+4]==true) b=b+2048; if (bits[offset+5]==true) b=b+1024; if (bits[offset+6]==true) b=b+512; if (bits[offset+7]==true) b=b+256; if (bits[offset+8]==true) b=b+128; if (bits[offset+9]==true) b=b+64; if (bits[offset+10]==true) b=b+32; if (bits[offset+11]==true) b=b+16; if (bits[offset+12]==true) b=b+8; if (bits[offset+13]==true) b=b+4; if (bits[offset+14]==true) b=b+2; if (bits[offset+15]==true) b++; return b; } // Return an 12 bit byte from a boolean array public int retTwelve (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=2048; if (bits[offset+2]==true) b=b+1024; if (bits[offset+2]==true) b=b+512; if (bits[offset+3]==true) b=b+256; if (bits[offset+4]==true) b=b+128; if (bits[offset+5]==true) b=b+64; if (bits[offset+6]==true) b=b+32; if (bits[offset+7]==true) b=b+16; if (bits[offset+8]==true) b=b+8; if (bits[offset+9]==true) b=b+4; if (bits[offset+10]==true) b=b+2; if (bits[offset+11]==true) b++; return b; } // Return an 9 bit byte from a boolean array public int retNine (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=256; if (bits[offset+1]==true) b=b+128; if (bits[offset+2]==true) b=b+64; if (bits[offset+3]==true) b=b+32; if (bits[offset+4]==true) b=b+16; if (bits[offset+5]==true) b=b+8; if (bits[offset+6]==true) b=b+4; if (bits[offset+7]==true) b=b+2; if (bits[offset+7]==true) b++; return b; } // Return an 8 bit byte from a boolean array public int retEight (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=128; if (bits[offset+1]==true) b=b+64; if (bits[offset+2]==true) b=b+32; if (bits[offset+3]==true) b=b+16; if (bits[offset+4]==true) b=b+8; if (bits[offset+5]==true) b=b+4; if (bits[offset+6]==true) b=b+2; if (bits[offset+7]==true) b++; return b; } // Return an 7 bit byte from a boolean array public int retSeven (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=64; if (bits[offset+1]==true) b=b+32; if (bits[offset+2]==true) b=b+16; if (bits[offset+3]==true) b=b+8; if (bits[offset+4]==true) b=b+4; if (bits[offset+5]==true) b=b+2; if (bits[offset+6]==true) b++; return b; } // Return a 6 bit byte from a boolean array public int retSix (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=32; if (bits[offset+1]==true) b=b+16; if (bits[offset+2]==true) b=b+8; if (bits[offset+3]==true) b=b+4; if (bits[offset+4]==true) b=b+2; if (bits[offset+5]==true) b++; return b; } // Return a 5 bit byte from a boolean array public int retFive (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=16; if (bits[offset+1]==true) b=b+8; if (bits[offset+2]==true) b=b+4; if (bits[offset+3]==true) b=b+2; if (bits[offset+4]==true) b++; return b; } // Return a 4 bit byte from a boolean array public int retFour (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=8; if (bits[offset+1]==true) b=b+4; if (bits[offset+2]==true) b=b+2; if (bits[offset+3]==true) b++; return b; } // Return a 3 bit byte from a boolean array public int retThree (boolean bits[],int offset) { int b=0; if (bits[offset]==true) b=4; if (bits[offset+1]==true) b=b+2; if (bits[offset+2]==true) b++; return b; } // Decode and display Service Options public String decodeServiceOptions (boolean bits[],int offset) { int priority; StringBuilder so=new StringBuilder(300); so.append("Service Options : "); // Emergency if (bits[offset]==false) so.append("Non-emergency"); else so.append("Emergency"); // Privacy if (bits[offset+1]==true) so.append("/Privacy Enabled"); // +2 and +3 are reserved bits // +4 is Broadcast if (bits[offset+4]==true) so.append("/Broadcast"); // +5 is OVCM if (bits[offset+5]==true) so.append("/OVCM Call"); // 6 and 7 are priority if (bits[offset+6]==true) priority=2; else priority=0; if (bits[offset+7]==true) priority++; if (priority==0) so.append("/No priority"); else so.append("/Priority "+Integer.toString(priority)); return so.toString(); } } ================================================ FILE: src/main/java/com/dmr/VoiceData.java ================================================ package com.dmr; import java.io.FileWriter; public class VoiceData { // Handle incoming voice data public void handleVoice (DMRDecode tTheApp,byte[] dibit_buf) { boolean bits[]=new boolean[216]; int vdata[]=new int[27]; // Extract the bits bits=extractVoiceBits(dibit_buf); // Pack the bits into an int array vdata=packBits(bits); // Send the data via the sockets tTheApp.socketThread.sendVoiceViaSocket(vdata,tTheApp.currentChannel); } // Get the voice data bits private boolean[] extractVoiceBits (byte dibit_buf[]) { int a,r=0; boolean rawData[]=new boolean[216]; // First block for (a=12;a<66;a++) { if (dibit_buf[a]==0) { rawData[r]=false; rawData[r+1]=false; } else if (dibit_buf[a]==1) { rawData[r]=false; rawData[r+1]=true; } else if (dibit_buf[a]==2) { rawData[r]=true; rawData[r+1]=false; } else if (dibit_buf[a]==3) { rawData[r]=true; rawData[r+1]=true; } r=r+2; } // Second block for (a=90;a<144;a++) { if (dibit_buf[a]==0) { rawData[r]=false; rawData[r+1]=false; } else if (dibit_buf[a]==1) { rawData[r]=false; rawData[r+1]=true; } else if (dibit_buf[a]==2) { rawData[r]=true; rawData[r+1]=false; } else if (dibit_buf[a]==3) { rawData[r]=true; rawData[r+1]=true; } r=r+2; } return rawData; } // Pack the bits into an int array private int[] packBits(boolean bts[]) { int a,c=0; int by[]=new int[27]; for (a=0;a<216;a=a+8) { by[c]=convertByte(bts,a); c++; } return by; } // Get a single byte from the boolean array private int convertByte (boolean b[],int offset) { int dby=0; if (b[offset]==true) dby=127; if (b[offset+1]==true) dby=dby+64; if (b[offset+2]==true) dby=dby+32; if (b[offset+3]==true) dby=dby+16; if (b[offset+4]==true) dby=dby+8; if (b[offset+5]==true) dby=dby+4; if (b[offset+6]==true) dby=dby+2; if (b[offset+7]==true) dby++; return dby; } // A function to save voice data to enable debugging private void voiceDump (int vdata[]) { try { int a; StringBuilder vline=new StringBuilder(500); FileWriter vfile=new FileWriter("voice.csv",true); // Run through all 27 ints for (a=0;a<27;a++) { if (a>0) vline.append(","); vline.append(Integer.toString(vdata[a])); } vline.append("\r\n"); vfile.write(vline.toString()); vfile.flush(); vfile.close(); }catch (Exception e) { System.err.println("Error: " + e.getMessage()); } } } ================================================ FILE: src/main/java/com/dmr/crc.java ================================================ package com.dmr; public class crc { private int crc8Value,crc16Value; public void setCrc8Value(int crc8Value) { this.crc8Value = crc8Value; } public int getCrc8Value() { return crc8Value; } // The CRC8 routine // public void crc8(boolean bit) { boolean shiftBit; if ((crc8Value&0x01)>0) shiftBit=true; else shiftBit=false; crc8Value=crc8Value>>1; if ((bit^shiftBit)==true) crc8Value=crc8Value^0xe0; } // The CCITT CRC16 routine // private void ccitt_crc16(int in) { boolean c15,bit; byte c=(byte)in; for (int i=0;i<8;i++) { c15=((crc16Value>>15&1)== 1); bit=((c>>(7-i)&1)==1); crc16Value<<=1; if (c15^bit) crc16Value^=0x1021; } crc16Value=crc16Value&0xffff; } // CSBK CRC check public boolean crcCSBK (boolean in[]) { int a,b,val; crc16Value=0; // Run through all 96 bits for (a=0;a<96;a=a+8) { val=0; for (b=0;b<8;b++) { if (in[a+b]==true) val=val+(int)Math.pow(2.0,(7.0-b)); } // Allow for the CSBK CRC mask if (a>=80) val=val^0xA5; ccitt_crc16(val); } if (crc16Value==0x1D0F) return true; else return false; } // Data Header CRC check public boolean crcDataHeader (boolean in[]) { int a,b,val; crc16Value=0; // Run through all 96 bits for (a=0;a<96;a=a+8) { val=0; for (b=0;b<8;b++) { if (in[a+b]==true) val=val+(int)Math.pow(2.0,(7.0-b)); } // Allow for the Data Header CRC mask if (a>=80) val=val^0xCC; ccitt_crc16(val); } if (crc16Value==0x1D0F) return true; else return false; } // Reed-Solomon (12,9) check // TODO : Get the Reed Solomon (12,9) check routine working public boolean RS129 (boolean in[]) { int a,b,d,byteCount=0; int inBytes[]=new int[12]; // Convert from binary to an array of integers for (a=0;a<96;a=a+8) { inBytes[byteCount]=0; for (b=0;b<8;b++) { d=(int)Math.pow(2.0,((8-b)-1)); if (in[a+b]==true) inBytes[byteCount]=inBytes[byteCount]+d; } byteCount++; } return false; } // CRC 5 check public boolean crcFiveBit (boolean in[],int tcrc) { int a,b=0,oct=0,total=0; // Convert the boolean array into an array of ints for (a=0;a<72;a++) { if (in[a]==true) oct=oct+(int)Math.pow(2.0,(int)b); b++; if (b==8) { b=0; total=total+oct; oct=0; } } total=total%31; if (total==tcrc) return true; else return false; } } ================================================ FILE: src/main/java/test/com/dmr/BPTC19696Test.java ================================================ package test.com.dmr; import java.util.Arrays; import com.dmr.BPTC19696; import junit.framework.TestCase; public class BPTC19696Test extends TestCase { public void testdecode () { boolean ok; boolean idleContents[]={ true,true,true,true,true,true,true,true, true,false,false,false,false,false,true,true,true,true,false, true,true,true,true,true,false,false,false,true,false,true, true,true,false,false,true,true,false,false,true,false,false, false,false,false,true,false,false,true,false,true,false,false, true,true,true,false,true,true,false,true,false,false,false, true,true,true,true,false,false,true,true,true,true,true, false,false,true,true,false,true,true,false,false,false,true, false,true,false,true,false,false,true,false,false,false,true }; byte uniFrame_good[]={3,3,2,0,2,0,3,3,1,3,0,2,1,1,0,3,3,0,0,2,1,1,3,2,2,2,2,3,2,2,2,0,1,2,1,3,0,1,3,1,3,0,1,3,0,3,2,0,0,3,2,3,3,1,2,1,0,3,1,2,0,1,1,2,1,2,3,1,3,3,3,3,1,1,1,3,3,1,1,3,1,1,3,1,3,3,1,1,3,1,3,3,1,0,3,3,3,3,1,2,3,2,1,0,1,2,1,1,0,1,1,3,0,1,2,3,1,0,2,0,3,0,2,2,1,2,3,1,1,0,3,3,3,0,1,2,0,1,0,0,2,3,1,0}; byte uniFrame_bad[]= {3,3,2,0,2,0,3,3,1,3,0,2,1,1,0,3,3,0,0,2,1,1,3,3,2,2,2,3,2,2,2,0,1,2,1,3,0,1,3,1,3,0,1,3,0,3,2,0,0,3,2,3,3,1,2,1,0,3,1,2,0,1,1,2,1,2,3,1,3,3,3,3,1,1,1,3,3,1,1,3,1,1,3,1,3,3,1,1,3,1,3,3,1,0,3,3,3,3,1,2,3,2,1,0,1,2,1,1,0,1,1,3,0,1,2,3,1,0,2,0,3,0,2,2,1,2,3,1,1,0,3,3,3,0,1,2,0,1,0,0,2,3,1,0}; BPTC19696 bptc19696=new BPTC19696(); ok=bptc19696.decode(uniFrame_good); assertEquals(true,ok); boolean data[]=bptc19696.dataOut(); ok=Arrays.equals(data,idleContents); assertEquals(true,ok); ok=bptc19696.decode(uniFrame_bad); assertEquals(false,ok); } } ================================================ FILE: src/main/java/test/com/dmr/SlotTypeTest.java ================================================ package test.com.dmr; import com.dmr.SlotType; import junit.framework.TestCase; public class SlotTypeTest extends TestCase { public void testDecode () { byte uniFrame[]={2,0,2,0,2,0,0,2,0,0,0,0,1,1,0,3,3,0,0,2,1,1,3,2,2,2,2,3,2,2,2,0,1,2,1,3,0,1,3,1,3,0,1,3,0,3,2,0,0,3,2,3,3,1,2,1,0,3,1,2,0,1,1,2,1,2,3,1,3,3,3,3,1,1,1,3,3,1,1,3,1,1,3,1,3,3,1,1,3,1,3,3,1,0,3,3,3,3,1,2,3,2,1,0,1,2,1,1,0,1,1,3,0,1,2,3,1,0,2,0,3,0,2,2,1,2,3,1,1,0,3,3,3,0,1,2,0,1,0,0,2,3,1,0}; SlotType slottype=new SlotType(); String sret=slottype.decode(null, uniFrame); assertEquals("Slot Type : Colour Code 5 Idle",sret); } } ================================================ FILE: src/main/java/test/com/dmr/TrellisTest.java ================================================ package test.com.dmr; import junit.framework.TestCase; import com.dmr.Trellis; public class TrellisTest extends TestCase { public void testTrellis() { // Three good sample 3/4 rate frames final boolean threequarterData1[]={ false,false,true,false,true,true,true,false,true,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, true,true,true,false,true,false,false,true,false,true, true,false,false,false,false,true,false,true,true,true, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,true,false,false,true,false,true,true, false,false,true,true,false,false,false,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,true,true,false,true, true,false,false,true,false,true,true,true,false,false, false,false,false,true,true,true,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,true,false, true,true,true,true,false,false}; final boolean threequarterData2[]={ false,false,true,false,true,false,true,true,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,true,true,true,false,true,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,true,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,true,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false,false,false,true,false, false,false,true,false,false,false,true,false,false,false, true,false,false,false,true,false}; final boolean threequarterData3[]={ false,false,true,false,true,false,false,false,false,true, true,false,true,true,true,false,false,false,true,false, false,false,true,false,true,false,false,true,true,true, false,true,false,false,true,false,true,true,false,false, true,true,true,false,false,false,true,false,false,false, true,false,false,false,true,false,true,true,true,true, false,false,true,false,true,true,true,true,false,false, true,false,false,false,true,false,false,false,true,false, true,true,true,false,false,false,true,false,true,true, true,true,false,false,true,false,false,false,true,false, false,false,true,false,true,true,false,true,false,false, true,false,true,true,true,true,true,true,true,false, false,false,true,false,false,false,false,true,true,true, true,true,true,true,true,false,false,true,true,true, false,false,true,false,false,false,true,false,false,false, true,false,false,true,false,true,false,false,true,false, false,false,true,false,true,false,true,false,false,false, false,true,false,true,false,false,false,false,true,false, true,false,false,true,true,true,false,true,false,false, true,false,false,false,true,false}; // and a bad frame final boolean threequarterBadData1[]={ false,false,true,false,false,false,false,false,false,true, true,false,true,true,true,false,false,false,true,false, false,false,true,false,true,false,false,true,true,true, false,true,false,false,true,false,true,true,false,false, true,true,true,false,false,false,true,false,false,false, true,false,false,false,true,false,true,true,true,true, false,false,true,false,true,true,true,true,false,false, true,false,false,false,true,false,false,false,true,false, true,true,true,true,false,false,true,false,true,true, true,true,false,false,true,false,false,false,true,false, false,false,true,false,true,true,false,true,false,false, true,false,true,true,true,true,true,true,true,false, false,false,true,false,false,false,false,true,true,true, true,true,true,true,true,false,false,true,true,true, false,false,true,false,false,false,true,false,false,false, true,false,false,true,false,true,false,false,true,false, false,false,true,false,true,false,true,false,false,false, false,true,true,false,false,false,false,false,true,false, true,false,false,true,true,true,false,true,false,false, true,false,false,false,true,false}; // Now to test the Trellis class Trellis trellis=new Trellis(); // Good frame 1 boolean tst1[]=trellis.decode(threequarterData1); assertNotNull(tst1); // Good frame 2 boolean tst2[]=trellis.decode(threequarterData2); assertNotNull(tst2); // Good frame 3 boolean tst3[]=trellis.decode(threequarterData3); assertNotNull(tst3); // Bad frame boolean tst4[]=trellis.decode(threequarterBadData1); assertNull(tst4); } } ================================================ FILE: src/main/java/test/com/dmr/crcTest.java ================================================ package test.com.dmr; import com.dmr.crc; import junit.framework.TestCase; public class crcTest extends TestCase { // Test the CRC8 code public void testCRC8 () { int a,returnCRC; crc crctest=new crc(); boolean testBinaryPass[]={true,true,true,true,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,false,false}; boolean testBinaryFail[]={false,true,true,true,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,true,false,false}; // Test for a pass crctest.setCrc8Value(0); for (a=0;a