Parent Web Page
/**
* Bandscope Receiver Applet 1.0.0
* @author Robert J Morton <robmorton@clara.net>
* @version 13 March 2002
* @copyright Robert J Morton (all rights reserved) */
/* This applet conforms to API 1.1
Contains a method computePlots() that is specific to the way the
AOR AR8600 Communications Receiver provides bandscope data via
its RS232 port. This applet gets its bandscope data via data files
acquired via a web server from an RS232 port server with an AR8600
specific data handling method - all written entirely in Java. */
import java.applet.*; //all the gubbins to make the applet work
import java.awt.*; //for graphics operations (GUI)
import java.awt.event.*; //for the new-fangled 1.1 event handling
import java.net.*; //for downloading data from the remote server
import java.io.*; //for stream handling for the above
import java.util.Date; //for bandscope data file date formatting
public class bs extends Applet implements Runnable {
AppletContext ac; //get details of HTML document this applet is running in
Date D;
String cb; //code base URL - where this applet's class file came from
URL url; //url of current HTML file
InputStream I; //input stream for downloading index or current HTML file
int L = 0; //length of the remote item being loaded
int l = 0; //number of bytes of the above successfully downloaded
byte B[]; //gigantic byte array to hold the downloaded signal data
int w; //pixel counter used on horizontal axis of graph
int fh; //font height - total height area needed for a line of text
int fa; //font ascent - distance from text base line to top of text area
Thread TH; //reference for a separate Internet Data Transfer thread
int lp = 1, LP = 1; //indicates current and previous download phases
String E; //for Exception during downloading + method where it occurred
int W = 400; //width of applet panel
int H = 235; //height of applet panel
int GW = 200; //graph width
int GH = 96; //graph height
int GL = 80; //Graph Left: x-co-ordinate of left side of graph area
int GC = GL + 100; //horizontal centre of graph
int GR = GL + GW; //Graph Right: x-co-ordinate of right side of graph area
int GB = 150; //Graph Bottom: panel y-co-ordinate of bottom of graph area
int GT = GB - GH; //Graph Top: panel y-co-ordinate of top of graph area
int VC = GB - GH / 2; //vertical centre of graph
int hS = 0; //graph x co-ordinate of highlighted signal
int mS = 0; //S co-ordinate within graph area
int rS = 0; //receiver's signal strength
int rF = 0; //receiver frequency in integral half-kHz
int mF = 0; //frequency of mouse position on graph
int redraw = 0; //redraw task switch
int SCH = 145; //height of top of span choice box above bottom of graph
int ADH = -20; //height of age data message relative to bottom of graph
long lm = 0; //when current data file last modified (seconds since 1970/01/01/00:00Z)
long T = 0; //last time's System time
String LM = ""; //date when data file last modified
String AgeMsg = ""; //age message for current graph data
String oldAge = ""; //to preserve the part of the age string that changes
Button EntBut; //button to enter the centre frequency
Button PulBut; //button to pull or re-pull bandscope data from server
Button UpBut; //for inching receiver frequency up
Button DnBut; //for inching receiver frequency down
Button HldBut; //for the Hold button - causes scope data to accumulate
Label FrqLab; //label for the centre-frequency entry field
TextField FrqFld; //centre frequency entry field
Label SpanLab; //Scope Frequency Span
Choice Span; //choices of bandscope frequency span
Choice BandTypes; //choices of radio band types
Choice Bands; //choices of radio band
Choice Update; //how often you update the trace
int sBT = 0; //choice index number of selected band type choice
int sbt = 0; //array index number of selected band type
int sB = 0; //choice index number of selected band choice
int sb = 0; //array index number of selected band
String FR = "50"; //Frequency Resolution of graph in kHz per pixel.
boolean dataOK = false; //data file is corrupt or contains wrong samples for selected band span
String dataMsg = ""; //'data unavailable' message
int Plots[] = new int[GW]; //to hold the graphical signal strength plots
String fn = "bs"; //default name for the bandscope data file
String mFs = "--------------MHz"; //mouse pointer frequency
String rFs; //receiving frequency
String PHmsg = "Peak Hold off"; //Peak Hold message
boolean peakHold = false; //Peak hold off/on status
boolean CFvalid = false; //centre frequency valid flag
boolean onGraph = true; //indicates when mouse is within graph area
boolean receiving = false; //receiver has been tuned to a frequency by mouse click
Color bg1 = new Color(210, 210, 210); //main background colour
Color bg2 = new Color(210, 195, 195); //graph background colour
Color bg3 = new Color(180, 180, 180); //graticule colour
Color amber = new Color(255, 255, 0); //for signals being monitored
int KP[] = { 100, 50, 20, 10, 5, 2, 1 }; //twice kilohertz per pixel for each selectable band span
int MC[] = {12000, 6000, 3000, 1200, 600, 220, 120 }; //minimum centre frequency (in half-kHz)
int FG[] = { 4000, 2000, 2000, 1000, 200, 200, 100 }; //half-kHz per annotated graduation on the frequency scale for each span choice
int FS[] = {10000, 4000, 2000, 1000, 400, 200, 100 }; //frequency to subtract to get first annotation (in half-kHz)
int NG[] = { 6, 5, 3, 3, 5, 3, 3 }; //number of annotated graduations
int SB[] = { 0, 20, 0, 0, 20, 0, 0 }; //starting bias in pixels
int FJ[] = { 40, 40, 100, 100, 40, 100, 100 }; //pixels to jump between successive annotations
int DP[] = { 0, 0, 1, 1, 2, 2, 2 }; //3 - number of required decimal places (including the decimal point itself)
int fs = 0; //selected frequency span number 0 = 10MHz ... 6 = 100kHz
int CF = 186000; //entered centre frequency in half-kHz
String Smeter[] = { //S-meter scale
"0", "", "2", "", "4", "", "6",
"", "8", "", "10", "", "12"
};
String bandNames[][] = {
{"None", "Long Wave", "Medium Wave", "120 metre", "90 metre", "75 metre", "60 metre", "49 metre", "39 metre", "31 metre", "25 metre", "22 metre", "19 metre", "16 metre", "13 metre", "11 metre", "FM (Low half)", "FM (High half)"},
{"None", "Top Band", "80 metre", "40 metre", "30 metre", "20 metre", "17 metre", "15 metre", "12 metre", "10 metre", "6 metre", "4 metre", "2 metre", "70 cm", "23 cm"},
{"None", "LF Navigation", "3 Meg", "31/2 Meg", "41/2 Meg", "51/2 Meg", "61/2 Meg", "9 Meg", "10 Meg", "11 Meg", "13 Meg", "15 Meg", "18 Meg", "22 Meg", "23 Meg", "VHF Civil (Lower Half)", "VHF Civil (Upper Half)"},
{"None", "2-3 Meg", "61/2 Meg", "81/2 Meg", "12-13 Meg", "18.8 Meg", "19.7 Meg", "22-23 Meg", "25 Meg", "Marine VHF"}
};
int bandCentres[][] = {
{250, 1000, 2400, 3300, 3950, 4900, 6100, 7200, 9700, 11800, 13700, 15550, 17700, 21650, 25850, 93000, 103000}, //broadcast
{1850, 3650, 7050, 10150, 14200, 18100, 21200, 24900, 29000, 51000, 70250, 145000, 435000, 1245000}, //amateur
{550, 3000, 3450, 4700, 5600, 6600, 8900, 10050, 11300, 13300, 15050, 17950, 21950, 23250, 123000, 133000}, //aircraft
{2500, 6350, 8500, 12800, 18850, 19750, 22600, 25300, 160000 } //marine
};
int bandStarts[][] = {
{150, 500, 2300, 3200, 3900, 4750, 5950, 7100, 9500, 11650, 13600, 15510, 17550, 21450, 25670, 88000, 98000},
{1810, 3500, 7000, 10100, 14000, 18068, 21000, 24890, 28000, 50000, 70025, 144000, 430000, 1240000},
{515, 2850, 3400, 4650, 5450, 6525, 8815, 10005, 11175, 13200, 15010, 17900, 21870, 23200, 118000, 128000},
{2000, 6200, 8195, 12330, 18780, 19680, 22000, 25010, 156000}
};
int bandEnds[][] = {
{350, 1500, 2498, 3400, 4000, 4995, 6200, 7300, 9900, 11975, 13800, 15600, 17900, 21850, 26100, 98000, 108000},
{1850, 3800, 7100, 10150, 14350, 18168, 21450, 24990, 29700, 52000, 70500, 146000, 440000, 1250000},
{540, 3150, 3500, 4750, 5730, 6765, 9040, 10100, 11400, 13360, 15100, 18030, 22000, 23350, 128000, 136950},
{2850, 6525, 8815, 13200, 18900, 19800, 22855, 25550, 162050}
};
int bandSpans[][] = {
{ 5, 3, 5, 5, 6, 4, 4, 5, 4, 4, 5, 6, 4, 4, 4, 0, 0 }, //broadcast
{ 6, 4, 6, 6, 4, 5, 4, 5, 2, 2, 4, 2, 0, 0 }, //amateur
{ 6, 4, 6, 6, 4, 4, 4, 6, 4, 5, 6, 5, 5, 5, 0, 0 }, //aircraft
{ 3, 4, 3, 3, 5, 5, 3, 3, 0 } //marine
};
String bandType[]= { //first 3 letters of bandscope data file names
"brd", //broadcast band
"ham", //amateur band
"air", //aeronautical band
"mar" //marine band
};
public void init() {
ac = getAppletContext(); //get details of HTML document this applet is running in
bg1 = getBackground(); //get host document's background colour
setBackground(bg1); //make this the applet panel's background colour
cb = getCodeBase().toString(); //URL (less file name) from where this applet came
if(!cb.endsWith("/")) { //workaround re Hotjava re Microsoft Intranet machine names
int x = cb.lastIndexOf('/'); //where getCodeBase() wrongly seems to return the
if(x != -1) cb = cb.substring(0, x + 1); //document base (circa Nov 1999)
}
setLayout(null); //to allow the control panels to be laid out manually
mml ce = new mml(this); //set up a mouse motion listener to
addMouseMotionListener(ce); //listen for mouse movements within this applet
ml cj = new ml(this); //set up a mouse listener to
addMouseListener(cj); //listen for mouse clicks etc within this applet
FrqFld = new TextField("93.000"); add(FrqFld); //set up centre frequency entry field
FrqFld.setBounds(GL + 70, GB + 35, 60, 20); //set its position and size on applet panel
bl ff = new bl(bl.FRQFLD, this); //set up action listener to
FrqFld.addActionListener(ff); //listen for c/r from text field
EntBut = new Button("Enter"); add(EntBut); //set up the Enter button
EntBut.setBounds(GL + 135, GB + 35, 40, 20);
bl eb = new bl(bl.ENTBUT, this);
EntBut.addActionListener(eb);
DnBut = new Button("-"); add(DnBut); //set up the Down button
DnBut.setBounds(GL + 82, GB - 114, 16, 16);
bl db = new bl(bl.DNBUT, this);
DnBut.addActionListener(db);
UpBut = new Button("+"); add(UpBut); //set up the Up button
UpBut.setBounds(GL + 102, GB - 114, 16, 16);
bl ub = new bl(bl.UPBUT, this);
UpBut.addActionListener(ub);
Span = new Choice(); //graph's frequency span choice box (located above the graph)
Span.add("10 MHz");
Span.add("5 MHz");
Span.add("2 MHz");
Span.add("1 MHz");
Span.add("500 kHz");
Span.add("200 kHz");
Span.add("100 kHz");
add(Span);
Span.setBounds(GL + 65, GB - SCH, 70, 20); //set its size and position on the applet panel
chl span = new chl(chl.SPAN, this); //set up listener for checkbox group
Span.addItemListener(span);
int x = 220; //x-bias of buttons and choices on right of graph
int dy = 48; //y-increment between successive buttons/choices
int y = 130; //y-bias of top choice object
int w = 95; //width of these choices/buttons
int d = 20; //depth of these choices/buttons
HldBut = new Button("Peak Hold"); //set up the HOLD button
add(HldBut);
HldBut.setBounds(GL + x, GB - y, w, d);
bl hb = new bl(bl.HLDBUT, this);
HldBut.addActionListener(hb);
y -= dy; //move down the panel to the position for the next Choice box
BandTypes = new Choice();
BandTypes.add("None");
BandTypes.add("Broadcast");
BandTypes.add("Amateur");
BandTypes.add("Aircraft");
BandTypes.add("Marine");
add(BandTypes);
BandTypes.setBounds(GL + x, GB - y, w, d);
chl bandtypes = new chl(chl.BANDTYPES, this); //set up listener for this choice menu
BandTypes.addItemListener(bandtypes);
y -= dy; //move down the panel to the position for the next Choice box
Bands = new Choice();
Bands.add("None"); //this is absolutely vital to avoid hang-up at ###1
add(Bands);
Bands.setBounds(GL + x, GB - y, w, d);
chl bands = new chl(chl.BANDS, this); //set up listener for this choice menu
Bands.addItemListener(bands);
y -= dy; //move down the panel to the position for the next Choice box
Update = new Choice();
Update.add("on demand");
Update.add("continuous");
Update.add("every minute");
Update.add("every 5 mins");
Update.add("every 10 mins");
Update.add("every 15 mins");
Update.add("every 20 mins");
Update.add("every 30 mins");
Update.add("every hour");
add(Update);
Update.setBounds(GL + x, GB - y, w, d);
chl update = new chl(chl.UPDATE, this);
Update.addItemListener(update); //set up listener for this choice menu
y = 60; //how far next button is below the baseline of the graph area
PulBut = new Button("Update Now"); //set up the Pull button
add(PulBut);
PulBut.setBounds(GL + x, GB + y, w, d);
bl pb = new bl(bl.PULBUT, this);
PulBut.addActionListener(pb);
TH = new Thread(this); //create a thread for downloading data from server
}
public void paint(Graphics g) {
FontMetrics fm = g.getFontMetrics(); //get dimensions of current type face
fh = fm.getHeight(); //get the full interline height of font
fa = fm.getAscent(); //how far top of lettering can be above base line
g.setColor(bg1); //applet's background colour
g.fillRect(0, 0, W, H); //clear the graph panel
//PUT THE 3-D EFFECT WHITE & BLACK LINES ROUND APPLET PANEL AND GRAPH AREA
g.setColor(Color.white);
int x = W - 1, y = H - 1; //x, y extents of applet panel
g.drawLine(0, 0, x, 0);
g.drawLine(0, 0, 0, y); //white top & left 3-D edges of applet panel
int x1 = GR + 1; //1 pixel beyond right graph edge
int x2 = GL - 1; //1 pixel before left graph edge
int y1 = GB + 1; //1 pixel below graph bottom
int y2 = GT - 1; //1 pixel above graph top
g.drawLine(x2, y1, x1, y1); //bottom white 3-D edge of graph area
g.drawLine(x1, y2, x1, y1); //right white 3-D edge of graph area
g.setColor(Color.black);
g.drawLine(0, y, x, y);
g.drawLine(x, 1, x, y); //black bottom & right 3-D edges of applet panel
g.drawLine(x2, y2, x1, y2); //black top 3-D edge of graph area
g.drawLine(x2, y2, x2, y1); //black right 3-D edge of graph area
y1 = GB + 5; y2 = GB + 10; //upper and lower extremities of graduation marks
x = 0; //set pixel counter to start of graph
for(int i = 0; i < 11; i++) { //for each frequency graduation
g.drawLine(GL + x, y1, GL + x, y2);//draw frequency graduation marks
x += 20; //move to position of next frequency graduation mark
}
g.drawLine(GL, y1, GR, y1); //horizontal axis (frequency)
y = GB - SCH + 10; //SCH = Span Choice Height
g.drawLine(GL, y, GL + 63, y); //horizontal left-hand span arrow
g.drawLine(GL, y, GL + 5, y - 3);
g.drawLine(GL, y, GL + 5, y + 3);
g.drawLine(GR - 63, y, GR, y); //horizontal right-hand span arrow
g.drawLine(GR - 5, y - 3, GR, y);
g.drawLine(GR - 5, y + 3, GR, y);
g.drawLine(GC, GB + 25, GC, GB + 40); //centre-line just above the centre frequency field
x = GR + 20; y = GT + 9; int dy = 48;
g.drawString("Select Band Type", x, y);
g.drawString("Select Band", x, y += dy);
g.drawString("Update trace...", x, y += dy);
rightString(g, "Centre Frequency MHz", GL + 65, GB + 50);
x1 = GL - 10; x2 = GL - 5; //DRAW THE S-METER SCALE AND LABELS
int x3 = GL - 20;
y = GB;
dy = fh >> 1; //put text base-line half text height below S-graduation mark
for(int i = 0; i < Smeter.length; i++) { //for each S-unit graduation
centreString(g, Smeter[i], x3, y + dy); //draw S-meter figure
g.drawLine(x1, y, x2, y); //draw graduation mark
y -= 8; //accumulated number of S-meter scale pixels
}
x = GL - 5;
g.drawLine(x, GB, x, GT); //vertical (signal strength) axis
x = GL - 55; y = GB - 51;
centreString(g, "Signal", x, y);
centreString(g, "Strength", x, y += fh);
y = GB - 5;
g.drawLine(x, GB, x, GB - 35); //lower arrow line
g.drawLine(x, GB, x - 3, y); //left half of lower arrow head
g.drawLine(x, GB, x + 3, y); //right half of lower arrow head
g.drawLine(x, GT, x, GT + 35); //upper arrow line
y = GT + 5;
g.drawLine(x, GT, x - 3, y); //left half of upper arrow head
g.drawLine(x, GT, x + 3, y); //right half of upper arrow head
redraw = 7; update(g); //paint/repaint the graph bars etc.
}
public void update(Graphics g) {
switch (redraw) {
case 1: //When mouse moves within graph area
drawMouseFreq(g); //redraw mouse frequency and S-meter
break;
case 2: //When mouse clicked on a signal
drawGraph(g); //redraw the graph and signals
break;
case 3: //When Span Choice or Band Choice is changed
drawFreqRes(g); //redraw 'kHz/pixel' message
case 4: //When new centre frequency is entered
drawFrequencyScale(g); //redraw horizontal frequency scale and kHz/pixel message
drawGraph(g); //redraw the graph and signals
break;
case 5: //When Peak Hold button pressed
drawPeakHoldMessage(g); //redraw the Peak Hold message
break;
case 6: //When new bandscope data is loaded
showTimeStamp(g); //display bandscope data file's time stamp and age message
break;
case 7: //full repaint
drawMouseFreq(g); //redraw mouse frequency and S-meter
drawFrequencyScale(g); //redraw horizontal frequency scale and kHz/pixel message
drawFreqRes(g); //redraw 'kHz/pixel' message
drawGraph(g); //redraw the graph and signals
drawPeakHoldMessage(g); //redraw the Peak Hold message
showTimeStamp(g); //display bandscope data file's time stamp and age message
}
}
void showTimeStamp(Graphics g) { //SHOW DATE AND AGE OF CURRENT BANDSCOPE DATA FOR SELECTED BAND
g.setColor(bg1);
int x1 = GL - 75;
int y1 = GB + 75; //base line of time stamp
int y2 = GB + 55; //base line of age message
g.fillRect(x1, y1 - fa, 295, fh); //clear the bandscope data file time stamp area
g.fillRect(GR + 20, y2 - fa, 95, fh); //clear the bandscope data age message above the Update Now button
g.setColor(Color.black);
g.drawString("Scanned: " + LM, x1, y1); //display data file time stamp
centreString(g, AgeMsg, GR + 67, y2); //display data file age message
}
void drawPeakHoldMessage(Graphics g) { //DRAW THE 'PEAK HOLD' MESSAGE ABOVE PEAK HOLD BUTTON
int y = GB - 135; //base line bias of message text
g.setColor(bg1);
g.fillRect(GR + 20, y - fa, 95, fh); //clear 'Peak Hold message' area
if(peakHold)
g.setColor(Color.green);
else
g.setColor(Color.black);
centreString(g, PHmsg, GL + 267, y); //display Peak Hold message
}
void drawFrequencyScale(Graphics g) { //DRAW FREQUENCY SCALE & kHz/pixel MESSAGE
int y = GB + 23; //base line of frequency scale text
g.setColor(bg1); //applet's general background colour
g.fillRect(GL - 20, y - fa, 240, fh); //clear the frequency scale
g.setColor(Color.black);
if(CF < MC[fs]) //make sure Centre Frequency CF is not less than
CF = MC[fs]; //minimum centre frequency for this band
FrqFld.setText(toMHz(CF, DP[fs])); //display corrected CF in entry field
int ff = CF - FS[fs]; //frequency of first annotation (in half-kHz)
w = SB[fs]; //pixels from start of frequency scale of first annotation
int dp = DP[fs]; //number of decimal places required for frequency annotations
for(int i = 0; i < NG[fs]; i++) { //for each frequency graduation
centreString(g, toMHz(ff, dp), GL + w, y); //frequency annotation
ff += FG[fs]; //increment to frequency of next annotation
w += FJ[fs]; //move to position of next annotation
}
}
void drawFreqRes(Graphics g) {
int x = GL - 75;
int y = GB - SCH + 10 + (fa >> 1); //base line of text
g.setColor(bg1); g.fillRect(x, y - fa, 74, fh); //clear 'kHz/pixel' area
g.setColor(Color.black);
g.drawString(FR + " kHz/pixel", x, y); //display kHz/pixel message
}
void drawGraph(Graphics g) { //re-draw the bandscope graticule and signals
if(sBT > 0 && sB > 0) { //if a specific band has been selected
g.setColor(bg1); //background colour of valid graph area
g.fillRect(GL, GT, GW, GH); //clear graph
g.setColor(bg2); //background colour of valid graph area
int kp = KP[fs]; //half-kHz/pixel for selected frequency span
int s = (CF - ((bandStarts[sbt][sb]) << 1)) / kp; //pixel distance of start of band from centre of graph
int e = (((bandEnds[sbt][sb]) << 1 ) - CF) / kp; //pixel distance of end of band from centre of graph
g.fillRect(GL + 100 - s, GT, s + e, GH); //put in coloured background for selected band
} else { //else no band is selected - ie manual mode
g.setColor(bg2); //background colour of valid graph area
g.fillRect(GL, GT, GW, GH); //put in coloured background for graph
}
g.setColor(bg3); //colour for on-graph division marks
int x = GL; //VERTICAL IN-GRAPH FREQUENCY GRADUATION LINES
for(int i = 0; i < 11; i++) { //for each frequency graduation
g.drawLine(x, GB, x, GT); //vertical division line in graph area
x += 20; //move to position of next frequency graduation line
}
int y = GB; //HORIZONTAL IN-GRAPH S-UNIT GRADUATION LINES
for(int i = 0; i < Smeter.length; i++) { //for each S-unit graduation
g.drawLine(GL, y, GR, y); //horizontal annotation mark
y -= 8; //accumulated number of vertical pixels
}
if(dataOK) {
g.setColor(Color.blue); //set trace colour to blue
if(lp == 3) { //provided data download completed
x = GL; //initial x-bias for week number
int j = 0;
for(int i = 0; i < GW; i++) { //display vertical bar on the chart
g.drawLine(x, GB, x, GB - Plots[i]);
x++; //increment the week number
}
}
} else {
g.setColor(Color.black); //set to print in black
centreString(g, dataMsg, GC, VC); //display unavailable data message
}
y = GB - 104; //base line of message text
g.setColor(bg1);
g.fillRect(GL - 75, y - fa, 155, fh); //clear 'Listening on' area
if(receiving) {
g.setColor(amber);
g.drawString("Listening on -->", GL - 75, y);
rightString(g, rFs, GL + 79, y); //display receiver frequency
g.drawLine(GL + hS, GB, GL + hS, GB - rS); //display vertical bar on the chart
}
}
void drawMouseFreq(Graphics g) { //DISPLAY MOUSE FREQUENCY AND S-METER
int y = GB - 104; //base line of mouse frequency
g.setColor(bg1); //set to panel colour
g.fillRect(GR - 80, y - fa, 80, fh); //clear mouse frequency area
g.setColor(Color.black);
rightString(g, mFs, GR - 1, y); //display mouse frequency
int x = GL - 5; y = GB - mS;
g.drawLine(x, y, x, GT); //upper black part of vertical (signal strength) axis
g.setColor(amber);
g.drawLine(x, GB, x, y); //lower amber part of vertical (signal strength) axis
}
public void start() { TH.start(); } //resume execution of Internet data transfer thread
public void run() { //MANAGE INTERNET DATA TRANSFERS ON A SEPARATE THREAD
while(TH.isAlive()) { //while this thread exists
switch(lp) { //THE 2 DOWNLOADING PHASES
case 1: fileConnect(); break; //connect to bandscope data file on server
case 2: fileLoad(); break; //manage the downloading of its content
}
if(lp != LP) { //if loading of data has finished
LP = lp; //latch the current load state
redraw = 2; repaint(); //display all data
}
try {
Thread.currentThread().sleep(250);//sleep to allow other things to take place
} catch (InterruptedException e) {} //catch interrupt from GUI or browser
checkAgeOfBandscopeData();
}
}
public void stop() { TH.stop(); } //freeze execution of thread while away from host HTML page
public void destroy() { //called after stop() before applet is removed from memory
if(TH != null) { //if the Internet data transfer thread object still exists
TH.stop(); //stop it running
TH = null; //and jettison its object into limbo
}
}
void fileConnect() { //CONNECT TO THE BANDSCOPE DATA FILE ON THE SERVER
fn = "bs"; //default name for the bandscope data file
if((sBT > 0) && (sB > 0)) { //if a specific band has been selected
String s = ""; //pad band number out to two digits
if(sB < 10) s = "0"; //with a leading zero if required
fn = bandType[sbt] + s + sB; //form the complete file name
}
fn += ".dat";
showStatus("Loading " + fn);
try { //enable local exception-catching
url = new URL(cb + fn); //form the url of the appropriate bandscope data file
URLConnection u = url.openConnection(); //open a connection to the remote file
LM = "" + new Date(lm = u.getLastModified());
lm /= 1000; //convert to whole seconds
L = u.getContentLength(); //length of the remote file (bytes)
l = 0; //number of bytes so far successfully downloaded
I = u.getInputStream(); //create an input stream object to access the file
B = new byte[L]; //create the gigantic buffer for the data
lp = 2; //advance to the index loading phase
} catch(Exception e) {
lp = 0; //set unrecoverable error status
E = "fileConnect() " + e; //note the exception and where it occurred
showStatus(E); //show exception message in browser's status bar
}
}
void fileLoad() { //DOWNLOAD THE BANDSCOPE DATA
int k; //current byte being read()
try { //if read() hasn't hit the current end of input stream
while(l < L && (k = I.read()) != -1) //and the entire file has not yet been downloaded
B[l++] = (byte)k; //add its new byte to the big byte array
if(l >= L) { //if the whole of the data has now been downloaded
I.close(); //close the URL Connection's input stream
computePlots(); //create graph plots from downloaded bandscope data
lp = 3; //indicate file loading completed successfully
showStatus("Data file: " + fn + " now loaded");
}
} catch(Exception e) {
lp = 0; //set error condition
E = "fileLoad() " + e + L + " " + l; //note the exception and where it occurred
showStatus(E); //show exception message in browser's status bar
}
}
void checkAgeOfBandscopeData() {
String Age; //changeable part of age message
long t = System.currentTimeMillis() / 1000; //current System time
if((t > T) && (lp == 3)) { //if more than a second has elapsed since last AgeMsg update
long age = t - lm; //elapsed time (in seconds) since data file last modified
if(age > 2592000)
Age = "Over a month";
else if(age > 604800)
Age = "Over a week";
else if(age > 86400)
Age = "Over a day";
else if (age > 3600)
Age = "Over an hour";
else if(age > 60)
Age = "" + age / 60 + " min";
else
Age = "" + age + " sec";
if(!Age.equals(oldAge)) { //if age message has changed since a second ago
AgeMsg = Age + " old"; //form the displayable age message
redraw = 6; repaint(); //display the age message
oldAge = Age; //save age string for comparison next time through
}
T = t; //note System time when AgeMsg last updated
}
}
// USER-EVENT HANDLERS
void selSpan() { //do when a new bandscope frequency span is selected
sBT = 0; BandTypes.select(sBT);
sB = 0; Bands.select(sB); //defaulting to manual selection ###1
selSpan2();
redraw = 3; repaint();
}
void selSpan2() { //do when a new bandscope frequency span is selected
fs = Span.getSelectedIndex();
if(fs == 6) //if 100 kHz span is selected
FR = "1/2"; //set straight to half kHz/pixel
else { //for all other spans
FR = "" + (KP[fs] >> 1); //get the integral resolution in kHz/pixel
if(fs == 4) //and if the 500 kHz span is selected
FR += "1/2"; //add the extra half sign for the 2.5 kHz/pixel
}
computePlots();
receiving = false;
}
// THIS METHOD IS SPECIFIC TO THE WAY THE AOR AR8600 SCANNER PROVIDES BANDSCOPE DATA
void computePlots() { //create graph plots from bandscope data
int D[] = { 0, 5, 1, 2, 4, 2, 4 }; //multipliers and divisors
int S[] = { 0, 249, 399, 449, 474, 0, 24 }; //starting points in bandscope samples array
int E[] = {1000, 500, 200, 100, 50, 100, 50 }; //extents of bandscope samples within array
int d = D[fs];
int s = S[fs]; //starting point for currently selected scope width
int e = s + E[fs]; //end point for currently selected scope width
dataOK = true;
if(L == 1000) {
if(fs == 0) { //if 10MHz span
int j = 0, h = 0, plot = 0; //5 samples are averaged to form 1 pixel plot
for(int i = s; i < e; i++) { //for each bandscope sample over the prescribed range
h += (int)B[i] - 2; //sum next 5 data points in h (-2 takes off noise)
if(++j == 5) {
Plots[plot++] = (h << 3) / 5; //store average in next graphical signal plot
j = 0;
h = 0;
}
}
} else if(fs == 1) { //if 5 MHz span
boolean MustStepBack = true; //2.5 samples are averaged (sort of) to form 1 pixel plot
int j = 0, h = 0, plot = 0;
for(int i = s; i < e; i++) { //for each bandscope sample over the prescribed range
h += (int)B[i] - 2; //in h (-2 takes off noise)
if(++j == 3) { //sum the next 3 bandscope samples
Plots[plot++] = (h << 3) / 3; //store average in next graphical signal plot
j = 0;
h = 0;
if(MustStepBack) //if done first 3 samples of current 5-sample group
i--; //back up by one bandscope sample
MustStepBack = !MustStepBack; //reverse the flag
}
}
} else if(fs < 5) { //if fs = 2 (2 MHz span), 3 (1MHz span) or 4 (500kHz span)
int plot = 0; //start at beginning of graph plots array
for(int i = s; i < e; i++) { //for each bandscope sample over the prescribed range
int x = (int)B[i] - 2; //get the sample and subtract the noise
for(int j = 0; j < d; j++) //store it in the next 1, 2 or 4 graph plots
Plots[plot++] = x << 3; //according to currently selected frequency span
}
} else {
dataMsg = "Loading 2kHz samples.";
for(int i = 0; i < GW; i++) Plots[i] = 0;
dataOK = false;
}
} else if(L == 100) { //assume first 100 bytes of B[] contain 2kHz samples
if(fs > 4) {
int plot = 0; //start at beginning of graph plots array
for(int i = s; i < e; i++) { //for each bandscope sample over the prescribed range
int x = (int)B[i] - 2; //get the sample and subtract the noise
for(int j = 0; j < d; j++) //store it in the next 1, 2 or 4 graph plots
Plots[plot++] = x << 3; //according to currently selected frequency span
}
} else {
dataMsg = "Loading 10kHz samples.";
for(int i = 0; i < GW; i++) Plots[i] = 0;
dataOK = false;
}
} else {
dataMsg = "L = " + L + ", default data file corrupt.";
for(int i = 0; i < GW; i++) Plots[i] = 0;
dataOK = false;
}
}
void pressFrqFld() { //do when C/R received from centre frequency field
sBT = 0; BandTypes.select(sBT);
sB = 0; Bands.select(sB); //defaulting to manual selection ###1
pressFrqFld2(); //do when C/R received from centre frequency field
if(CFvalid) {
dataMsg = "Please update bandscope data.";
for(int i = 0; i < GW; i++) Plots[i] = 0;
dataOK = false;
redraw = 4; repaint(); //so frequency scale will be re-drawn
}
}
void pressFrqFld2() { //do when C/R received from centre frequency field
receiving = false;
String s = FrqFld.getText(); //get the text present in the field
int x = s.indexOf(".");
if(x == -1) { //if text does not contain a decimal point
s += ".0"; //append a decimal point with a trailing zero
x = s.indexOf("."); //find index of inserted point
FrqFld.setText(s); //display amended text in centre-frequency field
}
String k = s.substring(x, s.length()); //isolate the decimal point + digits
s = s.substring(0, x); //isolate the integral MHz
int l = k.length();
if(l > 1) { //if more than just the decimal point on its own
if(l > 3) l = 3; //limit to 2 decimal places of MHz
k = k.substring(1, l); //isolate the decimal digits
while(k.length() < 2) k += "0"; //pad out with zeros to 3 digits
} else k = "00"; //no decimal places, so add 3 zeros
s += k; //s now contains integral 10 kHz
CFvalid = false;
try { //try to
x = Integer.parseInt(s + "0"); //parse string as an integer
if(x < 2040000 && x > 100) { //if frequency within range of receiver scanner
CF = x << 1; //set new valid centre frequency (in integral half-kHz)
CFvalid = true; //to trigger appropriate repaint mode
} else
showStatus("Frequency out of range!"); //show error message in browser's status bar.
} catch(NumberFormatException e) { //if what was entered cannot be parsed into an integer
showStatus("Invalid centre frequency!"); //show error message in browser's status bar.
}
}
void selBand() { //action to be taken when new band is selected
sB = Bands.getSelectedIndex(); //index number of band that has been selected
sb = sB - 1; //corresponding array index
if(sB > 0 && sBT > 0) {
fs = bandSpans[sbt][sb]; //get frequency span for this band
Span.select(fs); //select the appropriate span choice
CF = (bandCentres[sbt][sb]) << 1; //get appropriate centre frequency for this band
FrqFld.setText(toMHz(CF, 4)); //set it in the CF entry field
selSpan2();
pressFrqFld2(); //enter the centre frequency
LP = 1; lp = 1; //trigger loading of the appropriate data file
redraw = 3; repaint();
}
}
void selBandType() { //action to be taken when new bandscope span is selected
sBT = BandTypes.getSelectedIndex(); //index number of band type that has been selected
sbt = sBT - 1; //corresponding array index
Bands.removeAll(); //remove band names of previously-selected band type
if(sBT > 0)
for(int i = 0; i < bandNames[sbt].length; i++)
Bands.add(bandNames[sbt][i]); //load the appropriate bands into the band choice
else Bands.add("None"); //set up a null band
receiving = false; selBand();
}
void scamper(int x, int y) { //MOUSE MOTION EVENT HANDLER
x -= GL; //frequency co-ordinate within graph area
y = GB - y; //signal co-ordinate within graph area
if(x >= 0 && x <= GW && y >= 0 && y <= GH) { //if mouse within graph area
onGraph = true; //state that mouse is currently within graph area
mF = CF + (x - 100) * KP[fs]; //true mouse frequency in integral half-kHz
mFs = toMHz(mF, 4) + "MHz"; //to string 4 decimal places of MHz
mS = Plots[x]; //Signal strength at mouse
redraw = 1; repaint(); //set for a mouse-only repaint
} else if(onGraph) { //if moved off graph area but not cleared frequency field
mF = 0;
mFs = "--------------MHz";
mS = 0;
redraw = 1; repaint(); //set for a mouse-only repaint
onGraph = false; //state that mouse is now outside graph area
} //so no more repaints of frequency display necessary
}
void click(int x, int y) { //MOUSE MOTION EVENT HANDLER
x -= GL; //mouse horizontal co-ordinate within graph area
y = GB - y; //mouse vertical co-ordinate within graph area
if(x >= 0 && x <= GW && y >= 0 && y <= GH) { //if mouse within graph area
rF = CF + (x - 100) * KP[fs]; //receiver frequency in integral half-kHz
rFs = toMHz(rF, 4) + "MHz"; //to string with 4 decimal places of MHz
rS = Plots[x]; //Signal strength at mouse (in pixels)
hS = x; //graph x-co-ordinate of highlighted signal
receiving = true; redraw = 2; repaint();
showStatus("RF" + rF * 500); //send 'tune to' command to receiver
}
}
void pressEntBut() { pressFrqFld(); } //action to be taken when Enter button is pressed
void pressUpBut() { //action to be taken when Up button is pressed
rF++; adjustRXFrequency();
}
void pressDnBut() { //action to be taken when Down button is pressed
rF--; adjustRXFrequency();
}
void adjustRXFrequency() {
rFs = toMHz(rF, 4) + "MHz"; //receiver frequency to string with 4 decimal places of MHz
hS = 100 + (rF - CF) / KP[fs]; //horizontal pixel position of receiver frequency on graph
if(hS < 0) hS = 0;
if(hS > 199) hS = 199;
rS = Plots[hS]; //Signal strength at mouse (in pixels)
receiving = true; redraw = 2; repaint();
showStatus("RF" + rF * 500); //send 'tune to' command to receiver
}
void pressPulBut() { //action to be taken when Pull button is pressed
if(false) { //replace with server ack signal
LP = 1; lp = 1; //trigger a reload of the data
} else {
showStatus("AR8600 port server currently unavailable.");
getToolkit().beep(); //beep
}
}
void pressHldBut() { //action to be taken when Hold button is pressed
if(peakHold) {
peakHold = false;
PHmsg = "off";
showStatus("PH0");
} else {
peakHold = true;
PHmsg = "ON";
showStatus("PH1");
}
PHmsg = "Peak Hold " + PHmsg;
redraw = 5; repaint();
}
void selUpdate() { //action to be taken when update choice made
showStatus("new update frequency selected");
}
//STRING FORMATTING METHODS
void centreString(Graphics g, String s, int x, int y) { //DRAW A STRING CENTRED AT A HORIZONTAL POSITION x
FontMetrics fm = g.getFontMetrics(); //dimensions of characters for current font
int b = (fm.stringWidth(s)) >> 1; //half the width of the string to be displayed
g.drawString(s, x - b, y); //display the string
}
void rightString(Graphics g, String s, int x, int y) { //DRAW A RIGHT-JUSTIFIED STRING
FontMetrics fm = g.getFontMetrics(); //dimensions of characters for current font
g.drawString(s, x - fm.stringWidth(s), y); //display the string
}
String toMHz( //INTEGRAL HALF-kHz to DECIMAL MHz STRING
int x, //frequency in integral half-kHz
int d //number of decimal places of MHz required
) {
int D[] = { 5, 3, 2, 1, 0 }; //to convert decimal places to string chop
String s;
if((x & 0x1) > 0) //if there is an odd half-kHz
s = "5"; //set 4th decimal place of MHz to '5'
else //if it is a whole number of kHz
s = "0"; //set 4th decimal place of MHz to '0'
s = "" + (x >> 1) + s; //string of rF in form GL.XXXX MHz
int l = s.length(); //insert the decimal point
x = l - 4; //to give MHz to 4 decimal places
s = s.substring(0, x) + "." + s.substring(x, l); //insert the decimal point
return s.substring(0, s.length() - D[d]); //chop to required number of decimal places
}
}
/* The following class creates instances of various event listeners.
They are used by this applet to create and service events for
each of the control buttons and choices, and also mouse movements
and clicks. The underlying system calls the actionPerformed()
method below every time a button is pushed. The method is invoked
upon the instance of one of the following classes corresponding to
that particular button, choice or mouse event. The value of the
variable id at the time is therefore that which corresponds to
the button, choice or mouse event concerned. This causes the
appropriate case statement to be executed, thus passing control to
the appropriate handling method in the above applet. */
class bl implements ActionListener {
static final int ENTBUT = 0; //'Enter button pressed' event
static final int PULBUT = 1; //'Pull button pressed' event
static final int FRQFLD = 2; //'C/R from centre frequency entry field' event
static final int UPBUT = 3; //'Up button pressed' event
static final int DNBUT = 4; //'Down button pressed' event
static final int HLDBUT = 5; //'Hold button pressed' event
int id; //one of the above
bs ap; //the application that called: always the above applet!
public bl(int id, bs ap) { //constructor for a new command
this.id = id;
this.ap = ap;
}
public void actionPerformed(ActionEvent e) { //one of the buttons has been clicked
switch(id) { //id of this instance of ActionEvent
case ENTBUT: ap.pressEntBut(); break; //it was the 'Enter' button
case PULBUT: ap.pressPulBut(); break; //it was the 'Pull' button
case FRQFLD: ap.pressFrqFld(); break; //it was a 'C/R' from centre frequency entry field
case UPBUT: ap.pressUpBut(); break; //it was the 'Up' button
case DNBUT: ap.pressDnBut(); break; //it was the 'Down' button
case HLDBUT: ap.pressHldBut(); break; //it was the 'Hold' button
}
}
}
class chl implements ItemListener { //LISTENS FOR EVENTS FROM THE CHOICE MENU SELECTORS
static final int SPAN = 0; //an event from the 'bandscope span selector' choice menu
static final int BANDTYPES = 1; //an event from the 'band type selector' choice menu
static final int BANDS = 2; //an event from the 'band selector' choice menu
static final int UPDATE = 3; //an event from the 'update frequency selector' choice menu
int id; //one of the above events
bs ap; //the application that called: always the above applet!
public chl(int id, bs ap) { //constructor for a new Choice selection event
this.id = id; //set the id number pertaining to this instance of 'cl'
this.ap = ap; //set the reference to the instance of the 'bs' applet
} //from which it came. (there will only be one instance anyway).
public void itemStateChanged(ItemEvent e) { //a Choice selection event has occurred from checkbox 'id'
switch(id) {
case SPAN: ap.selSpan(); break; //Execute the method in the above applet which deals with this choice
case BANDTYPES: ap.selBandType(); break; //Execute the method in the above applet which deals with this choice
case BANDS: ap.selBand(); break; //Execute the method in the above applet which deals with this choice
case UPDATE: ap.selUpdate(); break; //Execute the method in the above applet which deals with this choice
}
}
}
class mml implements MouseMotionListener { //LISTENS FOR MOUSE MOVEMENTS
bs ap; //the application that called: always the above applet!
public mml(bs ap) { //constructor for a new Choice selection event
this.ap = ap; //set the reference to the instance of the 'bs' applet
} //from which it came. (there will only be one instance anyway).
public void mouseMoved(MouseEvent e) { //The mouse has moved
int x = e.getX(); //the mouse's GL co-ordinate
int y = e.getY(); //the mouse's GB co-ordinate
ap.scamper(x, y); //scamper() is in my applet.
} //It does the donkey work of processing mouse movements
public void mouseDragged(MouseEvent e) { //a Choice selection event has occurred from checkbox 'id'
} //I don't need to use this at the moment
}
class ml implements MouseListener { //LISTENS FOR MOUSE CLICKS
bs ap; //the application that called: always the above applet!
public ml(bs ap) { //constructor for a new cat's jaw
this.ap = ap; //set the reference to the instance of the 'bs' applet
} //from which it came. (there will only be one instance anyway).
public void mouseClicked(MouseEvent e) { //Invoked when the mouse has been clicked on a component.
int x = e.getX();
int y = e.getY();
ap.click(x, y); //handles the donkey work associated with a mouse click.
}
public void mousePressed(MouseEvent e) {} //Invoked when a mouse button has been pressed on a component.
public void mouseReleased(MouseEvent e) {} //Invoked when a mouse button has been released on a component.
public void mouseEntered(MouseEvent e) {} //Invoked when the mouse enters a component.
public void mouseExited(MouseEvent e) {} //Invoked when the mouse exits a component.
}
class ar8600 { //ENCAPSULATES AR8600 COMMUNICATIONS RECEIVER COMMANDS
/* AT What is the attenuator status?
AT1 Engage attanuator
AT0 Disengage attenuator
AM Switch to bandscope mode
CF Set bandscope centre frequency in hertz
DC Set bandscope data centre frequency
DS Acquire current bandscope data
MF Bandscope: set marker frequency
PH Bandscope peak hold
SW Bandscope frequency span */
}
/* Robert J Morton, the author of this program,
is a poor but Right Honourable Member of the
Ancient and Noble Order of the Long-term Unemployed.
Offers of paid work or donations please to:
robmorton@clara.net
*/