/**
* HF Receiver Controller Applet 1.0.0
* @author Robert J Morton <robmorton@clara.net>
* @version 25 November 2001
* @copyright Robert J Morton (all rights reserved) */
/* This applet conforms to API 1.1
It does the following:
INITIALISATION SEQUENCE
1. Loads a file full of the names of lots of HF broadcasting services and stations
from a file located on the server, and places them into a stations Choice list.
2. Selects the first one in the list and loads the list of frequencies on which
this station broadcasts into a frequency Choice list.
3. Sets up the standard set of squelch levels S0 to S9+ in a squelch Choice list
and selects S5 as the default.
4. Sends the default frequency and squelch level back to the server to be POSTed
to a receiver whose command interface is connected to the server PC's AUX port.
5. Awaits user input.
USER-INITIATED ACTIONS
1. User can select another station or broadcasting service from the list.
The new station's list of frequencies is then loaded from the server
into the frequencies Choice list. New station's first frequency is then
sent automatically to the receiver as a re-tuning command.
2. User can select a frequency from the frequency Choice list. This frequency
is then sent automatically to the receiver as a re-tuning command.
3. User can set the receiver scanning through the current station's frequencies
at the rate of one per second. It stops scanning when the receiver detects
a signal of a strength equal to or greater than the currently selected
squelch level. The user can press the scan Start button again to resume
scanning round the frequencies list.
4. User can step up and down the frequency list manually by pressing the Higher
and Lower buttons. Each new frequency is sent as a tuning command to the
receiver.
5. User can enter a word or phrase and search for it within all station names
in the list of stations or braodcasting services. If found, the the first
station's name is selected and its broadcasting frequencies loaded. Pressing
the Find/Next button again proceeds with the search onwards down the list
of stations.
Appropriate code has to be inserted into this applet where indicated to construct
and POST receiver command messages of the type required for your receiver and its
control interface software. Your server needs to know where to POST these messages.
*/
import java.awt.*;
import java.io.*;
import java.applet.*;
import java.awt.event.*; //for the new-fangled 1.1 event handling
import java.net.*; //for downloading data from the remote server
public class hfbrx extends Applet implements Runnable {
AppletContext ac; //get details of HTML document this applet is running in
String cb; //code base URL - where this applet's class file came from
Choice stations, frequencies, squelch; //stations and frequencies choice lists
Label Stn, Frq, Squ, ScanLab, NextLab, PrevLab, FindLab;
Label TextLab1, TextLab2, TextLab3, TextLab4, TextLab5;
Button ScanBut; //button to start/stop frequency scanning for current station
Button NextBut, PrevBut, FindBut; //button to go to the next frequency in the list
BufferedReader r; //for reading in station and frequency lists from files
long si = 1000; //frequency scan interval
long T; //time at which to step to the next frequency
Thread TH; //declare a thread reference variable
int MaxFreq = 0; //max lisat number for frequencies this station transmits on
int FreqNum = 0; //list number of the current listening frequency
boolean ScanFlag = false; //indicates whether or not automatic scanning in progress
int lp = 0, LP = 1; //indicates current and previous download phases
String loadFile = "stations.txt"; //name of the station names file
int loadType = 0; //0 = Station names file, 1 = Frequencies file
int L = 0; //length of the remote item being loaded
int l = 0; //number of bytes of the above successfully downloaded
InputStream I; //input stream for downloading index or current HTML file
byte B[]; //gigantic byte array to hold the downloaded index data
boolean StationNamesLoaded = false; //station names not yet loaded
boolean FrequenciesLoaded = false; //selected station's frequencies not yet loaded
boolean PanelNotCompleted = true; //to stop panel flicker as items are added to station names
boolean NewFreq = false; //new frequency must be displayed when true
boolean NewStn = false; //new station must be displayed when true
boolean NewSquelch = false; //new squelch level must be displayed when true
String E; //for Exception during downloading + method where it occurred
TextField FindStn; //keyword entry field on the applet panel
String SearchString = ""; //search string for finding a station name
int SearchIndex = 0; //list number of station name we have searched up to
public void init() {
ac = getAppletContext(); //get details of HTML document this applet is running in
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 (cira Nov 1999)
}
setLayout(null); //to allow the control panels to be laid out manually
Stn = new Label("Station or Broadcasting Service");
add(Stn); Stn.setBounds(100, 6, 200, 15);
stations = new Choice();
add(stations);
stations.setBounds(100, 26, 200, 15);
chlisten ws = new chlisten(chlisten.STATION, this);
stations.addItemListener(ws);
Frq = new Label("Freq (kHz)");
add(Frq); Frq.setBounds(315, 6, 60, 15);
frequencies = new Choice();
add(frequencies);
frequencies.setBounds(315, 26, 60, 15);
chlisten bk = new chlisten(chlisten.FREQUENCY, this);
frequencies.addItemListener(bk);
Squ = new Label("Squelch");
add(Squ);
Squ.setBounds(390, 6, 50, 15);
squelch = new Choice();
add(squelch);
squelch.setBounds(390, 26, 50, 15);
chlisten sq = new chlisten(chlisten.SQUELCH, this);
squelch.addItemListener(sq);
squelch.addItem("S0"); squelch.addItem("S1");
squelch.addItem("S2"); squelch.addItem("S3");
squelch.addItem("S4"); squelch.addItem("S5");
squelch.addItem("S6"); squelch.addItem("S7");
squelch.addItem("S8"); squelch.addItem("S9");
squelch.addItem("S9+");
squelch.select(5); NewSquelch = true; //set default squench level to 'S5' signal strength
FindLab = new Label("Station Search"); //set up the label for the station search field
add(FindLab);
FindLab.setBounds(10, 6, 80, 15);
FindStn = new TextField();
add(FindStn);
FindStn.setBounds(10, 26, 80, 20);
btlisten sf = new btlisten(btlisten.FINDSTN, this);
FindStn.addActionListener(sf); //listen for c/r from text field
lp = 1; //start the station names loading process
}
public void paint(Graphics g) {
showStn(g); //re-display station name when an overlaid window removed
showFreq(g); //re-display frequency when an overlaid window removed
showSquelch(g); //re-display the squelch setting
}
public void update(Graphics g) {
if(PanelNotCompleted) {
ScanLab = new Label("Automatic scanning of this station's frequency list.");
add(ScanLab);
ScanLab.setBounds(155, 60, 290, 20);
ScanBut = new Button("Start");
add(ScanBut);
ScanBut.setBounds(100, 60, 50, 20);
btlisten sb = new btlisten(btlisten.SCANBUT, this);
ScanBut.addActionListener(sb);
NextLab = new Label("Listen on next higher frequency in this station's list.");
add(NextLab);
NextLab.setBounds(155, 85, 290, 20);
NextBut = new Button("Higher");
add(NextBut);
NextBut.setBounds(100, 85, 50, 20);
btlisten nb = new btlisten(btlisten.NEXTBUT, this);
NextBut.addActionListener(nb);
PrevLab = new Label("Listen on next lower frequency in this station's list.");
add(PrevLab);
PrevLab.setBounds(155, 110, 290, 20);
PrevBut = new Button("Lower");
add(PrevBut);
PrevBut.setBounds(100, 110, 50, 20);
btlisten pb = new btlisten(btlisten.PREVBUT, this);
PrevBut.addActionListener(pb);
FindBut = new Button("Find/Next");
add(FindBut);
FindBut.setBounds(10, 135, 80, 20);
btlisten fb = new btlisten(btlisten.FINDBUT, this);
FindBut.addActionListener(fb);
TextLab1 = new Label("Find station(s)"); add(TextLab1); TextLab1.setBounds(10, 54, 80, 15);
TextLab2 = new Label("whose names"); add(TextLab2); TextLab2.setBounds(10, 68, 80, 15);
TextLab3 = new Label("contain the"); add(TextLab3); TextLab3.setBounds(10, 82, 80, 15);
TextLab4 = new Label("entered word"); add(TextLab4); TextLab4.setBounds(10, 96, 80, 15);
TextLab5 = new Label("or phrase."); add(TextLab5); TextLab5.setBounds(10,110, 80, 15);
PanelNotCompleted = false;
}
if(NewFreq) { //if a new frequency has just been selected
showFreq(g); //re-display the frequency field
NewFreq = false; //note that this has been done
}
if(NewStn) { //if a new station has just been selected
showStn(g); //re-display the station name
NewStn = false; //note that this has been done
}
if(NewSquelch) { //if a new squelch level has just been selected
showSquelch(g); //re-display the squelch setting
NewSquelch = false; //note that this has been done
}
}
void showStn(Graphics g) {
if(StationNamesLoaded) { //provided staion names download is complete
g.setColor(getBackground());
g.fillRect(100, 135, 200, 15);
g.setColor(Color.black);
g.drawString("Station: " + stations.getSelectedItem(), 100, 150);
}
}
void showFreq(Graphics g) {
if(FrequenciesLoaded) { //provided frequencies download is complete
g.setColor(getBackground());
g.fillRect(315, 135, 60, 15);
g.setColor(Color.black);
g.drawString(frequencies.getSelectedItem() + " kHz", 315, 150);
}
}
void showSquelch(Graphics g) {
g.setColor(getBackground());
g.fillRect(390, 135, 50, 15);
g.setColor(Color.black);
g.drawString(squelch.getSelectedItem(), 390, 150);
}
void selectStation() { //broadcast selected event handler
if(StationNamesLoaded) {
NewStn = true; //trigger display of new station name
NewFreq = true; //new frequency must be displayed
frequencies.removeAll(); //clear the frequencies list
int x = stations.getSelectedIndex(); //number of the selected station within the choice list
loadFile = "" + x; //form string version of station number
if(x < 10) //if it is less than 10
loadFile = "00" + loadFile; //pad it out with 2 leading zeros
else if(x < 100) //else if it is less than 100
loadFile = "0" + loadFile; //pad it with just 1 leading zero
loadFile = "freqs" + loadFile + ".txt"; //form the name of the appropriate frequencies file
loadType = 1; //state type of file being loaded
FrequenciesLoaded = false; //selected station's frequencies not yet loaded
lp = 1; //start the loading process
}
}
void selectFrequency() { //frequency selection event handler
FreqNum = frequencies.getSelectedIndex(); //note the list number of the frequency just selected
NewFreq = true; //trigger display of new selected frequency
repaint(); //re-display new frequency
/* Here is where you construct and send an HTTP 'POST' message and send it
back to your HTTPD server, telling it to send a 'tune to this frequency'
command to your physical receiver. Your HTTPD server will have to know
where to POST this type of message on to. I wrote my web server in Java,
so I simply added a class file to construct and send receiver commands
via my PC's AUX port. */
}
void selectSquelch() { //SQUELCH SELECTION EVENT HANDLER
String SquelchLevel = squelch.getSelectedItem(); //get the selected squelch level S0, S1, S2, ...
NewSquelch = true; //trigger display of new squelch level
repaint();
/* Here is where you construct and send an HTTP 'POST' message and send it
back to your HTTPD server, telling it to send a 'set this squelch level'
command to your physical receiver. Your HTTPD server will have to know
where to POST this type of message on to. I wrote my web server in Java,
so I simply added a class file to construct and send receiver commands
via my PC's AUX port. */
}
void pressScanBut() { //WHEN THE SCAN BUTTON IS PRESSED
if(ScanFlag) { //If is scanning in progress
ScanFlag = false; //kill scanning and re-display button labels
FindBut.setLabel("Find/Next");
NextBut.setLabel("Higher");
PrevBut.setLabel("Lower");
ScanBut.setLabel("Start");
} else { //otherwise
ScanFlag = true; //start scanning and kill button labels
FindBut.setLabel("");
NextBut.setLabel("");
PrevBut.setLabel("");
ScanBut.setLabel("Stop");
}
}
void pressNextBut() {
if(!ScanFlag) nextFreq(); //do nothing if automatic scanning is on
}
void pressPrevBut() {
if(!ScanFlag) { //do nothing if automatic scanning is on
if(--FreqNum < 0) FreqNum = MaxFreq; //decrement frequency number, loop back to max if overshot
frequencies.select(FreqNum); //select the new listening frequency
NewFreq = true; //new frequency must be displayed
repaint(); //sets up a call to update()
}
}
void nextFreq() {
if(++FreqNum > MaxFreq) FreqNum = 0; //increment frequency number, loop back to zero if overshot
frequencies.select(FreqNum); //select the new listening frequency
NewFreq = true; //new frequency must be displayed
repaint(); //sets up a call to update()
}
void findStation() { //x is the item number at which to start/resume search
if(!ScanFlag) { //get search string currently in the text field
String s = FindStn.getText().trim().toLowerCase();
if(!SearchString.equals(s)) { //if different from last time's search string
SearchString = s; //update search string
SearchIndex = 0; //reset search index to start of list
}
int I = stations.getItemCount(); //get the number of station names in the list
for(int i = SearchIndex; i < I; i++) { //for each station name in the list
s = stations.getItem(i).toLowerCase(); //get the next station name
if(s.indexOf(SearchString) != -1) { //if search string found in this station name
stations.select(i); //select the station name
SearchIndex = i + 1; //mark where we have searched up to
showStatus(""); //clear possible 'not found' message from last time
selectStation(); //see above
return;
}
}
showStatus("Search string not found in any station name.");
}
}
void fileConnect() { //CONNECT TO THE APPROPRIATE DATA FILE ON THE SERVER
try { //set to capture any exceptions locally
URL url = new URL(cb + loadFile); //form the url of the station data file
URLConnection u = url.openConnection(); //open a connection to the remote file
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) { //if any exception at all occurs
lp = 0; //set unrecoverable error status
E = "fileConnect() " + e; //note the exception and where it occurred
showStatus(E); //display error message on browser's status line
}
}
void fileLoad() { //DOWNLOAD THE APPROPRIATE DATA FILE
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 array big byte array
if(l >= L) { //if the whole of the index has now been downloaded
I.close(); //close the URL Connection's input stream
lp = 3; //indicate file loading completed successfully
unravel();
}
} catch(Exception e) {
lp = 0; //set error condition
E = "fileLoad() " + e + L + " " + l; //note the exception and where it occurred
showStatus(E); //display error message on browser's status line
}
}
void unravel() {
try {
r = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(B)));
String s;
while((s = r.readLine()) != null) { //read in the next line of text
if(loadType == 0)
stations.addItem(s.trim()); //add station name to stations choice list
else
frequencies.addItem(s.trim()); //add frequency to current station's frequencies list
}
r.close(); //close the byte array reader
} catch(Exception e) {
}
B = null; //garbage the byte array to release memory
if(loadType == 0) {
StationNamesLoaded = true;
selectStation(); //pre-select the default station name
} else {
FrequenciesLoaded = true;
MaxFreq = frequencies.getItemCount() - 1; //get total number of frequencies for this station
FreqNum = 0; //reset to first frequency in the list
repaint();
}
}
public void run() { //RUN THE AUXILIARY THREAD
while(TH.isAlive()) { //while this thread exists
if(ScanFlag && FrequenciesLoaded) nextFreq();
long s = T - System.currentTimeMillis(); //get time remaining in this scan interval
if (s < 5) s = 5; //in case host PC is too slow
try {
Thread.currentThread().sleep(s); //sleep for remaining time
} catch (InterruptedException e) { //allow browser events to break the thread
} //happens if you return to applet's HTML page
T = System.currentTimeMillis() + si; //set finish time of next time frame
switch(lp) { //THE 2 DOWNLOADING PHASES
case 1: fileConnect(); break; //connect to index resource on server
case 2: fileLoad(); break; //manage the downloading of its content
}
if(lp != LP) { //if loading of station data has finished
LP = lp; //latch the current load state
}
}
}
public void start() { TH = new Thread(this); TH.start(); } //Start program thread
public void stop() { TH.stop(); } //Stop program thread
}
class chlisten implements ItemListener { //LISTENS FOR EVENTS FROM THE CHOICE MENU SELECTORS
static final int STATION = 0; //an event from the 'Stations' Choice Menu
static final int FREQUENCY = 1; //an event from the 'Frequency' Choice Menu
static final int SQUELCH = 2; //an event from the 'Squelch' Choice Menu
int id; //one of the above events
hfbrx ap; //the application that called: always the above applet!
public chlisten(int id, hfbrx ap) { //constructor for a new Choice selection event
this.id = id; //set the id number pertaining to this instance of 'chlisten'
this.ap = ap; //set the reference to the instance of the 'hfbrx' 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 STATION: ap.selectStation(); break; //Execute the method in the above applet which deals with the
case FREQUENCY: ap.selectFrequency(); break; //Choice selection that invoked this instance of 'chlisten'
case SQUELCH: ap.selectSquelch(); break;
}
}
}
/* The following class creates instances of ActionListener. It is used by this applet to create
and service an action event for each of the control buttons. The underlying system calls the
actionPerformed() method below every time a button is pushed. The method is invoked upon the
instance of the following class corresponding to that particular button. The value of the
variable id at the time is therefore that which corresponds to the button concerned. This
causes the appropriate case statement to be executed, thus passing control to the appropriate
handling method above. */
class btlisten implements ActionListener {
static final int SCANBUT = 0; //'scan button pressed' event
static final int NEXTBUT = 1; //'next button pressed' event
static final int PREVBUT = 2; //'prev button pressed' event
static final int FINDSTN = 3; //'station search text field' event
static final int FINDBUT = 4; //'station search button pressed' event
int id; //one of the above
hfbrx ap; //the application that called: always the above applet!
public btlisten(int id, hfbrx 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 SCANBUT: ap.pressScanBut(); break; //it was the 'scan' button
case NEXTBUT: ap.pressNextBut(); break; //it was the 'next' button
case PREVBUT: ap.pressPrevBut(); break; //it was the 'prev' button
case FINDSTN: ap.findStation(); break; //it was the 'find' fiels C/R
case FINDBUT: ap.findStation(); break; //it was the 'find' button
}
}
}
/* Robert J Morton, the author of this program,
is a poor but Right Honourable Fellow of the
Ancient and Noble Order of the Long-term Unemployed.
Offers of work please to: robmorton@clara.net */