/* * Program: Communications Command & Control * Programmer: Robert John Morton UK-YE572246C * Date: Fri 17 Oct 2020 - Belo Horizonte-MG This source code is exclusively the intellectual property of the programmer. The listing is copiously commented in order to serve as a self-teaching aid for programmers new to the 'C' programming language. Different techniques of achieving the same functionality are included deliberately for illustration. The code has not been tightened too much in order to leave the program easily adaptable to facilitate future modifications and additions. Project: Phase 1: SCOPE, SCAN, I/O tabs Started: Thu 11 Sep 2020 Finished: Fri 17 Oct 2020 [37 days] Project: Phase 2: HAMS, XFER, LINK, NETS tabs Started: Mon 18 Apr 2022 Finished: Tue 24 May 2022 [37 days] Project: Phase 3: Moon tracking for Moonbounce Communications Started: Mon 05 Sep 2022 Finished: Fri 23 Sep 2022 [19 days] Project: Phase 4: Moon added tracking sub-tabs BAND and CHAN Started: Wed 04 Jan 2023 Finished: Mon 23 Jan 2023 [19 days] 7358 lines From the directory in which 'com.c' resides: location of the X-windows X11 graphics library | name of the executable output file | | to compile: gcc com.c -L/usr/X11R6/lib -o com -lX11 -lm | | link to X-windows X11 graphics objects library | link to mathematics objects library for sin, cos etc. to run: ./com [runs program as a self-contained unit] ./com -o [runs program sending tranceivers commands to 'stdout'] ./com -io [also accepting input from transceivers via 'stdin'] The following data files must be present within the same directory as the 'com' executable: bands.dat broadcasters.dat fracnets.dat nodes.dat nodes.idx nodes.txt scope.dat stns.dat stns.idx hams.txt hams.idx hams.dat Discourse on this program at: https://robmorton.website/radio/com.html */ #include // contains the X11 grapical drawing functions #include // input output to/from the console and keyboard #include // standard functions for the 'C' language #include // for initialising strings as character arrays #include // contains the function usleep() #include // sin() cos() asin() atan() atan2() etc #include #define WHITE 0xFFFFFF // colour white for 'selected item' lettering #define GREY 0x999999 // colour grey for 'unselected item' lettering #define BLEY 0xBBCCDD // colour blue-grey for 'instructions' lettering #define DARK 0x333333 // colour for menu item background #define BROWN 0x556633 // colour for menu item background #define BLACK 0x000000 // colour black for clearing graph areas #define YELLOW 0xFFFF00 // colour yellow for menu titles #define YELL 0xBBBB00 // duller yellow for scroll bars #define GREEN 0x00FF00 // colour green for graphical traces and messages #define GRN 0x00AA00 // dark green for scope trace #define BLEEN 0x88AA88 // greeny grey #define RED 0xFF5555 // colour red for graphical traces and messages #define pi 3.14159265358979323846264338327950288 // circular constant π #define TwoPi 6.28318530717958647692528676655900576 // 2π [360 degrees] #define HalfPi 1.57079632679489661923132169163975144 // π/2 [90 degs] #define MPR 6371000.0 // metres per mean Great Circle radian [Erad] #define DPR 57.2957795141996 // number of degrees per radian #define RPD 0.017453293 // number of radians per degree #define SPR 206264.806247 // seconds of arc per radian 648000/π #define RPS 0.000004848137 // radians per second of arc Display *XD; // pointer to an X11 display device Window XW; // reference for the window created for this program GC XG; // X11 graphics context within which to write and draw FILE // FILE HANDLES *ft, // for 'bands.dat' *bf, // for broadcaster names file 'broadcasters.dat' *sf, // for the 'scope.dat' file *fi, // for the 'stns.idx' file *ff; // for the 'stns.dat' file struct bands { // to contain information for a frequency band int gs, // gratucule start frequency os, // official start frequency ow; // official extent of band char N[22]; // name [up to 20 characters only allowed] } bn[30]; // array of pointers up to 30 instances of this structure int bg[360][26], /* bg[x][0] contains the number of nodes within each degree of bearing 'x' from the base node. bg[x][1 to 25] contain the index numbers [within the nodes structure] of each of the nodes themselves. */ // VARIABLES PERTAINING TO TAB 0 [SCOPE] a0 = 0, // Tab 0 control button states b0 = 0, // frequency indicated by mouse pointer c0 = 0, // frequency off-set from low end of band d0 = 0, // Band Type defaults to Broadcast e0 = 0, // Band Name within Type defaults to zero f0[5] = {29,20,14,21,0}, // number of bands of each of the 5 Band Types g0 = 0, // Listening On frequency h0[300], // current scope trace amplitudes // VARIABLES PERTAINING TO TAB 1 [SCAN] a1 = 0, // Tab 1's iteration counter b1 = 0, // Tab 1 control button states c1 = 14, // currently selected broadcaster starting at zero d1 = 0, // Total number of broadcasters on file e1 = 0, // number of top displayed line in list of broadcasters f1 = 0, // current frequency of selected broadcaster g1 = 0, // list number of current frequency for selected broadcaster h1 = 0, // start record of current broadcaster's freqs in stns.dat i1 = 0, // number of frequencies the current broadcaster transmits on k1 = 0, // number of first visible column in list of frequencies // VARIABLES PERTAINING TO TABS 2 [HAMS] a2 = 4, // Tab 2 control button states c2 = 0, // number of hams loaded [number of hams on-file] d2 = 0, // listening frequency of currently selected ham e2 = 0, // '1' indicates that nodes have been loaded f2 = 0, // vertical bias for hams list display in Tab 2 // VARIABLES PERTAINING TO Tab 3 [I/O] and Tab 7 [MOON] Geoid = 0, // List Number of the selected geoidic ellipsoid Gauto = 0, // 1:geoid selection on automatic; 0:manual a3 = 0, // Tab 3 control button states b3 = 0, // Tab 3 input line number c3 = 0, // Tab 3 output line number // VARIABLES PERTAINING TO TAB 4 [XFER] a4 = 4, // Tab 4 control button states [temp default to 'stdout' on] b4 = 0, // percentage of file transferred c4 = 0, // transfer status Bit0 1=up 0=down load d4 = 0, // data transfer arrow direction [right or left] e4 = 0, // Tab 4's update iteration counter [used in T5XFR()] f4 = 0, // record number in 'nodes.idx' of selected destination node g4 = 0, // record number in 'nodes.dat' of selected destination node h4 = 0, // number of lines not visible before first line displayed on list i4 = 0, // fracnet node number for the currently selected destination j4 = -1, // transfer sequencer status [-1=inactive; phases 0 to 6] k4 = 0, // sequencer timer /* VARIABLES PERTAINING TO TAB 5 LINK. Each row of this array is respect- ively the corresponding variables in the T5LP() function. */ a5[] = { // Sine Table giving sin(a) every half degree 0, 87, 175, 262, 349, 436, 523, 610, 698, 785, 872, 958, 1045, 1132, 1219, 1305, 1392, 1478, 1564, 1650, 1736, 1822, 1908, 1994, 2079, 2164, 2250, 2334, 2419, 2504, 2588, 2672, 2756, 2840, 2924, 3007, 3090, 3173, 3256, 3338, 3420, 3502, 3584, 3665, 3746, 3827, 3907, 3987, 4067, 4147, 4226, 4305, 4384, 4462, 4540, 4617, 4695, 4772, 4848, 4924, 5000, 5075, 5150, 5225, 5299, 5373, 5446, 5519, 5592, 5664, 5736, 5807, 5878, 5948, 6018, 6088, 6157, 6225, 6293, 6361, 6428, 6494, 6561, 6626, 6691, 6756, 6820, 6884, 6947, 7009, 7071, 7133, 7193, 7254, 7314, 7373, 7431, 7490, 7547, 7604, 7660, 7716, 7771, 7826, 7880, 7934, 7986, 8039, 8090, 8141, 8192, 8241, 8290, 8339, 8387, 8434, 8480, 8526, 8572, 8616, 8660, 8704, 8746, 8788, 8829, 8870, 8910, 8949, 8988, 9026, 9063, 9100, 9135, 9171, 9205, 9239, 9272, 9304, 9336, 9367, 9397, 9426, 9455, 9483, 9511, 9537, 9563, 9588, 9613, 9636, 9659, 9681, 9703, 9724, 9744, 9763, 9781, 9799, 9816, 9833, 9848, 9863, 9877, 9890, 9903, 9914, 9925, 9936, 9945, 9954, 9962, 9969, 9976, 9981, 9986, 9990, 9994, 9997, 9998,10000, 10000 }, b5[4][5], // Tab 5 Lissajous Data c5 = 0, /* Quadrant Counter [incremented 4 quadrants at a time] in order to test when each cycle has completed. */ d5 = 0, // indicates that the lock has taken place e5 = 0, // quadrature modulation phase angle f5 = 0, // 'quadrature amplitude changed' flag g5 = 0, // Lissajous phase angle h5 = 8, // Tab 5 control button states [SIMUL set by default] i5 = 0, // Tab 5 sequencer status 0 to 9 j5 = 0, // Tab 5's delay timer k5 = 0, // Tab 5's WAIT flag l5 = 1, // Tab 5 can operate in SIMUL mode m5 = 0, // link connection status v5 = 2, // LINK Tab's vertical button states. Defailt=FNET // VARIABLES PERTAINING TO TAB 6 NETS a6 = 0, // the current states of Tab 6's horizontal buttons b6 = 0, // the current states of Tab 6's vertical buttons c6 = 0, // number of nodes loaded [number of nodes on-file] d6[25], // node refs for 5 nodes of each of the 5 fractal networks e6 = 0, // indicates that the nodes data has not yet been loaded f6 = 0, // vertical bias for nodes list display in Tab 6 g6 = 0, // vertical bias for nodes display on a selected bearing h6 = 0, // fractal network number i6 = 0, // nodes data line number within displayed portion of shortlist j6 = 0, // node number within current fracnet k6 = 0, // connection status bits for the 25 nodes l6 = 0, // vertical mouse click line in bearing graph [bearing number] m6 = 0, // number of the selected node on selected bearing n6 = 0, // button selector bits for the REGEN and AUTO buttons // VARIABLES PERTAINING TO TAB 7 [MOON] a7 = 0, // Tab 7 MOON button states b7 = 4, // the current states of Tab 7 BAND band buttons c7 = RED,// moon up [above horizon] = GREEN; down [below it] = RED f7 = 0, // down-channel frequency m7 = 32, // the current states of Tab 7 BAND modulation buttons q7 = 0, // moon's 'status changed' flag s7 = 0, // BAND sequencer status: 0:inactive; 1:scanning; 2:finished t7 = 0, // data readout re-write timer u7 = 0, // moon-bounce up-channel frequency v7 = 0, // 'up-channel frequency acquired' flag w7 = 0, // up-channel request button states x7 = 0, // up-channel request timer // VARIABLES PERTAINING TO THE ENTIRE PROGRAM: NOT SPECIFIC TO ANY ONE TAB dn = 0, // current day number of the year tb = 0, // number of the currently displayed Tab [0 to 7] Tb = 0, // number of currently selected tab cm = 0, // index number of current message mc = 0, // colour of current message mx = 0, // x-coord of mouse click my = 0, // y-coord of mouse click mw = 0; // mouse wheel variable: 1:scroll up, 2:scroll down char g2[22] = "gedit _________.txt &", d3[16][80], // to hold screen full of intput commands [Tab 4] e3[16][80], // to hold screen full of output commands [Tab 4] f3[9], // HEX network address of base node g3[9], // HEX port address of base node h3[9], // HEX network address of currently selected destination node i3[9]; // HEX port of currently selected destination node /* VARIABLES FOR THE VINCENTY FORWARD & INVERSE DISTANCE & BEARING METHODS ALL ANGLES GIVEN IN RADIANS */ double MoonEl = 0.0, // moon elevation FwdAzi = 0.0, // forward azimuth: bearing of node as viewed from base node BakAzi = 0.0, // back-azimuth: bearing of base node as viewed from node VinDst = 0.0, // ellipsoidal distance base node to node [metres] EquRad = 0.0, // Equatorial radius of the Earth in metres FlatF = 0.0, // flattening factor [depends on which geoid is selected] EquPol = 0.0, // Equatorial radius x EquPol = polar radius NodLat = 0.0, // latitude of base node NodLng = 0.0; // longitude of base node time_t // time variable [generally a long 64-bit integer] gms, // elapsed time between local time and 00h00 01 January 1970 etm; // elapsed time between GMT and 00h00 01 January 1970 struct tm // structure for the different components of the time data lmt, // for local time gmt; // for GMT int lcs; // local clock time in seconds past midnight void helpPage(char *S) { // DISPLAY THE HELP PAGE IN THE DEFAULT WEB BROWSER int i, s = strlen(S); // length of 'com.html' file path char C[64] = "xdg-open https://robmorton.website/"; // 35 characters for(i = 0; i < s; i++) // add the file path for 'com.html' C[i+35] = S[i]; // onto the end of the base URL C[i+35] = ' '; // space after the end of the command C[i+36] = '&'; // place command in 'background' mode C[i+37] = 0; // string-terminating NULL character printf("%s\n",C); // verification print in terminal window system(C); // issue the command to the system } void clearTab() { XSetForeground(XD,XG,BLACK); // clear the display area XFillRectangle(XD,XW,XG,17,38,484,330); } void showButs() { // SHOW THE 8 BLANK BUTTONS ACROSS THE BOTTOM OF THE TAB int h = 17; // set 'h' to left side of first button XSetForeground(XD,XG,DARK); // shade for buttons' background for(int i = 0; i < 8; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,h,362,53,21); h += 59; // move across to the next button } } /* GET AN INT FROM A 4-BYTE TRAIN IN A FILE. Called from 3 places in loadBands(), 2 places in T1showBroadcasters(), 1 place in T1showFreqs(), T1nextFreq(), T1click().*/ int getInt(FILE *F) { unsigned int x // need to shift the sign bit too = fgetc(F) << 24 // shift first byte to left of int | fgetc(F) << 16 // shift second byte to 2nd from left | fgetc(F) << 8 // shift 3rd byte to 2nd from right | fgetc(F); // leave 4th byte where it is at far right return (int)x; // return as a signed integer } /* PUT AN INT TO A 4-BYTE TRAIN IN A FILE: NO FLUSH. Called from a large number of places throughout the program */ void putInt(int X, FILE *F) { unsigned int x = (unsigned int)X; // need to shift the sign bit too fputc(x >> 24 & 0xFF, F); // Store the new number of records fputc(x >> 16 & 0xFF, F); // as a 4 byte int in the first fputc(x >> 8 & 0xFF, F); // 4 bytes of the records file. fputc(x & 0xFF, F); } /* CONVERT AN 'int x' TO AN l-DIGIT NUMERIC STRING WITH LEADING SPACES OR ZEROS IN ARRAY S[]: Called from 1 place each in: T5LF(), T3update(), T1showFreqs(), T1nextFreq(), T1showBroadcasters(), T0BandNames(), T0show(), T0click(), TomouseFreq(). Called from 2 places each in: T4showLines(), T4update(), T3showLines().*/ char *showInt(int x, int l, int y) { static char S[33]; // to hold the string version of the number char s = ' '; // for leading spaces if(y) s = '0'; // for leading zeros if(l > 11) l = 11; // safety precaution for(int i = 0; i < l; i++) S[i] = s; // fill the whole field with spaces or zeros S[l - 1] = '0'; S[l] = (char)0; // terminating NULL character if(x > 0) { // if the number is non-zero: int i = l; // total of 'l' possible digits in the number /* While there're still more digits to process, work back- wards from [8] to [0], storing the remainder after dividing the number by 10 as a numeric character in array S[]. Then divide the number by 10 [unrounded] and loop back. */ while(x > 0 && i > 0) { S[--i] = (char)(x % 10 + 48); x /= 10; } } return S; // return the address of start of char array } // containing the formatted number. /* CONVERT A DOUBLE TO AN 'N'-DIGIT DISPAYABLE STRING */ char *showDouble(double D) { static char S[16]; // up to 16 digits allowed for(int i = 0; i < 16; i++) // for each of the up to 16 digits S[i] = ' '; // set it to be a 'space' character S[16] = 0; // the 16th digit is the terminating null character sprintf(S,"%15f",D); // mount the number right justified into the array return S; // return the pointer to the array } /* Formats a latitude or longitude supplied as a number of radians into a character display in degrees:minutes:seconds. */ char *RadToDMS(double d, int flag) { // convert radians to DEG:MIN:SEC format int x = rint(d * SPR); // convert radians to integral seconds static char C[11] = "000:00:00N"; // template for display string if(flag == 0) // If formatting a latitude if(x < 0) { C[9] = 'S'; x = -x; } else C[9] = 'N'; else // else formatting a longitude if(x < 0) { C[9] = 'W'; x = -x; } else C[9] = 'E'; int y = x / 60, // integral minutes z = x % 60; // remaining seconds C[7] = (char)(z / 10 + 48); // integral 10s of seconds C[8] = (char)(z % 10 + 48); // integral remaining seconds x = y / 60; // integral degrees z = y % 60; // remaining minutes C[4] = (char)(z / 10 + 48); // integral 10s of minutes C[5] = (char)(z % 10 + 48); // integral remaining minutes y = x / 10; // 10s of degrees C[2] = (char)(x % 10 + 48); // integral remaining degrees C[1] = (char)(y % 10 + 48); // integral remaining 10s of degrees if(flag == 0) // if a latitude, C[0] = ' '; // prefix it with a space else // else it's a longitude, so show the C[0] = (char)(y / 10 + 48); // integral remaining 100s of degrees return &C[0]; // address of the output string } /* Formats the latitude or longitude of the current Node, supplied as an inte- gral number of seconds of arc, within a 10-character array ready for display in degrees, minutes and seconds format. */ char *SecToDMS(int x, int flag) { // convert seconds to DEG:MIN:SEC format static char C[11] = "000:00:00N"; // template for display string if(flag == 0) // If formatting a latitude if(x < 0) { C[9] = 'S'; x = -x; } else C[9] = 'N'; else // else formatting a longitude if(x < 0) { C[9] = 'W'; x = -x; } else C[9] = 'E'; int y = x / 60, // integral minutes z = x % 60; // remaining seconds C[7] = (char)(z / 10 + 48); // integral 10s of seconds C[8] = (char)(z % 10 + 48); // integral remaining seconds x = y / 60; // integral degrees z = y % 60; // remaining minutes C[4] = (char)(z / 10 + 48); // integral 10s of minutes C[5] = (char)(z % 10 + 48); // integral remaining minutes y = x / 10; // 10s of degrees C[2] = (char)(x % 10 + 48); // integral remaining degrees C[1] = (char)(y % 10 + 48); // integral remaining 10s of degrees if(flag == 0) // if a latitude, C[0] = ' '; // prefix it with a space else // else it's a longitude, so show the C[0] = (char)(y / 10 + 48); // integral remaining 100s of degrees return &C[0]; // address of the output string } /* CONVERT SECONDS TO HRS:MIN:SEC STRING */ char *showTime(int t, int w) { static char S[9]; if(w == 0) // if unsigned output is requested S[0] = ' '; // begin with space else // otherwise, signed output is required if(t < 0) { // so if the presented time is negative S[0] = '-'; // begin with a minus sign t = -t; // and make the time figure positive } else // if the presented time is positive S[0] = '+'; // begin with a plus sign int s = t % 60, // remaining seconds x = t / 60, // total whole minutes m = x % 60, // remaining minutes h = x / 60; // total whole hours if(h < 10) S[1] = '0'; else S[1] = h / 10 + 48; S[2] = h % 10 + 48; S[3] = ':'; if(m < 10) S[4] = '0'; else S[4] = m / 10 + 48; S[5] = m % 10 + 48; S[6] = ':'; if(s < 10) S[7] = '0'; else S[7] = s / 10 + 48; S[8] = s % 10 + 48; return S; } /* Places the HEIGHT of the current node, supplied as an integer, right- justified with leading spaces, within a 10-character array ready for display by the T0updt() function. */ char *showHeight(int x) { static char // use the same character array for each call C[11] = " 0+"; // template for display format if(x < 0) { // if the number is negative C[9] = '-'; // install the minus-sign character x = -x; // and make the number positive } else // otherwise C[9] = '+'; // install the plus-sign character int i; for(i = 0; i < 9; i++) // set all 9 characters C[i] = ' '; // as spaces initially if(x == 0) // if the number is simply zero C[8] = '0'; // just set final numeric charcter as a zero else { // else, if the number is non-zero: i = 9; // total of 9 possible digits in the number /* while there are more digits to process | work backwards from '8' to '0' | | */ while(x > 0 && i > 0) { C[--i] = (char) // Store what remains after dividing the number (x % 10 + 48); // by 10 as a numeric character in the array. x /= 10; // divide the number by 10 [unrounded] } // and loop back } return &C[0]; // return the address of the output string } // MAKE ANGLE LIE BETWEEN 0 AND 2π [Called generally throughout the program] double RatAng(double a) { while(a >= TwoPi) a -= TwoPi; while(a < 0) a += TwoPi; return a; } // MAKE ANGLE LIE BETWEEN -π AND +π [Called generally throughout the program] double BipAng(double a) { while(a > pi) a -= TwoPi; while(a <= -pi) a += TwoPi; return a; } /* INSTALL THE METRICS FOR THE SELECTED GEIOD. Called from only 1 place each in initFlightParams(), T7auto() and T7click(). */ void T7setGeoid() { const double A[] = { // SEMI-MAJOR AXES OF AVAILABLE GEOIDS 6378137.000, // 0 GRS80 / WGS84 (NAD83) * 6378206.400, // 1 Clark 1866 (NAD27) 6377563.396, // 2 Airy 1858 6377340.189, // 3 Airy Modified 6378160.000, // 4 Australian National 6377397.155, // 5 Bessel 1841 6378249.145, // 6 Clark 1880 6377276.345, // 7 Everest 1830 6377304.063, // 8 Everest Modified 6378166.000, // 9 Fisher 1960 6378150.000, // 10 Fisher 1968 6378270.000, // 11 Hough 1956 6378388.000, // 12 International (Hayford) 6378245.000, // 13 Krassovsky 1938 6378145.000, // 14 NWL-9D (WGS 66) 6378160.000, // 15 South American 1969 6378136.000, // 16 Soviet Geod. System 1985 6378135.000, // 17 WGS 72 }, F[] = { // FLATTENING FACTORS OF GEOIDS 0.00335281068118366789616980869833, // 0 GRS80 / WGS84 (NAD83) * 0.00339007530392991937343144432063, // 1 Clark 1866 (NAD27) 0.00334085064149707745426055919427, // 2 Airy 1858 0.00334085064149707745426055919427, // 3 Airy Modified 0.00335289186923721709974853310981, // 4 Australian National 0.00334277318217480587901060845382, // 5 Bessel 1841 0.00340756137869933382175046428024, // 6 Clark 1880 0.00332444929666288455151682985834, // 7 Everest 1830 0.00332444929666288455151682985834, // 8 Everest Modified 0.00335232986925913509889373114314, // 9 Fisher 1960 0.00335232986925913509889373114314, // 10 Fisher 1968 0.00336700336700336700336700336700, // 11 Hough 1956 0.00336700336700336700336700336700, // 12 International (Hayford) 0.00335232986925913509889373114314, // 13 Krassovsky 1938 0.00335289186923721709974853310981, // 14 NWL-9D (WGS 66) 0.00335289186923721709974853310981, // 15 South American 1969 0.00335281317789691440603238146967, // 16 Soviet Geod. System 1985 0.00335277945416750486153020854288, // 17 WGS 72 }; EquRad = A[Geoid]; // semi-major axis of selected geoid [metres] FlatF = F[Geoid]; // flattening factor of selected geoid EquPol = 1 - FlatF; // ratio between polar and Equatorial radii of Earth } /* Solution to the Geodetic INVERSE Problem by T. Vincenty: a modified Rains- ford's Method with Helmert's elliptical terms. Effective in any azimuth and at any distance short of antipodal. Latitudes and longitudes in radians positive north and east. Azimuths in radians clockwise from north. Distance in metres. Transcoded from FORTRAN version by Lcdr L Pfeifer NGS Rockville MD 20FEB75. Error Code values: 0 no error 30 6 base node's latitude > 89°59'00"N 31 7 base node's latitude > 89°59'00"S 32 8 node's latitude > 89°59'00"N 33 9 suspect that node is at base node's antipode 34 10 Reference longitude > 180° E or W 35 11 Observed's longitude > 180° E or W 36 12 More than 100 iterations without converging 37 13 Observer and node are antipodal */ int VSGIP( // VINCENTY SOLUTION TO THE GEODETIC INVERSE PROBLEM double RefLat, // latitude of the base node double RefLng, // longitude of the base node double ObsLat, // latitude of the remote node double ObsLng // longitude of the remote node ) { // The 'PolarLimit' PL is 1 second of arc less than a right-angle. #define PL 1.57050543858623089763516774317834 if(RefLat > +PL) return 6; // base node's latitude > 89°59'00"N if(RefLat < -PL) return 7; // base node's latitude > 89°59'00"S if(ObsLat > +PL) return 8; // remote node's latitude > 89°59'00"N if(ObsLat < -PL) return 9; // remote node's latitude > 89°59'00"S if(RefLng > +pi || RefLng < -pi) return 10; // RefLng > 180° E or W if(ObsLng > +pi || ObsLng < -pi) return 11; // ObsLng > 180° E or W double A = ObsLng - RefLng, // longitude difference B = EquPol * tan(RefLat), // tan(reduced latitude of base node) C = EquPol * tan(ObsLat), // tan(reduced latitude of node) /* Compute the sine and cosine of the reduced latitude of the observer and the cosine of the reduced latitude of the node. */ D = 1 / sqrt(1 + B * B), E = D * B, F = 1 / sqrt(1 + C * C), T = A, // prime T before entering the loop O, // holds previous value of T for each iteration G, H, I, J, // sine(T) and cos(T) K, L, // sin(S) and cos(S) M, e, y, c; VinDst = D * F; // prime ellipsoidal dist (base node to node) BakAzi = VinDst * C; // prime the back-azimuth FwdAzi = BakAzi * B; // prime the forward azimuth int ic = 0; // zero the loop iteration counter /* DO UNTIL NO FURTHER SIGNIFICANT CHANGE IN T. Note that 'ic' is an iteration counter, which cuts off the loop after 100 iterations in case T does not converge. 0.5e-13 is the convergence limit for a distance accuracy to within 1 mm on the Earth's surface. */ do { I = sin(T); J = cos(T); B = F * I; C = BakAzi - E * F * J; G = sqrt(B * B + C * C); H = VinDst * J + FwdAzi; y = atan2(G, H); K = VinDst * I / G; L = 1 - K * K; M = FwdAzi + FwdAzi; if(L > 0) M = H - M / L; e = M * M * 2 - 1; c = ((4 - 3 * L) * FlatF + 4) * L * FlatF / 16; O = T; // save old T before computing its new value T = (1 - c) * ((e * H * c + M) * G * c + y) * K * FlatF + A; } /* while the convergence term is still too big | and not yet completed 100 iterations | | | */ while(fabs(O - T) > 0.5e-13 && ++ic < 100 ); if(ic >= 100) return 12; // didn't manage to converge sufficiently // T converged OK, so now compute the distance 'VinDst'. T = sqrt((1 / EquPol / EquPol - 1) * L + 1) + 1; T = (T - 2) / T; c = (T * T / 4 + 1) / (1 - T); T = (0.375 * T * T - 1) * T; VinDst = ((((G * G * 4 - 3) * (1 - e - e) * M * T / 6 - e * H) * T / 4 + M ) * G * T + y ) * c * EquRad * EquPol; /* If distance calculation be indeterminate then the locations of the base node and node are either antipodal or coincident; else, compute the forward and back azimuths. */ if(isnan(VinDst) > 0) { // if the distance between the base node // and the node be indeterminate, then if(fabs(A) > 1) // if the base node and node are antipodal return 13; // return errpr code 13 [invalid] VinDst = 0; // the points must therefore be coincident } else { // otherwise the distance was determined FwdAzi = atan2(B, C); // so compute forward and back azimuths BakAzi = atan2(D * I, BakAzi * J - E * F) + pi; } if(VinDst < 0) // Ensure that distances are always positive, VinDst = -VinDst; // just to be on the safe side. FwdAzi = RatAng(FwdAzi); BakAzi = RatAng(BakAzi); return 0; // success } /* Places the BEARING or RADIAL of the current node, supplied as a double in radians, right-justified with leading spaces, within a 3-character array ready for display by the T0updt() function. The supplied angle is usually bipolar, so it is first rationalised into the range 0-360 degs. */ char *showRat(double d) { int x = rint(RatAng(d) * DPR), // convert to rounded integral degrees i; static char C[4] = "000"; // 3-digit display string template for(i = 0; i < 3; i++) // make all 3 digits C[i] = '0'; // into '0' characters i = 3; // possibe maximum of 3 characters to deal with int y = 0; // assume 3 characters to start with if(x < 100) y = 1; // if number less than 100, 1 less char to deal with if(x < 10) y = 2; // if number less than 10, 2 less chars to deal with while(i > y) { // while there's yet [another] character to deal with C[--i] = (char) // Write to appropriate position in the 3-char array (x % 10 + 48); // the remainder augmented into an ASCII character. x /= 10; // divide the number by 10 [truncated] } return &C[0]; // return the address of the output string [char array] } /* GET LAT, LNG OR HGT AS A NUMERIC STRING FROM FILE AND RETURN IT AS AN INTEGER. Called only by T6getNodes() and T2getHams(). */ int getLatLngHgt(int N, FILE *F) { int x = 0, n = 0, i; for(i = 0; i < N; i++) { // For the N characters of the lat, lng or hgt char c = fgetc(F); // input the [next] character from the file if(c == '-') n = 1; // set negative flag if '-' sign encountered else if(c > 47 && c < 58) // if it's within the ASCII range of a numeric x = x * 10 // accumulate it character by character to + (c - '0'); // form an integer } if(n == 1) x = -x; // reverse sign if '-' sign was encountered return x; // return lat, long or height as an integer } /* SWAP A PAIR OF RECORDS OF THE 'nodes.dat, 'hams.dat' or 'nodes.idx or 'hams.idx FILES */ void sortSwap( int i, // start byte of first record int j, // start byte of second record int K, // number of bytes per record FILE *F) { // file handle of file being sorted char I[K], // to hold content of 'i'th record J[K]; // to hold content of 'j'th record int k; fseek(F,i,SEEK_SET); // start byte of the 'i'th record for(k = 0; k < K; ++k) // put content of 'i'th record into array I[] I[k] = fgetc(F); fseek(F,j,SEEK_SET); // start byte of the 'j'th record for(k = 0; k < K; ++k) // put content of 'j'th record into array J[] J[k] = fgetc(F); fseek(F,i,SEEK_SET); // start byte of the 'i'th record for(k = 0; k < K; ++k) // put content of J[] into 'i'th record fputc(J[k],F); fseek(F,j,SEEK_SET); // start byte of the 'j'th record for(k = 0; k < K; ++k) // put content of I[] into 'j'th record fputc(I[k],F); } /* GET NODE'S OR HAM'S NAME/LOCATION FROM 'nodes.idx' or 'hams.idx'. Returns the first 8 characters as a long integer for faster alphabetic comparison. Bit of a frig, I know, but it works well. Called from 3 places each in T2idxSort() and T6idxSort. */ unsigned long getLong(int i, int o, int s, FILE *F) { /* file handle of 'nodes.idx' or 'hams.idx' | record number within 'nodes.idx' or 'hams.idx' | | multiply by 32 or 64 [number of bytes per record] | | | offset of required field within the record | | | | */ fseek(F,(i << s) + o,SEEK_SET); unsigned long x = 0; for(int i = 56; i >= 0; i -= 8) /* for each of the 8 bytes [characters] accumulates the ORed bytes | OR-into x | | to avoid shifting the first 4 bytes off the left end into nowhere | | | because fgetc naturally returns an 'int' | | | | file handle for 'pos.idx' or 'nam.idx' | | | | | shift the retrieved byte to the left by | | | | | | 56, 48, 40, 32, 16, 24, 8 or 0 bits | | | | | | | */ x |= (unsigned long)fgetc(F) << i; return x; // return the 8 bytes in an unsigned long integer } /* DISPLAY ADVICE AND ERROR MESSAGES. Called from 1 place in T5status() and from 7 places in T5LSS(). */ void showMsg(int m, int colour) { if(tb != 4 && tb != 5 && tb != 7) return; cm = m; mc = colour; // preserve current message number const char msg[] = { 00,26,31,35,57,40,47,41,24,38,60,63,24, 17,19,19,26,19,21,21,44,44,40,35,34,22, 22,33,23,53,53,42,42,37,53,46,47,53,41 }; const char *MSG[] = { "", "01 Powering up transceiver", "02 Tuning to required frequency", "03 Trying to acquire remote carrier", "04 Trying to phase-lock parser signal with remote carrier", "05 Trying to stabilise 16-bit test frame", "06 Expediting data channel handshaking protocol", "07 Receive channel open for incoming data", "08 Loading nodes data...", "09 Nodes data file could not be opened", "10 Cannot send because no destination node has been selected", "11 Cannot receive because no origination node has been selected", "12 Upload in progress...", "13 Upload paused:", "14 UPLOAD COMPLETED", "15 UPLOAD CANCELLED", "16 Download in progress...", "17 Download paused:", "18 DOWNLOAD COMPLETED", "19 DOWNLOAD CANCELLED", "20 Cannot initiate a SEND from a paused RECV", "21 Cannot initiate a RECV from a paused SEND", "22 Need to release the DEST button first", "23 Initiating voice stream protocol", "24 Duplex voice stream established", "25 Voice stream closed", "26 Acquiring Moon Lock", "27 The Moon is below the horizon.", "28 Tracking the Moon...", "29 No moon up-channel established: see MOON->CHAN tab", "30 No link established: go to LINK tab to set up link", // Vincenty errors "31 Error 6 base node's latitude > 89d59m00sN", "32 Error 7 base node's latitude > 89d59m00sS", "33 Error 8 node's latitude > 89d59m00sN", "34 Error 9 suspect that node is at base node's antipode", "35 Error 10 Reference longitude > 180d E or W", "36 Error 11 Observed's longitude > 180d E or W", "37 Error 12 More than 100 iterations without converging", "38 Error 13 Observer and node are antipodal" }; int x = 65, y = 331, z = 418; XSetForeground(XD,XG,DARK); // clear the message area XFillRectangle(XD,XW,XG,x,y,z,21); XSetForeground(XD,XG,colour); XDrawString(XD,XW,XG,x + 6,y + 14,MSG[m],msg[m]); } /* DISPLAY THE FREQUENCY INDICATOR GRATICULE. Called from one place each in T1nextFreq() and T0BandNames(). */ void graticule() { XSetForeground(XD,XG,DARK); // clear the graticule area XFillRectangle(XD,XW,XG,182,80,301,121); int x = 80; // left top corner horizontal XSetForeground(XD, XG, BROWN); for(int i = 0; i < 13; i++) { // draw vertical graticule lines XDrawLine(XD,XW,XG,182,x,481,x); x += 10; // shift to next line rightwards } x = 182; // left top corner horizontal for(int i = 0; i < 31; i++) { // draw horizontal graticule lines XDrawLine(XD,XW,XG,x,80,x,200); x += 10; // drop to next line down } } /* READ-IN THE BAND NAMES FOR THE CURRENTLY SELECTED BAND-TYPE. This data pertaining to each band is stored in an instance of the 'struct' called 'bands'. All stuctures pertaining to the bands of the currently selected band type are stored in the array of structures bn[]. Called from 1 place each in T1show(), T0show() and T0click(). */ void loadBands() { // FIND RECORD NUMBER WITHIN bands.dat WHERE THIS BAND'S DATA STARTS int r = 0; // For each band type before the current for(int i = 0; i < d0; i++) // one, add in the number of bands it r += f0[i] + 1; // contains + 1 for the the band type. /* There are 32 bytes per band data record. So the start byte of the first band of the current type is 32 times the number of preceding records. */ int R = (r + 1) << 5; // start byte of record 'r+1' int N = f0[d0]; // number of bands within selected band type for(int i = 0; i < N; i++) { // for each band of the selected type fseek(ft,R,SEEK_SET); // find the start byte of its data record bn[i].gs = getInt(ft); // get its graticule start frequency bn[i].os = getInt(ft); // get its official start frequency bn[i].ow = getInt(ft); // get its official extent [in kHz] int x = 0, // band name index number c; // for character pulled from bands.dat while((c = fgetc(ft)) != '\0') // while what is pulled isn't a null char bn[i].N[x++] = c; // add it to the structure's name element bn[i].N[x] = '\0'; // terminate the name with a null char R += 32; // advance to start of next band's record } } // FUNCTIONS PERTAINING TO TAB 6: NETWORKS ----------------------------------- /* SHOWS THE STACK OF 7 EXTRA VERTICAL BUTTONS ON THE GRAPH DISPLAY */ void T6graphButs() { int h = 114; // set 'h' to top of first button XSetForeground(XD,XG,DARK); // shade for buttons' background for(int i = 0; i < 7; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,17,h,53,21); h += 27; // move across to the next button } char // annotate the vertical row of control buttons *A[] = {"BRGS","NODE1","NODE2","NODE3","NODE4","LIST","SPARE"}; int a[] = {15,12,12,12,12,15,12}, // horizontal offsets for word starts b[] = { 4, 5, 5, 5, 5, 4, 5}; // number of letters in each word h = 114 + 15; // x coordinate of button block for(int i = 0; i < 7; i++) { // for each of the 8 buttons: if(b6 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else // otherwise XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,17+a[i],h,A[i],b[i]); h += 27; // move across to the next button } } /* SHOWS THE STACK OF 3 EXTRA VERTICAL BUTTONS ON THE FRACNETS DISPLAY */ void T6fracButs() { int h = 281; // set 'h' to top of first button XSetForeground(XD,XG,DARK); // shade for buttons' background for(int i = 0; i < 3; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,17,h,53,21); h += 27; // move down to the next button } char // annotate the vertical row of control buttons *A[] = {"REGEN","AUTO","CLEAR"}; int a[] = {12,15,12}, // horizontal offsets for word starts b[] = { 5, 4, 5}; // number of letters in each word h = 296; // x coordinate of button block for(int i = 0; i < 3; i++) { // for each of the 8 buttons: if(n6 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else // otherwise XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,17+a[i],h,A[i],b[i]); h += 27; // move across to the next button } } /* DISPLAYS A SCROLLABLE LIST OF THE NODES THAT ARE ON A SPECIFIED BEARING FROM THE BASE NODE. Called from 1 place in T6graphPanel() & */ void T6listScroll() { int I = bg[l6][0]; // number of NODES on the selected bearing if(I > 25) I = 25; // limited to storing 25 nodes per degree XSetForeground(XD,XG,BLACK); // show column headings in yellow if(mw == 1 && g6 < I-16) { // if Mouse Wheel 1 g6++; // scroll down the list XFillRectangle(XD,XW,XG,130,73,353,270); } else if(mw == 2 && g6 > 0) { // if Mouse Wheel 2 g6--; // scroll back up list XFillRectangle(XD,XW,XG,130,73,353,270); } mw = 0; // Mouse wheel event now dealt with int v = 63 + 20, // y-coord for first node shown M = I; // number of lines to be displayed if(I > 16) // only got space for 16 lines max M = 16; FILE *F = fopen("nodes.dat","r"); // open the 'nodes.dat' file for(int i = 0; i < M; i++) { // for each line to be displayed int x = bg[l6][i+g6+1], // main node number for current bearing r = x << 6; // start byte of this rec'd in 'nodes.dat' // CHECK STATUS OF NODE IN 'nodes.dat' fseek(F,r + 24,SEEK_SET); // start byte of marker field int m = 1 << (i + g6); // shortlist number of field if(getInt(F)) { // if the marker flag is set m6 |= m; // set the highlight bit XSetForeground(XD,XG,WHITE); // highlight it in bright white } else { // else, if it is unset m6 &= ~m; // unset the highlight bit XSetForeground(XD,XG,BLEEN); // display it in grey } // DISPLAY NODE'S LIST NUMBER, DISTANCE, FREQUENCY, PORT, ADDRESS, NAME int Q = 130; XDrawString(XD,XW,XG,Q,v,showInt(x,3,1),3); // list number fseek(F,r + 8,SEEK_SET); // start byte of Dst field XDrawString(XD,XW,XG,Q+=24,v,showInt(getInt(F),8,0),8); // Dst fseek(F,r + 20,SEEK_SET); // start byte of Frq field XDrawString(XD,XW,XG,Q+=54,v,showInt(getInt(F),9,0),9); // Frq // DISPLAY THIS NODE'S PORT AND NET ADDRESS int k; char S[21]; fseek(F,r + 28,SEEK_SET); // start byte of Prt field for(k = 0; k < 4; k++) S[k] = fgetc(F); // copy each byte of Prt into S[] XDrawString(XD,XW,XG,Q+=60,v,S,4); // display Prt [port number] for(k = 0; k < 8; k++) S[k] = fgetc(F); // copy each byte of Net into S[] XDrawString(XD,XW,XG,Q+=30,v,S,8); // display Net [network address] // DISPLAY THIS NODE'S LOCATION NAME int l = 0; // inset [in pixels] to right-justify the location name char c; // to hold current character of location name for(k = 0; k < 20; k++) { // copy the upto 20 bytes of if((c = fgetc(F)) == 0) // the location name int S[] break; // break out if end of name is encountered S[k] = c; // add the character to the location name } XDrawString(XD,XW,XG,Q+=54,v,S,k); // display the location name v += 17; // drop to next lower line } fclose(F); // close the 'nodes.dat' file // IF MORE THAN 16 NODES IN THE ROUTE WE NEED TO DISPLAY A SCROLL BAR if(I < 17) return; // exit if only 16 nodes or less int h = 4352 / I; // height of highlighted scroll bar v = g6 * (272-h) / (I-16); // dull above the highlighted part XSetForeground(XD,XG,DARK); // show full length scroll bar in grey XFillRectangle(XD,XW,XG,475,71,8,272); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,475,71+v,8,h); } /* DISPLAYS TITLES & TEXT FOR THE SCROLLABLE LIST OF THE NODES ON A SPECIFIED BEARING FROM THE BASE NODE. Called only from one place in T6graphPanel() */ void T6listGraph() { int Q = 130; // y-coord of column headings char *S = "REF DISTANCE FREQUENCY PORT ADDRESS ------LOCATION------"; XSetForeground(XD,XG,YELLOW); // show column headings in yellow XDrawString(XD,XW,XG,Q,63,S,57); XSetForeground(XD,XG,GREY); // show show text in grey XDrawString(XD,XW,XG,17,63,"Select [click]",14); XDrawString(XD,XW,XG,17,78,"one node only.",14); T6listScroll(); } /* DRAW THE VERTICAL LINES TO MARK BEARING POSITIONS ON THE GRAPH Called only from one place in T6vertButs(). */ void T6brgLines() { int h; if(b6 & 1) { // if the BRGS button is bright // DRAW THE YELLOW LINES TO MARK THE BEARINGS OF SELECTED FRACNET'S NODES FILE *F = fopen("nodes.dat","r"); XSetForeground(XD,XG,YELLOW); // bearing line colour for(int i = 0; i < 5; i++) { // for each of the 5 nodes in selected NET /* start byte of node's Brg [bearing 0 to 360] within 'nodes.dat' | record numbers in 'nodes.dat' of all 25 fracnet nodes | | record number of 1st of nod of selected fracnet h6 * 5 | | | | | ----------------- */ fseek(F,(((d6[(h6 << 2) + h6 + i]) << 6) + 12),SEEK_SET); /* | | | node number within selected fracnet | | times 64 to get start byte of record | add offset ofBrg field within the record */ h = getInt(F) + 109; /* the line's x-coord is the Brg + the x-coord of the start of the graph. */ XDrawLine(XD,XW,XG,h,55,h,304); // vertical line marking node bearing } fclose(F); } else { // else [if the BRGS button is dim] // DRAW THE BLUE LINE TO MARK THE CURRENTLY SELECTED BEARING h = l6 + 109; XSetForeground(XD,XG,0x4488AA); // mouse line colour; draw a XDrawLine(XD,XW,XG,h,55,h,304); // vertical line where mouse clicked } } /* DISPLAY THE ACTUAL GRAPH ITSELF ON THE GRAPH SCREEN AREA. cALLED FROM ONLY ONE PLACE IN t7GRAPH(). */ void T6showGraph() { // ANNOTATIONS FOR GRAPH'S VERTICAL AND HORIZONTAL SCALES char *S[7] = {"25","20","15","10","05","00"}, *T[14] = {"000","030","060","090","120","150","180", "210","240","270","300","330","360"}; // DRAW THE DARK GRATICULE FOR THE GRAPH int i, j = 55; for(i = 0; i < 6; i++) { // draw the long horizontal gratucule lines XDrawLine(XD,XW,XG,109,j,469,j); j += 50; } j = 109; for(i = 0; i < 13; i++) { // draw the long vertical graticule lines XDrawLine(XD,XW,XG,j,55,j,305); j += 30; } // DRAW THE GRAPH AREA'S EXTRA BUTTONS XSetForeground(XD,XG,DARK); // lettering colour XFillRectangle(XD,XW,XG,17,303,24,21); // LEFT button XFillRectangle(XD,XW,XG,46,303,24,21); // LEFT button XSetForeground(XD,XG,WHITE); // lettering colour XDrawString(XD,XW,XG,26,318,"<",1); XDrawString(XD,XW,XG,55,318,">",1); // DISPLAY THE GRAPH'S VERTICAL SCALE XSetForeground(XD,XG,GREY); XDrawLine(XD,XW,XG,107,55,107,305); // draw long vertical scale line j = 55; for(i = 0; i < 26; i++) { // draw short horizontal scale marks XDrawLine(XD,XW,XG,103,j,107,j); j += 10; } j = 60; for(i = 0; i < 6; i++) { // annotate the vertical scale XDrawString(XD,XW,XG,87,j,S[i],2); j += 50; } // DISPLAY THE GRAPH'S HORIZONTAL SCALE XDrawLine(XD,XW,XG,109,307,468,307); // draw the long horizontal scale j = 109; for(i = 0; i < 13; i++) { // draw the short vertical scale marks XDrawLine(XD,XW,XG,j,307,j,312); j += 30; } j = 101; for(i = 0; i < 13; i++) { // annotate the horizontal scale XDrawString(XD,XW,XG,j,327,T[i],3); j += 30; } // DRAW THE AXIS LABELS XDrawString(XD,XW,XG,190,345,"BEARING FROM BASE NODE [DEGREES]",32); XDrawString(XD,XW,XG,37,60," NUMBER",7); XDrawString(XD,XW,XG,37,76,"OF NODES",8); // DRAW THE GREEN BAR PLOTS INDICATING THE NUMBER OF NODES j = 305; for(i = 0; i < 360; i++) { // for each of the 360 degrees of bearing int n = bg[i][0]; // get the number of nodes on this bearing if(n == 0) continue; // don't show zero plots int c = GREEN; // geen is the normal colour for a plot line if(n > 25) { // however, if there are more than 25 nodes n = 25; // on this bearing, limit the graph to 25 c = RED; // and display the bar in red } XSetForeground(XD,XG,c); // set colour for the plot, then display it XDrawLine(XD,XW,XG,109+i,j,109+i,j-n*10); } } /* DRAW THE GRAPH OF THE NUMBER OF NODES PER DEGREE OF BEARING FROM BASE NODE Called only from one place in T6butClick(). */ void T6graphPanel() { XSetForeground(XD,XG,BLACK); // clear the graph's display area XFillRectangle(XD,XW,XG,17,38,484,320); XSetForeground(XD,XG,DARK); if(b6 & 32) // if the LIST button is lit T6listGraph(); // draw the list of NODES on selected bearing else { // else, if the LIST button is dim T6showGraph(); // display the graph itself T6brgLines(); // display bearing lines or mouse bearing marker } XSetForeground(XD,XG,WHITE); // bearing read-out colour XDrawString(XD,XW,XG,25,100,"BRG",3); XDrawString(XD,XW,XG,46,100,showInt(l6,3,1),3); T6graphButs(); // display the states of the vertical buttons } /* DISPLAY AND SCROLL THE NODES LIST. Called from only 1 place each in T6showList(), T6scroll(). */ void T6nodeScroll() { XSetForeground(XD,XG,BLACK); // clear the nodes list display area if(mw == 1 && f6 < c6-16) { // if Mouse Wheel 1 f6++; // scroll down the list XFillRectangle(XD,XW,XG,17,73,484,270); } else if(mw == 2 && f6 > 0) { // if Mouse Wheel 2 f6--; // scroll back up list XFillRectangle(XD,XW,XG,17,73,484,270); } mw = 0; // Mouse wheel event now dealt with int v = 83, // y-coord for first node shown M = c6; // number of lines to be displayed if(c6 > 16) // only got space for 16 lines max M = 16; FILE *F = fopen("nodes.dat","r"); // open the 'nodes.dat' file for(int i = 0; i < M; i++) { // for each line to be displayed int x = f6 + i, // node number for current line r = x << 6; // start byte in 'nodes.dat' of this node's data // SET COLOUR FOR WHETHER OR NOT THIS LINE IS TO BE HIGHLIGHTED fseek(F,r + 24,SEEK_SET); // seek start byte of marker if(getInt(F) == 1) // if this is a selected node XSetForeground(XD,XG,WHITE); // highlight it in bright white else // otherwise XSetForeground(XD,XG,BLEEN); // display it in grey // DISPLAY NODE'S LIST NUMBER, NAME, LATITUDE, LONGITUDE AND HEIGHT int Q = 17; XDrawString(XD,XW,XG,Q,v,showInt(x,3,1),3); fseek(F,r,SEEK_SET); // start byte of this record within 'nodes.dat' XDrawString(XD,XW,XG,Q+=20,v,SecToDMS(getInt(F),0),10); // Lat XDrawString(XD,XW,XG,Q+=70,v,SecToDMS(getInt(F),1),10); // Lng getInt(F); // skip distance getInt(F); // skip bearing XDrawString(XD,XW,XG,Q+=42,v,showHeight(getInt(F)),10); // Hgt XDrawString(XD,XW,XG,Q+=78,v,showInt(getInt(F),7,0),7); // Frq getInt(F); // skip the marker integer // DISPLAY THIS NODE'S PORT AND NET ADDRESS int k; char S[21]; for(k = 0; k < 4; k++) S[k] = fgetc(F); // copy each byte of Prt into S[] XDrawString(XD,XW,XG,Q+=48,v,S,4); // display Prt [port number] for(k = 0; k < 8; k++) S[k] = fgetc(F); // copy each byte of Net into S[] XDrawString(XD,XW,XG,Q+=30,v,S,8); // display Net [network address] // DISPLAY THIS NODE'S LOCATION NAME int l = 0; // inset [in pixels] to right-justify the location name char c; // to hold current character of location name for(k = 0; k < 20; k++) { // copy the upto 20 bytes of if((c = fgetc(F)) == 0) // the location name int S[] break; // break out if end of name is encountered S[k] = c; // add the character to the location name } XDrawString(XD,XW,XG,Q+=54,v,S,k); // display the location name v += 17; // drop to next lower line } fclose(F); // close 'nodes.dat' // IF MORE THAN 16 NODES IN THE ROUTE WE NEED TO DISPLAY A SCROLL BAR if(c6 < 17) return; // exit if no more than '16 lines to dispolay int h = 4352 / c6; // height of highlighted scroll bar v = f6 * (272-h) / (c6-16); // dull above the highlighted part XSetForeground(XD,XG,DARK); // show full length scroll bar in grey XFillRectangle(XD,XW,XG,475,71,8,272); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,475,71+v,8,h); } /* DISPLAY THE TITLES OF THE NODES LIST. Called from only 1 place each in showTab(). */ void T6showList() { if(tb != 6) return; // bail out if NETS tab not currently visible XSetForeground(XD,XG,BLACK); // clear the nodes list display area XFillRectangle(XD,XW,XG,17,38,484,320); int Q = 17; // y-coord of column headings char *S = "REF LATITUDE LONGITUDE HEIGHT FREQUENCY " "PORT NET ADDR ------LOCATION------"; XSetForeground(XD,XG,YELLOW); // show column headings in yellow XDrawString(XD,XW,XG,Q,63,S,77); T6nodeScroll(); } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF NODES TAB Called from only one place in T6show() and T6click(). */ void T6Buts(){ showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate the horizontal row of control buttons *A[] = {"LIST","BUILD","GRAPH","NET1","NET2","NET3","NET4","HELP"}; const int a[] = {15,12,12,15,15,15,15,15}, // horizontal offsets for word starts b[] = { 4, 5, 5, 4, 4, 4, 4, 4}; // number of letters in each word int h = 17; // x coordinate of button block for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a6 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; // move across to the next button } } /* GET DISTANCE OF A GIVEN NODE FOR SORTING. The 4-byte integer containing the dostance of the 'i'th node from the base node starts at Byte 8 of the 'i'th 64-byte record of 'nodes.dat'. Called from 3 places in T6datSort() */ int T6idxDst(int i, FILE *F) { fseek(F,(i << 6) + 8,SEEK_SET); // start byte of the distance field return getInt(F); // return distance of the 'i'th node } /* SORTS 'nodes.dat' INTO ASCENDING ORDER OF DISTANCE. Called only from one place in T6getNodes(). This function is based on the Hoare Quick-Sort. */ void T6datSort( int L, // lowest occupied element of the array to be sorted int H, // highest occupied element of the array to be sorted FILE *F) { // file handle for 'nodes.dat' int l = L, // set moving low to LOW end of partition h = H; // set moving high to HIGH end of partition if(H > L) { // if partition contains anything int M = T6idxDst(L + H >> 1,F); // get content of its mid element while(l <= h) { // loop through file until the indices cross /* While distance held in lowest element of Hdst[] < distance held in midway element of Hdst[], push lower sort boundary up by one element. */ while(l < H && T6idxDst(l,F) < M) l++; /* While distance held in highest element of Hdst[] > distance held in midway element of Hdst[], pull upper sort boundary down by 1 element. */ while(h > L && T6idxDst(h,F) > M) h--; if(l <= h) { // if low index <= high index, swap contents of /* number of the moving 'low' record | multiply by 64 [the number of bytes per record | | number of the moving 'high' record | | multiply by 64 [the number of bytes per record | | | record size = 64 bytes | | | | file handle for 'nodes.idx' | | | | | */ sortSwap(l<<6,h<<6,64,F); // high & low elements of 'nodes.dat' l++; // push lower sort boundary up by one element h--; // pull upper sort boundary down by one element } } if(L < h) T6datSort(L,h,F); // sort lower partition if(l < H) T6datSort(l,H,F); // sort upper partition } } /* SORTS 'nodes.idx' INTO ALPHABETICAL ORDER OF LOCATION. Called only from one place in T6getNodes(). This function is based on the Hoare Quick-Sort. */ void T6idxSort( int L, // lowest occupied element of the array to be sorted int H, // highest occupied element of the array to be sorted FILE *F) { // file handle for 'nodes.dat' int l = L, // set moving low to LOW end of partition h = H; // set moving high to HIGH end of partition if(H > L) { // if partition contains anything // get content of the mid record of 'nodes.idx' unsigned long M = getLong(L + H >> 1,4,5,F); while(l <= h) { // loop through file until the indices cross /* While distance held in lowest element of Hdst[] < distance held in midway element of Hdst[], push lower sort boundary up by one element. */ while(l < H && getLong(l,4,5,F) < M) l++; /* While distance held in highest element of Hdst[] > distance held in midway element of Hdst[], pull upper sort boundary down by 1 element. */ while(h > L && getLong(h,4,5,F) > M) h--; if(l <= h) { // if low index <= high index, swap contents of /* number of the moving 'low' record | multiply by 32 [the number of bytes per record | | number of the moving 'high' record | | multiply by 32 [the number of bytes per record | | | record size = 32 bytes | | | | file handle for 'nodes.idx' | | | | | */ sortSwap(l<<5,h<<5,32,F); // high & low elements of 'nodes.idx' l++; // push lower sort boundary up by one element h--; // pull upper sort boundary down by one element } } if(L < h) T6idxSort(L,h,F); // sort lower partition if(l < H) T6idxSort(l,H,F); // sort upper partition } } /* LOAD THE NODES INFORMATION LINE-BY-LINE FROM THE NODES FILE 'nodes.dat'. Called from one place each in T6regen(), T6show(), T6butClick(). */ void T6getNodes() { if(e6) return; // exit if nodes data already loaded // LOAD THE NUMBER OF NODES ON-FILE FILE *F = fopen("nodes.dat","r"); // open 'nodes.dat' file for writing fseek(F,8,SEEK_SET); // start byte of record 0's 'Dst' field c6 = getInt(F); // set the number of nodes on-file fclose(F); // close 'nodes.dat' // LOAD THE CURRENT FRACNET CONFIGURATIONS int i; F = fopen("fracnets.dat","r"); // load the fracnet settings for(i = 0; i < 25; i++) // for each node in the 5 fracnets d6[i] = getInt(F); // get its 'nodes.dat' record number k6 = getInt(F); // load the current status bits for the 25 nodes fclose(F); // close the fracnets file // FIND THE NUMBER OF AND INDEX NUMBERS FOR THE NODES ON EACH BEARING for(i = 0; i < 360; i++) // clear the bearings per degree array bg[i][0] = 0; F = fopen("nodes.dat","r"); // open for read-only int r = 76; // start byte of bearing field of record 1 for(i = 1; i < c6; i++) { // for each node record in 'nodes.dat' fseek(F,r,SEEK_SET); // start byte of bearing field int b = getInt(F); // bearing of the 'i'th node bg[b][0]++; // increment number of nodes on this bearing int j = bg[b][0]; // number of nodes so far on this bearing if(j < 26) // if still space available bg[b][j] = i; // put node's index num in this bearing's list r += 64; // increment file pointer to next record } fclose(F); // close 'nodes.dat' } /* LOAD THE NODES INFORMATION LINE-BY-LINE FROM THE NODES FILE 'nodes.txt'. STORE IT IN 'nodes.dat' TOGETHER WITH DISTANCE AND BEARING INFORMATION. Format for file 'nodes.dat': Each 64-byte record contains: OFFSET STORAGE CONTENT 0 NNNN latitude 4 NNNN longitude 8 NNNN distance [in rec 0 is number of recs in file] 12 NNNN bearing 16 NNNN height 20 NNNN frequency 24 NNNN marker 28 XXXX port number in hex 32 XXXXXXXX network address hex 40 AAAAAAAAAAAAAAAAAAAA location name 60 SSSS 4 spare bytes Called from only one place in T6fracBut(). */ void T6regen() { FILE *F = fopen("nodes.txt","r"); // open the 'nodes.txt' file for reading showMsg(8,WHITE); // show "Regenerating nodes data" message if(F == NULL) { // if can't open waypoints data file showMsg(9,RED); return; // display error message and exit } // BYPASS THE FILE'S HEADING COMMENTS int n = 0, c, f = 0, i, j, m = 0; while(!feof(F)) { // while we haven't yet reached the end of the file c = fgetc(F); // get the next character from the file 'nodes.txt' m++; // increment the char count to next char to be read if(f == 1) { // if we are inside a comment line if(c == '\n') { // if we have reached the end of the comment line f = 0; // reset the 'in comment' flag n = 0; // reset the line's character count } } else { // otherwise we must be outside a comment line if(c == '#') // must be at the beginning of a comment line f = 1; // so set the 'in comment' flag and loop back else break; // break out of the header bypass loop } } fseek(F,m-1,SEEK_SET); // back-track to character just read FILE *G = fopen("nodes.dat","w"); // open 'nodes.dat' file for writing // READ IN THE NODES DATA AND WRITE IT TO 'nodes.dat' int P = 0; // file pointer for 'nodes.dat' file double X, Y; // lat and lng of the base node for(c6 = 0; c6 < 200; c6++) { // For all possible nodes on file if(feof(F)) break; // terminate loop at end of file fseek(G,P,SEEK_SET); /* set 'nodes.dat' file pointer to start of 1st/next 64-byte record Input the latitude, longitude [in integral seconds of arc] and height [in metres] as strings from the nodes.txt file, convert them all to integers and store all 3 in the nodes structure. */ int x = getLatLngHgt(7,F), // latitude [seconds of arc] y = getLatLngHgt(8,F); // longitude [seconds of arc] putInt(x,G); // save latitude to 'nodes.dat' putInt(y,G); // save longitude to 'nodes.dat' if(c6 == 0) { // if 1st node in the file X = x * RPS; // base node's latitude in radians Y = y * RPS; // base node's longitude in radians putInt(0,G); // base node has zero distance putInt(0,G); // base node has zero bearing } else { // for all other nodes: /* Compute the distance and bearing of the current node from the base node then store them as rounded integers in the nodes structure. */ int e = VSGIP(X,Y,x*RPS,y*RPS); // compute distance and bearing if(e > 0) // if an error occurs printf("VinErr %i\n",e); // print it to the terminal putInt((int)rint(VinDst),G); // put node's distance in 'nodes.dat' putInt((int)rint(FwdAzi*DPR),G); // put node's bearing in 'nodes.dat' } putInt(getLatLngHgt(6,F),G); // store height in 'nodes.dat' file putInt(getLatLngHgt(8,F),G); // store frequency in 'nodes.dat' file putInt(0,G); // store marker as zero [un-marked] fgetc(F); // skip the space character for(i = 0; i < 4; i++) // For each of 4 Port Number chars fputc(fgetc(F),G); // store char to 'nodes.dat' fgetc(F); // skip the space character for(i = 0; i < 8; i++) // For each of 8 Network Address chars fputc(fgetc(F),G); // store char to 'nodes.dat' fgetc(F); // skip the space character for(i = 0; i < 22; i++) { // For each of upto 20 possible chars if((c = fgetc(F)) == '\n') // if it's a new-line character, add a break; // break out of loop if it's a 'CR' fputc(c,G); // store char to 'nodes.dat' } if(i < 21) // if there were less than 20 characters in the location fputc(0,G); // name, put in a terminating NULL character. P += 64; // advance 'nodes.dat' file pointer to start of next record } fclose(G); // close the 'nodes.dat' file. fclose(F); // close the 'nodes.txt' file. e6 = 1; // indicates that nodes have been loaded f6 = 0; // zero the scroll display bias c6--; // total number of nodes [gets over-incremented in loop] /* Open 'nodes.dat' again, this time in updating mode. Set its file pointer to the start of the field that holds the number of records in the file and store the number of records in this field. */ F = fopen("nodes.dat","r+"); T6datSort(1,c6-1,F); // sort 'nodes.dat' into order of ascending distance fseek(F,8,SEEK_SET); // the 'number of records in the file' field starts putInt(c6,F); // at Byte 8 of the first record of the file fclose(F); // close 'nodes.dat' e6 = 0; // clear the 'nodes already loaded' flag T6getNodes(); // load the session nodes parameters // GENERATE & SORT NODES INDEX FILE 'nodes.idx' FROM DATA IN 'nodes.dat' int g = 40; // start byte of name field of 1st record of 'nodes.dat' f = 0; // start byte of 1st record of 'nodes.idx' G = fopen("nodes.dat","r"), // open for read-only F = fopen("nodes.idx","w"); // open for create and write for(i = 0; i < c6; i++) { // for each record on-file: fseek(F,f,SEEK_SET); // start byte of this output record putInt(i,F); // store node's reference number in index file fseek(G,g,SEEK_SET); // start byte of input record's name field for(j = 0; j < 22; j++) { // For each of upto 20 possible chars if((c = fgetc(G)) == 0) // if it's a NULL terminator break; // break out of loop fputc(c,F); // store char to 'nodes.idx' } if(j < 21) // if there were less than 20 characters in the location fputc(0,F); // name, put in a terminating NULL character. g += 64; // advance to the next input record f += 32; // advance to the next output record } fclose(G); // close 'nodes.dat' fclose(F); // close 'nodes.idx' F = fopen("nodes.idx","r+"); // open for reading and modifying T6idxSort(1,c6-1,F); // sort 'nodes.idx' into alpabetical order of location fclose(F); // close 'nodes.idx' } /* DISPLAY THE MAIN FRACNETS SCREEN. Called from one place each in T6show() and T6markFrac(). */ void T6showNet() { FILE *F = fopen("nodes.dat","r"); NodLat = getInt(F); // latitude of base node NodLng = getInt(F); // longitude of base node XSetForeground(XD,XG,BLACK); // clear the nodes list display area XFillRectangle(XD,XW,XG,16,38,486,320); XSetForeground(XD,XG,DARK); // do dark background for selected node XFillRectangle(XD,XW,XG,80 + 84 * j6,50,70,224); // DISPLAY THE SELECTED FRACNET NUMBER int V = 66, v = 20, i; XSetForeground(XD,XG,WHITE); char S[21] = "NET "; // used also for location names [<= 20 chars] *(S + 3) = 48 + h6; // add the fracnet number XDrawString(XD,XW,XG,17,V,S,4); // display the fracnet number // DISPLAY THE COLUMN TITLES AND ROW ANNOTATIONS XSetForeground(XD,XG,YELLOW); // draw node column titles in yellow XDrawString(XD,XW,XG,85,V, "--NODE 0-- --NODE 1-- --NODE 2-- --NODE 3-- --NODE 4--",66); char *U[2] = {" INACTIVE"," CONNECTED"}; char *T[10] = { "LATITUDE ", "LONGITUDE","DISTANCE ", "BEARING ","HEIGHT ", "FREQUENCY","PORT ", "ADDRESS ", "LOCATION ","STATUS " }; for(i = 0; i < 10; i++) // draw the nodes' parameter names also in yellow XDrawString(XD,XW,XG,17,V+=v,T[i],9); // DISPLAY THE NODES DATA AND FRACNET AUTO GENERATOR TEXT XSetForeground(XD,XG,BLEEN); XDrawString(XD,XW,XG,80,296, "Regenerate nodes data file 'nodes.dat' from input file 'nodes.txt'.",67); XDrawString(XD,XW,XG,80,323, "Generate the currently selected FracNet automatically.",54); XDrawString(XD,XW,XG,80,350, "Clear all the selection marks [highlighting] from the nodes list.",65); // DISPLAY THE FRACNET DATA int h = 84, // inter-column shift [in pixels] m = (h6 << 2) + h6; // start element of selected NET's node refs for(i = 0; i < 5; i++) { // FOR EACH OF THE 5 POSSIBLE NODES int q = 85 + i * h, V = 66, j = m + i; XSetForeground(XD,XG,BLEEN); // default colour for the data lines /* record number of node data within 'nodes.dat' | times 64 to get start byte of record | | */ fseek(F,d6[j] << 6,SEEK_SET); // seek start byte of record 'j' XDrawString(XD,XW,XG,q,V+=v,SecToDMS(getInt(F),1),10); // Lat XDrawString(XD,XW,XG,q,V+=v,SecToDMS(getInt(F),1),10); // Lng XDrawString(XD,XW,XG,q,V+=v,showInt(getInt(F),10,0),10); // Dst XDrawString(XD,XW,XG,q,V+=v,showInt(getInt(F),10,0),10); // Brg XDrawString(XD,XW,XG,q,V+=v,showHeight(getInt(F)),10); // Hgt XDrawString(XD,XW,XG,q,V+=v,showInt(getInt(F),10,0),10); // Frq getInt(F); // skip the marker integer int k; // loop variable char S[21]; // for accumulating the inputted characters // DISPLAY THE PORT NUMBER for(k = 0; k < 4; k++) // Port Number is 4-char HEX S[k] = fgetc(F); // copy each byte of Prt into S[] XDrawString(XD,XW,XG,q+36,V+=v,S,4); // display Prt [port number] // DISPLAY THE NETWORK ADDRESS for(k = 0; k < 8; k++) // Network Address is 8-char HEX S[k] = fgetc(F); // copy each byte of Net into S[] XDrawString(XD,XW,XG,q+12,V+=v,S,8); // display Net [network address] // DISPLAY THE LOCATION NAME int l = 0; // inset [in pixels] to right-justify the location name char c; // to hold current character of location name for(k = 0; k < 20; k++) { // location name has up to 20 characters if((c = fgetc(F)) == 0) // when NULL character encountered break; // break out of the for() loop S[k] = c; // add the character to the location name } if(k < 10) l = (10 - k) * 6; // right-justify location name XDrawString(XD,XW,XG,q+l,V+=v,S,k); // display the location name // DISPLAY THE CONNECTION STATUS int r = 0; // index number of status word if(k6 & 1 << j) { // if the current node is CONNECTED r = 1; // set index to CONNECTED status word XSetForeground(XD,XG,GREEN); // bright green for CONNECTED } XDrawString(XD,XW,XG,q,V+=v,U[r],10); // display the status } fclose(F); // close 'nodes.dat' // SAVE EACH NODE'S CONNECTION STATUS F = fopen("fracnets.dat","r+"); // save active nodes fseek(F,100,SEEK_SET); // start byte of node status bits putInt(k6,F); // write them to the file fclose(F); // close 'fracnets.dat' T6fracButs(); // display the two on-panel buttons } /* TAB 6 EXPOSURE FUNCTION. Called only from 1 place in showTab(). */ void T6show() { T7setGeoid(); // initialise the geoid T6getNodes(); // import the nodes data n6 &= ~1; // set the REGEN button to GREY T6fracButs(); // re-display the REGEN, AUTO and CLEAR buttons if(a6 & 1) // if NODES button bright T6showList(); // display the main Nodes List else // else if(a6 & 4) // if GRAPH button bright T6graphPanel(); // display the bearings graph and its vertical buttons else // else T6showNet(); // display the main Fracnets Screen T6Buts(); // re-display the control buttons } /* CLEAR ALL THE MARKED NODES IN THE FILE 'nodes.dat' Called from one place only in T6fracBut(). */ void T6clear() { FILE *F = fopen("nodes.dat","r+"); // open for reading and modifying int r = 24; // byte offset for first record's marker for(int i = 0; i < c6; i++) { // for each node in the file fseek(F,r,SEEK_SET); // start byte of marker field putInt(0,F); // set it to zero r += 64; // advance to the next node record } fclose(F); // close the 'nodes.dat' file n6 &= ~4; // dim the CLEAR button T6fracButs(); // redisplay the REGEN, AUTO and CLEAR buttons } /* PUTS THE REFERENCE NUMBERS OF THE FIRST 5 NODES HIGHLIGHTED IN THE NODES LIST FILE 'nodes.dat' INTO THE PART OF THE FRACTAL NETWORKS REFERENCE ARRAY d6[] THAT PERTAINS TO THE CURRENTLY SELECTED NETWORK h6. 'i' is the record number for a node entry in 'nodes.dat' and 'j' is the index of the current node in d6[]. Called from only one place in T6butClick(). */ void T6build() { FILE *F = fopen("fracnets.dat","r+"), // open for read and modify *G = fopen("nodes.dat","r"); // open for read-only /* h6 contains the number of the currently selected fractal network NET0 to NET4. The array d6[] contains the record numbers, within 'nodes.dat', of the 5 nodes for each of 5 fractal networks. Thus, element h6 * 5 of the array d6[] contains the record number within 'nodes.dat' of the first node pertining to the T6NNth fractal network. */ int // 5 nodes per fracnet in d6[] array m = (h6 << 2) + h6, // m = h6 * 5 done faster n = m << 2, // 4 bytes per integer in 'fracnets.dat' i, // for() loop index j = 0, // count the captured marked [highlishted] nodes k = m; // index for selected fracnet's node data in d6[] // SAVE THE SELECTED FRACNET'S NODE DATA TO THE FRACNETS FILE fseek(F,n,SEEK_SET); // start byte in file of selected fracnet's data for(i = 0; i < c6; i++) { // for each of the nodes in 'nodes.dat' /* file handle for the 'nodes.dat' file | number of the current 64-byte record within the 'nodes.dat' file | | 64 bytes per record [sift 6 left = multiply by 64] | | | offset of the marker field within the 'i'th record | | | | */ fseek(G,(i << 6) + 24,SEEK_SET); // seek first byte of marker field if(getInt(G)) { // if the current node is highlighted d6[k++] = i; // put the node's number in the fractal network list putInt(i,F); // save list num of highlighted node to 'fracnets.dat' if(++j > 4) // if 5 highlighted nodes have now been captured break; // break out of the for(i) loop } } fclose(G); // close the 'nodes.dat' file // WHEN YOU BUILD A NEW FRACNET, ALL NODES MUST START OFF INACTIVE fseek(F,100,SEEK_SET); /* start byte in file of the 25 'CONNECTED' bits CONNECTED status bits for the 5 * 5 = 25 nodes in the 5 fracnets | clear the 5 CONNECTED bits of the selected fracnet h6 | | mask for the first 5 status bits | | | shift 5 bits left per fracnet beyond NET0 | | | | */ k6 &= ~(31 << m); // mask out the CONNECTION bits for fracnet h6 putInt(k6,F); // save the 'CONNECTED' bits to 'fracnets.dat' fclose(F); // close 'fracnets.dat' T6show(); // display the newly built fracnet } /* MARKS THE CURRENTLY HIGHLIGHTED NODE as the 'n'th member of the currently selected fracnet, where 'n' is the number of the illuminated node button on the left. If no node button is highlighted, NODE0 is assumed. */ void T6markNode(int m) { FILE *F = fopen("nodes.dat","r+"); // open to read and modify int I = bg[l6][0]; // total number of nodes on currently selected bearing if(I > 25) I = 25; // maximum number of nodes selectable for(int i = 0; i < I; i++) { // clear all marked nodes on this bearing /* bearing number [0 to 360] currently being marked | element containing record number of [next] node in list | | multiply by 64 to get start byte of record | | | offset of marker field within the record | | | | */ fseek(F,(bg[l6][i+1] << 6) + 24,SEEK_SET); // seek start byte of marker putInt(0,F); // clear marker of this node } /* record number [within 'nodes.dat'] of node to be marked [highlighted] | 2D array of degree number vs list numbers of nodes on this bearing | | number of the bearing degree 0 to 360 | | | */ int x = bg[l6][i6 + g6 + 1]; /* | | | | | + 1 because 0th slot holds the number of nodes | bias from start of list to start of displayed nodes nodes data line number within displayed portion of list file pointer for the 'nodes.dat' file | record num [within 'nodes.dat'] of node to be marked [highlighted] | | times 64 bytes per record to get to start-byte of record | | | add the offset, within the record, of the marker field | | | | */ fseek(F,(x << 6) + 24,SEEK_SET); putInt(m,F); // mark [m=1] or unmark [m=0] the node entry fclose(F); // close the 'nodes.dat' file } /* HANDLES CLICKS TO THE GRAPH SCREEN'S VERTICAL BUTTONS. Called from only one place in T6graphClick(). */ void T6vertButs(int i) { switch(i) { case 0: if(b6 & 1) { // if the BRGS button is on b6 &= ~1; // switch it off } else { // else b6 |= 1; // switch it on } break; // of the selected NET case 1: if(b6 & 2) // if the NODE1 button is on b6 = 0; // switch off all buttons else { // else b6 = 0; // switch off the other NODE buttons and MARK button b6 |= 2; // switch it on m6 = 0; // clear any previous node highlighting } break; case 2: if(b6 & 4) // if the NODE2 button is on b6 = 0; // switch off all buttons else { // else b6 = 0; // switch off all buttons b6 |= 4; // switch it on m6 = 0; // clear any previous node highlighting } break; case 3: if(b6 & 8) // if the NODE3 button is on b6 = 0; // switch off all buttons else { // else b6 = 0; // switch off all buttons b6 |= 8; // switch it on m6 = 0; // clear any previous node highlighting } break; case 4: if(b6 & 16) // if the NODE4 button is on b6 = 0; // switch off all buttons else { // else b6 = 0; // switch off all buttons b6 |= 16; // switch it on m6 = 0; // clear any previous node highlighting } break; case 5: if(b6 & 32) { // if the LIST button is on b6 &= ~32; // switch it off } else { // else b6 |= 32; // switch it on g6 = 0; // zero scroll bias for 'nodes on give bearing' display } break; case 6: // [this button is not currently in use] if(b6 & 64) // if the SPARE button is on b6 &= ~64; // switch it off else { // else b6 |= 64; // switch it on } // of the currently selected fracnet } } /* HANDLE A CLICK MADE ON THE MAIN NODES LISTING. Highlights [selects] one of the nodes displayed in the nodes list. Called only from T6click() */ void T6listClick() { if(mx < 17 && mx > 482) // if outside horizontal limits of list return; // return without doing anything int y = 70,i; // y-coord of first node data line for(i = 0;i < 16;i++) { // for each of the 16 visible data lines if(my>y && my 16 && mx < 70) { // HORIZONTAL LIMITS OF VERTICAL BUTTONS // IF CLICK OCCURRED ON ONE OF THE VERTICAL BUTTONS if(my > 113 && my < 298) { // vertical limits of '<' '>' inching buttons int y = 113; // y-coord of top of top button for(int i = 0; i < 7; i++) { // find in which button the click occurred if(my > y && my < y+21) { // if it occurred within the 'i'th button T6vertButs(i); // go process the button click break; // and break out of the for() loop } y += 27; // drop down to try the next button } } else // IF THE CLICK OCCURRED ON ONE OF THE INCHING BUTTONS if(my > 302 && my < 324) { // limits of the '<' '>' inching buttons if(mx > 16 && mx < 41) { // if click was inside the '<' button, then if(--l6 < 0) // if, after being decremented l6 < 0 l6 = 359; // back up to across to the 359 degree mark } else if(mx > 45 && mx < 70) { // if click was inside the '>' button, then if(++l6 > 359) // if, after incrementing, l6 > 359 l6 = 0; // jump round to the zero degrees mark } } } else if(b6 & 32) { // IF THE 'LIST' BUTTON IS ILLUMINATED // THE CLICK OCCURRED ON A LINE IN THE LIST OF NODES ON CURRENT BEARING int y = 70,i,m; // y-coord of first node data line if(mx > 129 && mx < 472) { // horizontal limits of mouse click for(i = 0; i < 16; i++) { // for each of the 16 visible data lines if(my > y && my < y + 16) { // vertical limits of mouse click int b = 1 << i + g6; // bit position of current line marker if(m6 & b) { // if the line is currently highlighted m6 &= ~b; // unhighlight it m = 0; // to unmark the node } else { m6 |= b; // else highlight it m = 1; // to mark the node } i6 = i; // shortlist number of the marked node T6markNode(m); // mark/unmark node in 'nodes.dat' break; // bail out of for() loop } y += 17; // drop to next node data line } T6listScroll(); // re-display the list of nodes on given bearing } } else // ELSE THE 'LIST' BUTTON IS DIM, SO // THE CLICK OCCURRED ON THE GRAPH ITSELF if(mx > 110 && mx < 470 && // if the mouse click was my > 55 && my < 306) // within the graph area l6 = mx - 109; // number of the bearing degree 0 to 360 T6graphPanel(); // re-display the whole of the GRAPH screen as is } /* AUTOMATICALLY GENERATE THE CURRENTLY SELECTED FRACNET [NET0, NET1, NET2, NET3 or NET4], according to the following procedure: 1) Locate, within bg[], the bearing degree with the most nodes. 2) Select this as the datum bearing. 3) Take its first [nearest] node as NODE0 of the current FracNet. 4) Move clockwise within bg[] by 36 degrees. 5) Start scanning clockwise for a further 72 degrees to find the bearing, within this segment, with the most nodes. 6) Select this bearing's first [nearest] node as NODE1 of current FracNet. Repeat steps 4 to 6 for NODE2, NODE3 and NODE4. Called from only one place in T6fracBut(). */ void T6auto() { int a = 36, // set start of 2nd segment as 36 degs beyond datum b = 108, // set end of 2nd segment 72 degs beyond datum v = 0, // real bearing number with the highest number of nodes w = 0, // the brearing with the greatest number of nodes x = 0, // highest number of nodes found on a bearing so far y = 0, // captures the bearing with the largest number of nodes z = 0, // number of nodes on current bearing i, j, // loop variables n = (h6 << 2) + h6; // first node ref for selected FracNet in d6[] FILE *F = fopen("nodes.dat","r+"); // open for read and modify for(i = 0; i < 360; i++) { // For each degree of bearing: z = bg[i][0]; // number of nodes on the 'i'th bearing if(x < z) { // If this bearing has more nodes than any x = z; // previous one, set its number of nodes w = i; // as the highest yet and note the bearing. } } /* array: number of nodes + their record numbers for each bearing 'w' | bearing number with the most nodes: make it the datum | | first [nearest] node on this bearing | | | offset of the marker field within the node's record | | | | */ fseek(F,bg[w][1] + 24,SEEK_SET); // seek start byte of marker field putInt(1,F); // mark this node for(j = 1; j < 5; j++) { // for each of the 4 remaining 72 deg segments x = 0; // number of nodes on given bearing y = 0; // bearing with largest number of nodes /* NOTE: 'a' and 'b' run as if the datum bearing [that with the greatest number of nodes] were 000 [North]. However, it is unlikely to be North. Each individual value of 'i' must therefore be biased by the amount of the datum bearing 'y' to get the real bearing in each case. However, adding a bias to 'i' will undoubtedly cause it to wrap round past North at some stage. Therefore the value of the real bearing 'v' must have 360 degrees subtracted from it whenever this happens. */ for(i = a; i < b; i++) { // for each degree of brg within this segment v = i + w; // the current real bearing while(v > 360) // if the bearing passes the North v -= 360; // take 360 from it to rationalise it */ z = bg[v][0]; // number of nodes on this real bearing if(x < z) { // If this bearing has more nodes than any x = z; // previous one, set its number of nodes y = v; // as the highest yet and note the bearing. } } /* number of nodes + their record numbers for each bearing 'w' | real bearing number, in this segment, with the most nodes | | first [nearest] node on this bearing | | | offset of the marker field within the node's record | | | | */ fseek(F,bg[y][1] + 24,SEEK_SET); // seek start byte of marker field putInt(1,F); // mark this node a += 72; // align to the start of the next segment b += 72; // align to the end of the next segment } fclose(F); // close 'nodes.dat' T6build(); // build the NET and store its details to disk n6 &= ~2; // dim the AUTO button } /* HANDLES MOUSE CLICK EVENTS ON THE 'REGEN' AND 'AUTO' BUTTONS Called only from one place in T6markFrac(). */ void T6fracBut(int i) { switch(i) { case 0: // REGEN button was clicked if(n6 & 1) // if the REGEN button is on n6 &= ~1; // switch it off else { // else n6 |= 1; // switch it on T6regen(); // regenerate 'nodes.dat' from 'nodes.txt' } break; case 1: // AUTO button was clicked if(n6 & 2) // if the AUTO button is on n6 &= ~2; // switch it off else { // else n6 |= 2; // switch it on T6auto(); // automatically generate currently selected FracNet } break; case 2: // CLEAR button was clicked if(n6 & 4) // if the CLEAR button is on n6 &= ~4; // switch it off else { // else n6 |= 4; // switch it on T6clear(); // clear all marks [highlighing] from the nodes list } } T6fracButs(); // re-display the REGEN, AUTO and CLEAR buttons } /* MARK ONE OF THE ESTABLISHED NODES WITHIN THE CURRENTLY SELECTED NETWORK Called from only one place in T6click(). */ void T6markFrac() { int y = -1; // in case click was outside if(my > 280) { // if click was below the nodes data table if(mx > 16 && mx < 70) { // horizontal limits of REGEN & AUTO buttons if(my < 302) // if the REGEN button was clicked y = 0; // set switch variable for the REGEN button else if(my > 307 && my < 329) // if the AUTO button was clicked y = 1; // set switch variable for the AUTO button else if(my > 334 && my < 356) // if the CLEAR button was clicked y = 2; // set switch variable for the CLEAR button T6fracBut(y); // go handle the button click } } else { // else the click was on one of the node data columns if(mx > 84 && mx < 145) y = 0; else if(mx > 168 && mx < 229) y = 1; // find which node column else if(mx > 252 && mx < 313) y = 2; // the click was in else if(mx > 336 && mx < 397) y = 3; else if(mx > 420 && mx < 481) y = 4; if(y != -1) j6 = y; // current node number within this fracnet } T6showNet(); // re-display the whole tab content } /* MOUSE CLICK HANDLER FOR TAB 6. Deals with clicks within the main NETS display, the NODES display and the GRAPH display. Called from only one place in the Mouse Events Handler MEH().*/ void T6click() { if(a6 & 1) // if LIST button is bright T6listClick(); // one of the nodes on the main list was clicked else if(a6 & 4) // if Tab 6's GRAPH button is illuminated T6graphClick(); // click occurred in the GRAPH display else // else the click was somewhere else on Tab 6 so it T6markFrac(); // assume it is selecting one of the 5 nodes of the } // currently displayed fracnet /* HANDLES CLICKS TO THE 8 BOTTOM BUTTONS OF THE NETS TAB [TAB 6] Called from only one place in MEH(). */ void T6butClick(int x) { switch(x) { case 0: if(a6 & 1) { // if the LIST button is bright a6 &= ~1; // dim it T6show(); // re-display the Fracnet Data nodes data } else { // else it is dim T6getNodes(); // load the nodes if not already loaded n6 &= ~1; // set the REGEN button to GREY T6fracButs(); // re-display the REGEN, AUTO and CLEAR buttons T6showList(); // display the nodes a6 |= 1; // so brighten it a6 &= ~6; // and dim the other two buttons } break; case 1: if(a6 & 2) // if the BUILD button is bright a6 &= ~2; // dim it else { // else it is dim a6 |= 2; // so brighten it a6 &= ~5; // and dim the other two buttons T6build(); // build the selected fractal network } break; case 2: // GRAPH button [disconnects a remote node] if(a6 & 4) { // if the BUILD button is bright a6 &= ~4; // dim it T6show(); // re-display the fracnets screen } else { // else it is dim a6 |= 4; // so brighten it a6 &= ~3; // and dim the other two buttons T6graphPanel(); // display the graph of nodes per degree of bearing } break; case 3: if(a6 & 8) { // if the NET1 button is bright a6 &= ~8; // dim it h6 = 0; // set to fractal network No 0 } else { // else it is dim a6 |= 8; // so brighten it a6 &= ~112; // and dim the other NET buttons h6 = 1; // selected button number } a6 &= ~7; // kill NODES, BUILD, ACTIV buttons T6show(); break; case 4: if(a6 & 16) { // if the NET2 button is bright a6 &= ~16; // dim it h6 = 0; // set to fractal network No 0 } else { // else it is dim a6 |= 16; // so brighten it a6 &= ~104; // and dim the other NET buttons h6 = 2; // selected button number } a6 &= ~7; // kill NODES, BUILD, ACTIV buttons T6show(); break; case 5: if(a6 & 32) { // if the NET3 button is bright a6 &= ~32; // dim it h6 = 0; // set to fractal network No 0 } else { // else it is dim a6 |= 32; // so brighten it a6 &= ~88; // and dim the other NET buttons h6 = 3; // selected button number } a6 &= ~7; // kill NODES, BUILD, ACTIV buttons T6show(); break; case 6: if(a6 & 64) { // if the NET4 button is bright a6 &= ~64; // dim it h6 = 0; // set to fractal network No 0 } else { // else it is dim a6 |= 64; // so brighten it a6 &= ~56; // and dim the other NET buttons h6 = 4; // selected button number } a6 &= ~7; // kill NODES, BUILD, ACTIV buttons T6show(); break; case 7: // HELP button was clicked helpPage("radio/com.html#nets"); } T6Buts(); // re-display the buttons } /* HANDLES MOUSE WHEEL ROTATIONS THAT OCCUR IN TAB 6 Called from one place each in MW4() and MW5(). */ void T6scroll() { if(tb == 6) { // if we are in Tab 6 if(a6 & 1) // and the NODES button is lit T6nodeScroll(); // scroll the nodes list else if(a6 & 4 && b6 & 32) // if GRAPH and LIST buttons are bright T6listScroll(); // scroll the 'nodes on selected bearing' list } } // FUNCTIONS PERTAINING TO TAB 5: LINK---------------------------------------- /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF LINK TAB. Called from 1 place in T5show() & T5butClick() and 4 places in T5LSS() */ void T5Buts() { if(tb != 5) return; // bail out if LINK tab not currently visible showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate the horizontal row of control buttons *A[] = {"CONNECT","RESET","LIVE","SIMUL","LISTEN","LOCK","MODU","HELP"}; const int a[] = {6,12,15,12,9,15,15,15}, // horizontal off-sets for word starts b[] = {7, 5, 4, 5,6, 4, 4, 4}; // number of letters in each word int h = 17; // x coordinate of button block for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(h5 & 1 << i) // if this button should be bright XSetForeground(XD,XG,WHITE); // set colour for bright lettering else XSetForeground(XD,XG,GREY); // colour for dull lettering XDrawString(XD,XW,XG,h+a[i],377,A[i],b[i]); h += 59; // move across to the next button } } void T5showVertButs() { // DISPLAY THE VERTICAL COLUMN OF BUTTONS char // annotate the vertical row of control buttons *A[] = {"HAMS","FNET","MOON","SPARE","SPARE","SPARE","SPARE"}; int a[] = {15,15,15,12,12,12,12}, // horizontal offsets for word starts b[] = { 4, 4, 4, 5, 5, 5, 5}, // number of letters in each word h = 150; // x coordinate of button block for(int i = 0; i < 7; i++) { // for each of the 8 buttons: XSetForeground(XD,XG,DARK); // shade for buttons' background XFillRectangle(XD,XW,XG,175,h-15,53,21); if(v5 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else // otherwise XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,175+a[i],h,A[i],b[i]); h += 27; // move across to the next button } } /* DISPLAY MESSAGE REGARDING INTERNAL SIMULATION OF RECEIVER. Called from 1 place T5show() and 2 places in T5butClick(). */ void T5simu() { int v = 63 + 40; XSetForeground(XD,XG,DARK); // clear the graph area XFillRectangle(XD,XW,XG,17,v-19,137,70); XSetForeground(XD,XG,YELLOW); // display the fracnet number and node number XDrawString(XD,XW,XG,189,v,"LINK",4); XDrawString(XD,XW,XG,192,v+20,"VIA",3); // lables, again in yellow XDrawString(XD,XW,XG, 23,v+20,"LOCATION",8); // lables, again in yellow XDrawString(XD,XW,XG, 23,v+40,"STATUS",6); char S[21] = "FRACNET NODE "; if(v5 & 1) // if the HAMS button is selected XDrawString(XD,XW,XG,23,v,"CALL SIGN: PY4BUY",17); else if(v5 & 2) { // if the FNET button is selected *(S + 8) = 48 + h6; *(S + 16) = 48 + j6; XDrawString(XD,XW,XG,23,v,S,17); } else if(v5 & 4) { // if the MOON button is selected XDrawString(XD,XW,XG,23,v,"DN FREQ: kHz",21); XDrawString(XD,XW,XG,77,v,showInt(f7,8,0),8); // show frequency } int // FracNet's node index [25 nodes: 5 fracnets of 5 nodes each] j = (h6 << 2) + h6 + j6, // j = h6 * 5 + j6 k = 0; // character number within name char c; // to hold each character in turn // DISPLAY THE NODES NAME/LOCATION FILE *F = fopen("nodes.dat","r"); // open for read only /* holds the reference numbers of the 25 fracnet nodes | index number of this node within d6[] | | times 64 [64 bytes per record to get start byte of record] | | | name field starts at the 40th byte of the record | | | | */ fseek(F,(d6[j] << 6) + 40,SEEK_SET); // set to start byte of node's name for(k = 0; k < 20; k++) { // for each character of the name/location if((c = fgetc(F)) == 0) // if it is NULL break; // break out of loop S[k] = c; // otherwise add it to the character array } XSetForeground(XD,XG,BLEEN); // colour for name/location XDrawString(XD,XW,XG,77,v+20,S,k); // display the node's location name fclose(F); char *U[2] = {"INACTIVE ","CONNECTED"}; // status words int i = 0; // set word index to '0' INACTIVE if(v5 & 1 && m5 & 1 // if HAMS selected & DATA CHANNEL ACTIVE || v5 & 2 && (k6 & 1 << j) // or FNET selected and NODE is selected || v5 & 4 && m5 & 4) { // or MOON selected & DATA CHANNEL ACTIVE i = 1; // set the CONNECTED flag XSetForeground(XD,XG,GREEN); // and show indication in GREEN } XDrawString(XD,XW,XG,77,v+40,U[i],9); // display the status word } /* DISPLAY THE STATUS ANNOTATIONS: You can highlight more than one annotation at a time by adding their binary patterns; e.g. to highlight "Receiver on Frequency" and "Receiver On and Ready" at the same time, set x = 16 + 32 = 48. Called from 2 places in T5show() and 6 places in T5LSS(). */ void T5status() { int v = 208, // y-coord of the top line of the status annotations h = 23; // x-coord of the start of the annotation lettering char *S[] = { "DATA CHANNEL ACTIVE ", // m5 = 000001 1 "Bit Frame Stabilised ", // m5 = 000010 2 "Parser Signal Synched", // m5 = 000100 4 "Far Carrier Acquired ", // m5 = 001000 8 "Receiver on Frequency", // m5 = 010000 16 "Receiver On and Ready" // m5 = 100000 32 }; XSetForeground(XD,XG,DARK); // clear the graph area XFillRectangle(XD,XW,XG,17,v-37,137,148); XSetForeground(XD,XG,YELLOW); // LINK STATUS annotation XDrawString(XD,XW,XG,h,v-20,"LINK SEQUENCER",14); int j = (h6 << 2) + h6 + j6; // position of node status bit h6*5+j6 for(int i = 0; i < 6; i++) { // for each of the 6 status annotations int y; // display colour if(m5 & 1) { // if 'DATA CHANNEL ACTIVE' then: if(i == 0) { // if doing 'DATA CHANNEL ACTIVE' annotation k6 |= 1 << j; // set link status to CONNECTED // STORE THE NEW 'CONNECTED' STATUS TO THE FRACNETS FILE FILE *F = fopen("fracnets.dat","r+"); // open for modifying fseek(F,100,SEEK_SET); // start byte of 32-bit status field putInt(k6,F); // save the fracnet status settings fclose(F); // close the 'fracnets.dat' file T5simu(); // to re-display the node status y = GREEN; // letter it in bright green h5 &= ~1; // dim the CONNECT button T5Buts(); // re-display the buttons } else // otherwise [if doing any other status annotation] y = GRN; // letter it in dull green } else { // Else [if DATA CHANNEL not yet ACTIVE] then: if(m5 & 1 << i) // if the current status has now been achieved y = GREEN; // display its annotation in bright green else y = GREY; // else display it in grey } XSetForeground(XD,XG,y); // set appropriate colour for annotation XDrawString(XD,XW,XG,h,v,*(S+i),21); // display the annotation v += 20; // drop to the next line } showMsg(0,GREEN); // clear the message panel } /* CLEAR THE LISSAJOUS GRAPH AND RE-DRAW THE AXES. Called from 1 place in T5show(), 4 places in T5LSS(), and 5 places in T5butClick(). */ void T5clearGraph() { XSetForeground(XD,XG,0x000000); // clear the graph area XFillRectangle(XD,XW,XG,360-100,200-100,200,200); XSetForeground(XD,XG,0xFFFFFF); // annotate the x & y axes XDrawLine(XD,XW,XG,260,200,460,200); XDrawLine(XD,XW,XG,360,100,360,300); XDrawString(XD,XW,XG,358,95,"Y",1); XDrawString(XD,XW,XG,465,205,"X",1); } /* CLEAR THE PHASE DIFFERENCE DISPLAY. Called from 1 place in T5LF() and 3 places in T5butClick(). */ void T5clearAngle() { XSetForeground(XD,XG, 0x000000); XFillRectangle(XD,XW,XG,401,51,24,20); } /* GENERATE A CLEAR CIRCULAR OR QUADRATURE MODULATED SIGNAL TRACE. Called from 1 place each in T5LF() and T5LSS(). */ void T5QuadMod() { if(tb != 5) return; // only display if Tab 5 selected static int x1,y1,x2,y2,pm,pn; if(++e5 > 179) // When, on advancing the phase angle e5, it e5 = 0; // overshoots 90 degrees, set it back to zero. int P = 180 - e5; // create the counter angle P for cosine computation if(P < 0) // if it undershoots zero, P += 180; // shift it round via the top of the quadrant int x = a5[e5]; // new x-coord is sine of the current phase angle int y = a5[P]; // new y-coord is cosine of the current phase angle int m = 7, // radius of high signal for 1st & 3rd quadrants n = 7; // radius of low signal for 2nd & 4th quadrants if(i5 > 7 || h5 & 64) { // if quadrature modulation is in effect n = 8; if(e5 < 45 || (e5 > 90 && e5 < 135)) { m = 8; // radius of low signal for 1st & 3rd quadrants n = 7; // radius of high signal for 2nd & 4th quadrants } } /* if either signal level has changed since the previous pass | | */ if(m != pm || n != pn) { f5 = 1; // set the 'signal level changed' flag pm = m; // save this time's odd quadrant signal level pn = n; // save this time's even quadrant signal level } XSetForeground(XD, XG, 0x00FF00); // plot new points in green int xn = x >> m, // create new x-coord for 1st & 3rd quadrants yn = y >> m; // create new y-coord for 2nd & 4th quadrants if(e5 > 1 && e5 < 178) { // to suppress fly-back trace XDrawPoint(XD,XW,XG,360+xn,200+yn); // draw plot in 1st quadrant XDrawPoint(XD,XW,XG,360-xn,200-yn); // draw plot in 3rd quadrant if(f5) { // if signal level has changed since previous pass XDrawLine(XD,XW,XG,360+x1,200+y1,360+xn,200+yn); XDrawLine(XD,XW,XG,360-x1,200-y1,360-xn,200-yn); } if(i5 == 9) { // if the data channel is now open XDrawPoint(XD,XW,XG,360-xn,200+yn); // draw plot in 2nd quadrant XDrawPoint(XD,XW,XG,360+xn,200-yn); // draw plot in 4th quadrant } } x1 = xn; // save this time's 1st & 3rd quadrant x-plot y1 = yn; // save this time's 1st & 3rd quadrant y-plot xn = x >> n; // create new x-coord for 2nd & 4th quadrants yn = y >> n; // create new y-coord for 2nd & 4th quadrants if(e5 > 1 && e5 < 178) { // to suppress fly-back trace XDrawPoint(XD,XW,XG,360-xn,200+yn); XDrawPoint(XD,XW,XG,360+xn,200-yn); if(f5) { // draw line from last time's x,y to this time's x,y XDrawLine(XD,XW,XG,360-x2,200+y2,360-xn,200+yn); XDrawLine(XD,XW,XG,360+x2,200-y2,360+xn,200-yn); f5 = 0; // reset the 'signal level changed' flag } if(i5 == 9) { // if the data channel is now open XDrawPoint(XD,XW,XG,360+xn,200+yn); // draw plot in 1st quadrant XDrawPoint(XD,XW,XG,360-xn,200-yn); // draw plot in 3rd quadrant } } x2 = xn; // save this time's 2nd & 4th quadrant x-plot y2 = yn; // save this time's 2nd & 4th quadrant y-plot } /* Reset the x and y coordinate data sets for both the painter trace and the wiper trace then wipe the plotting area and re-draw the axes. Called from 1 place in showTab() and 2 places each in T5LF() & T5butClick(). */ void T5show() { T6getNodes(); // in case the nodes data has not yet been loaded for(int i = 0; i < 4; i++) { b5[i][0] = 0; // main phase angle 'a' b5[i][1] = 1; // main phase angle incrementer 'd' b5[i][2] = 0; // clear the quadrant counter b5[i][3] = 1; // output sign 'F' 1 = '+' 0 = '-' b5[i][4] = 0; // clear the wiper start trigger 'f' } T5clearGraph(); // clear the graph area and redisplay the annotated axes XSetForeground(XD,XG,BLEY); // display the parser phase offset angle XDrawLine(XD,XW,XG,260,200,460,200); XDrawLine(XD,XW,XG,360,100,360,300); XDrawString(XD,XW,XG,17,63, "Phase difference between remote carrier & local parser signals: ",67); XSetForeground(XD,XG,YELLOW); // Display MESSAGE annotation in yellow XDrawString(XD,XW,XG,17,345,"MESSAGE",7); c5 = 4; // number of quadrants to update // RE-DISPLAY THE DIGITAL PHASE SIGNAL IF NODE ALREADY CONNECTED int j = (h6 << 2) + h6 + j6; // position of node status bit if(k6 & 1 << j) { // if current node is connected e5 = 0; // set signal phase angle to zero i5 = 9; // set sequencer phase = DATA CHANNEL ACTIVE for(int i = 0; i < 180; i++) // for each of 180 degrees T5QuadMod(); // call the quadrature modulation plotter m5 = 1; // set DATA CHANNEL ACTIVE XSetForeground(XD,XG,YELLOW); // display the locked phase difference XDrawString(XD,XW,XG,401,63,showInt(90,3,1),3); } else m5 = 0; // clear LINK SEQUENCER phases T5simu(); // set the SIMUL button [brighten it's annotation] T5Buts(); // redisplay the states of the Tab 6 control buttons T5status(); // display the status annotations T5showVertButs(); // display the vertical buttons } /* LISSAJOUS PLOT FUNCTION. Returns the x or y value for a point on either the tracer or the wiper according to the supplied value of 'i' [0,1,2,3]. It uses the SINE table to calculate an interpolated value of the sine of the phase angle of the test signal. The arguement values for each value of 'i' are preserved in the array b5[][]. Note: F and f are integers used as Booleans. The value 2164 is used to trigger the first cycle. Called from 4 places in T5LF(). */ int T5LP(int i) { // i=plot number 0:x-plot, 1:y-plot, 2:x-wipe, 3:y-wipe int a = b5[i][0], // main phase angle d = b5[i][1], // main phase angle drift rate Q = b5[i][2], // quadrant counter F = b5[i][3], // output sign 'F' 1 = '+' 0 = '-' f = b5[i][4]; // trigger start of wiper int p = 1; // phase angle drift rate if(i == 1 || i == 3) // if doing an x-plot p = 0; // there is no phase drift int x = a5[a] >> 7; // get x or y coord corresponding to given angle if(F == 0) x = -x; // reverse sign according to quadrant a += d; // advance the main phase angle if (a < 0) { // if advanced phase angle is below bottom of table a = -a; // wrap back up the table a += p; // add this quadrant's phase increment d = 1; // set for main angle to move up the table Q++; // increment the quadrant number if(F == 0) // reverse F for the next 2 quadrants F = 1; else F = 0; } else if (a > 180) { // if advanced phase angle is above top of table a = 360 - a; // wrap back down the table a -= p; // subtract this quadrant's phase increment d = -1; // set 'd' to move the angle DOWN the table Q++; // increment the quadrant counter [Q] } /* if the wiper start flag is not set | and more than 3 quadrants have been completed | | then set the wiper start flag | | | */ if(f == 0 && Q > 3) f = 1; b5[i][0] = a; // save this plot's new phase angle value b5[i][1] = d; // save this plot's new phase angle drift rate b5[i][2] = Q; // save this plot's new quadrant count b5[i][3] = F; // save this plot's new output sign flag b5[i][4] = f; // save this plot's new wiper start flag return(x); // return the new x or y co-ordinate. } /* LISSAJOUS FUNCTION. While the current main cycle is not yet finished, get the new x and y plots and paint the next bit of line in green. Called from 1 place in T5LSS(). */ void T5LF() { static int p = 0; // counts half-degrees round the ellipse if(++p > 719) { // if completed a full ellipse p = 0; // reset the half-degrees counter g5++; // increment phase drift amount by half a degree } /* If the LOCK button is bright and | the phase difference between carrier and parser signals | | is multiple of 90 degrees: | | | */ if(h5 & 32 && (g5 + 45) % 90 == 0) { d5 = 1; // set the 'locked' flag e5 = 0; // zero the main phase angle g5 = 45; // set phase difference to 45 degrees T5show(); // clear the graph area T5QuadMod(); // do the first cycle of the locked signal } else { int Q = b5[0][2]; // quadrant number if(Q < c5) { // if the current 4 quadrants not yet completed int x = T5LP(0), // get the painter's next x & y plots y = T5LP(1); XSetForeground(XD,XG,0x00FF00); // set trace colour to green XDrawPoint(XD,XW,XG,360+x,200-y); // display next plot if(b5[0][4] == 1) { // If painter has completed > 3 quadrants of x = T5LP(2); // 1st cycle [ie wiper flag 'f' has been set] y = T5LP(3); // get the wiper's next x and y coordinates XSetForeground(XD,XG,0x000000); // set wiper colour to black XDrawPoint(XD,XW,XG,360+x,200-y); // wipe the next plot XSetForeground(XD,XG,0xFFFFFF); // and re-draw the axes XDrawLine(XD,XW,XG,260,200,460,200); XDrawLine(XD,XW,XG,360,100,360,300); } } c5 = Q + 4; // set 'c5' to 4 quadrants ahead if(Q > 1073741824) T5show(); // safety net } T5clearAngle(); // clear the 3-digit phase difference display XSetForeground(XD,XG,YELLOW); // display the updated phase difference /* convert integer to string | the Lissajous phase angle | | multiplied by 2 | | | three digits long | | | | with leading zeros | | | | | display 3-character string | | | | | | */ XDrawString(XD,XW,XG,401,63,showInt(g5 << 1,3,1),3); } /* LINK START-UP SEQUENCER. Manages the sequence of processes involved in switching on a transceiver to establish an active duplex digital channel. Called only from 1 place in main()'s timer loop. */ void T5LSS() { if(i5 == 0 || i5 == 10) return; // Link Connect Sequencer inactive static int T[9] = { // timers: one for each stage of the linking process // TIMES EXPRESSED IN SECONDS 0, // not used at the moment 300, // 15 seconds for receiver warm-up 140, // 7 seconds for receiver to tune to the required frequency 140, // 7 seconds for receiver to acquire the remote carrier // TIMES EXPRESSED AS MULTIPLES OF 100 MICROSECONDS 80000, // 8 secs for parser signal to free-wheel with remote carrier 0, // wait for parser signal to phase-lock with remote carrier 70000, // 7 seconds time for the test frame to synchronise and lock 40000, // 4 seconds for data channel handshaking to take place 40000 // 4 seconds for sequencer control loop to release }; if(l5 == 0) return; // cannot operate in LIVE mode in this version if(j5 > 0 || k5) { // if the timer is still running if(i5 > 4) { // carrier is active in stages 5, 6, 7, 8, 9 if(d5) // if phase-locked on to received carrier T5QuadMod(); // use the quadrature modulation function else // else, if carrier is still free-wheeling T5LF(); // use the lissajous figure function } /* if waiting for the phase-lock to occur | and the lock flag becomes set | | */ if(i5 == 6 && d5) k5 = 0; // set for timer-only delays if(!k5) // if not in the wait state [stage 6] j5--; // decrement timer counter } else { // otherwise: switch(i5) { // switch according to Tab 5 Sequencer Status case 1: // RECEIVER IS CURRENTLY OFF OR DISCONNECTED if(h5 & 1) { // if the START button has just been pressed showMsg(1,YELLOW); // MESSAGE: "01 Powering up receiver." j5 = T[1]; // set delay: units of 120 microseconds i5 = 2; // set Tab 5 sequencer status to 2 k5 = 0; // clear the WAIT flag } break; // now wait for the receiver to boot up case 2: // RECEIVER IS NOW POWERED ON AND READY m5 = 32; // STATUS: "Receiver on and Ready" T5status(); // re-display the Link Status panel showMsg(2,YELLOW); // MESSAGE: "02 Tuning to required frequency." j5 = T[2]; // set delay: units of 120 microseconds i5 = 3; // initiate delay while tuning takes place break; // wait for the tuning process to finish case 3: // RECEIVER HAS NOW TUNED TO REQUIRED FREQUENCY m5 = 48; // STATUS: "Receiver on Frequency" T5status(); // re-display the Link Status panel showMsg(3,YELLOW); // MESSAGE: "03 Trying to acquire remote carrier." j5 = T[3]; // set delay: units of 120 microseconds i5 = 4; // initiate the carrier acquisition process break; // wait for receiver to acquire the carrier case 4: // RECEIVER HAS HOW ACQUIRED THE REMOTE CARRIER m5 = 56; // STATUS: "Far Carrier Acquired" T5status(); // re-display the Link Status panel showMsg(4,YELLOW); // MESSAGE: "04 Trying to phase-lock parser...." h5 |= 16; // brighten the LISTENING button T5Buts(); // re-display the control buttons i5 = 5; // initiate the free-wheeling carrier stage T5clearGraph(); // clear the graph area and repaint the axes j5 = T[4]; // set delay: units of 120 microseconds e5 = 0; // reset the carrier frequency angle [omega * t] g5 = 0; // phase difference angle between carrier & parser break; // wait for free-wheeling stage to finish case 5: // RECEIVER NOW READY TO LOCK PARSER ONTO CARRIER h5 |= 32; // set the 'lock request' flag-bit ['LOCK capture' active] T5Buts(); // re-display the control buttons [with LOCK button bright] i5 = 6; // initiate the 'locking on' process d5 = 0; // set flag to indicate "not yet locked on" k5 = 1; // no time-out: just wait for 'locked = 1' to occur break; // wait for the 'locked' flag to become set to '1' case 6: // PARSER HAS NOW LOCKED ON TO CARRIER SIGNAL m5 = 60; // STATUS: "Parser Signal Synched" T5status(); // re-display the Link Status panel showMsg(5,YELLOW); // "05 Trying to stabilise 16-bit test frame." j5 = T[6]; // set delay: units of 50 microseconds i5 = 7; // initiate the quadrature frame test process T5clearGraph(); // clear the graph area and repaint the axes e5 = 0; // reset the carrier frequency angle [omega * t] g5 = 0; // phase difference angle between carrier & parser break; // wait for the frame test process to complete case 7: // THE TEST FRAME HAS NOW STABILISED m5 = 62; // add "Bit Frame Stabilised" T5status(); // re-display the Link Status panel showMsg(6,YELLOW); // "06 Expediting data channel handshaking." h5 |= 64; // brighten the MODU button T5Buts(); // re-display Tab 5's control buttons T5clearGraph(); // clear the graph area and repaint the axes j5 = T[7]; // set delay: units of 50 microseconds i5 = 8; // initiate the data channel handshaking process e5 = 0; // reset the carrier frequency angle [omega * t] g5 = 0; // phase difference angle between carrier & parser break; // wait for the data channel hanshaking to finish case 8: // THE DATA CHANNEL IS NOW OPEN m5 = 63; // STATUS: "DATA CHANNEL ACTIVE" T5status(); // re-display the Link Status panel showMsg(7,YELLOW); // "07 Receive channel open for incoming data." h5 &= ~64; // dim the MODU button T5Buts(); // re-display Tab 5's control buttons T5clearGraph(); // clear the graph area and repaint the axes j5 = T[8]; // set delay: units of 50 microseconds i5 = 9; // set Tab 6 sequencer status to 9 k5 = 0; // set the WAIT flag break; // wait for the data graph to complete case 9: // THE DATA CHANNEL IS NOW OPEN i5 = 10; // set Tab 5 sequencer status to 10 // reduces CPU load after completion } // end of 'switch' } // end of 'else' } // RESET THE CURRENT CONNECTION. Called from one place in T5butClick(). void T5reset() { k5 = 0; // clear the WAIT flag i5 = 0; // set Tab 5 sequencer status to 0 j5 = 0; // clear the timer d5 = 0; // clear the 'locked' flag m5 = 0; // set to connection status 0 /* shift the '1' left a number of bit positions equal to: | 5 times the fracnet number = h6*5 | | plus the node number within the fracnet | | | */ k6 &= ~(1 << ((h6 << 2) + h6 + j6)); // set link INACTIVE FILE *F = fopen("fracnets.dat","r+"); // open for modifying fseek(F,100,SEEK_SET); // start byte of 32-bit status field putInt(k6,F); // save the new fracnet status setting fclose(F); // close the 'fracnets.dat' file T5show(); // redisplay the Tab 5 content T5clearAngle(); // clear the 3-digit phase difference display T5clearGraph(); } /* HANDLES CLICKS TO THE VERTICAL BUTTONS. Called from only one place in MEH(). */ void T5click() { // click occurred within vertical button area if(mx < 175 && mx > 227) return; // click outside horiz limits of buttons if(my < 135 && my > 317) return; // click outside vert limits of buttons int y = 134, i; // height of top of first button - 1 for(i = 0; i < 7; i++) { // for each of the 8 buttons if(my > y && my < y + 22) // if click was within the 'i' th button break; // break loop: 'i' is button number y += 27; // otherwise move down to next button } switch(i) { case 0: if(!(v5 & 1)) { // if the HAMS button is off v5 |= 1; // switch on HAMS button v5 &= ~62; // switch off all other HAMS buttons } break; case 1: if(!(v5 & 2)) { // if the FNET button is off v5 |= 2; // switch it on v5 &= ~61; // switch off all other FNET buttons } break; case 2: if(!(v5 & 4)) { // if the MOON button is off v5 |= 4; // switch it on v5 &= ~59; // switch off all other MOON buttons } break; } T5reset(); // reset the LINK T5showVertButs(); // re-display the states of the vertical buttons } /* THE LINK TAB'S CONTROL BUTTONS. h 5= Tab 5 Button States. The logic asso- ciated with each control button is, for the most part, unique. There is little commonality in the functionality invoked by each respective button and its interactivity with the other buttons. For this reason, it is better - if not essential - to leave the logic for each button entirely separate: not compacted, tabulated or looped. The specific behaviour of each button can thereby be changed independently without having repercussions in the logic of the other buttons. Called from only one place in MEH(). */ void T5butClick(int x) { switch(x) { // switch according to button number case 0: // CONNECT button clicked /* if the CONNECT button is dim [lowest bit in h5 is not set] | AND HAMS selected & DATA CHANNEL ACTIVE | | | AND selected ham's frequency is non-zero | | | | | OR FNET is selected | | | | | | | */ if(!(h5 & 1) && (v5 & 1 && d2 > 0 || v5 & 2 || v5 & 4 && f7 > 0)) { /* | | | | OR MOON is selected | | AND lunar down-channel frequency is non-zero */ h5 |= 1; // brighten it h5 &= ~114; // dim RESET, LISTEN, LOCK, MODU = 114 i5 = 1; // set Tab 5 sequencer status to 1 k5 = 0; // clear the WAIT flag j5 = 0; // clear the timer d5 = 0; // clear the locked flag T5show(); // redisplay the Tab 6 content T5clearAngle(); // clear the 3-digit phase difference display } break; case 1: // RESET button clicked if(!(h5 & 2)) { // if the RESET button is dim h5 |= 2; // brighten the RESET button h5 &= ~113; // dim START, LISTEN, LOCK, MODU = 114 T5reset(); // reset the connection h5 &= ~2; // dim the RESET button } break; case 2: // LIVE button clicked if(!(h5 & 4)) { // if LIVE button is dim h5 |= 4; // brighten it h5 &= ~8; // dim the SIMUL button l5 = 0; // live operation not implemented at the moment T5simu(); // display message } break; case 3: // SIMU BUTTON CLICKED if(!(h5 & 8)) { // if SIMUL button is dim h5 |= 8; // brighten it h5 &= ~4; // dim the LIVE buttom l5 = 1; // simulation mode implemented OK T5simu(); // display message } break; case 4: // LISTEN button if(!(h5 & 1)) { // provided CONNECT button not on if(h5 & 16) { // if the LISTEN button is bright h5 &= ~16; // dim it h5 &= ~32; // dim the LOCK button also h5 &= ~64; // dim the MODU button also d5 = 0; // unset the 'locked' flag k5 = 0; // clear the WAIT flag i5 = 0; // stop T5clearGraph(); // clear the graph area and repaint the axes T5clearAngle(); // clear the 3-digit phase difference display } else { // else h5 |= 16; // brighten the LISTEN button h5 &= ~2; // dim the RESET button also i5 = 5; // initiate the free-wheeling carrier stage T5clearGraph(); // clear the graph area and repaint the axes k5 = 1; // set the event WAIT flag e5 = 0; // reset carrier phase angle g5 = 0; // reset the phase difference } } break; case 5: // LOCK button clicked if(!(h5 & 1)) { // provided CONNECT button not on if(h5 & 32) { // if the LOCK button is bright h5 &= ~32; // dim it h5 &= ~64; // dim the MODU button also d5 = 0; // unset the 'locked' flag e5 = 0; // reset carrier phase angle g5 = 0; // reset the phase difference T5clearGraph(); // clear the graph area and repaint the axes } else if(h5 & 16) // else, provided the LISTEN button is bright h5 |= 32; // brighten the LOCK button } break; case 6: // MOLUlate button if(!(h5 & 1)) { // provided CONNECT button not on if(h5 & 64) { // if the MODU button is bright h5 &= ~64; // dim it T5clearGraph(); // clear the graph area and repaint the axes e5 = 0; // reset carrier phase angle g5 = 0; // reset the phase difference } else /* provided the LISTEN and LOCK buttons are bright | and the parser has locked phase with the carrier | | */ if(h5 & 48 && d5) { h5 |= 64; // brighten the MODU button. T5clearGraph(); // clear the graph area and repaint the axes e5 = 0; // reset carrier phase angle g5 = 0; // reset the phase difference } } break; case 7: helpPage("radio/com.html#link"); } // end of 'switch' T5Buts(); // re-display the button states } // FUNCTIONS PERTAINING TO TAB 3: O/I ---------------------------------------- /* DISPLAYS INPUT/OUTPUT INFORMATION LINES FROM THE RECEIVER IN THE TAB 3 WINDOW AREA. Called from 1 place each in T3update() and T3show(). */ void T3showInput() { if(tb != 3) return; XSetForeground(XD,XG,BLACK); // clear the lines panel area XFillRectangle(XD,XW,XG,17,72,230,276); // NOTE: values of b3 start at 1, not 0. int v = 86, // set y-coord of first line of output Q = b3 - 15, // line number bias for shunted lines M = 15; // default number of lines to display if(b3 < 16) { // if the panel's display area is not yet full M = b3 - 1; // loop only as far as the penultimate command line Q = 1; // line number is simply the panel's line index } // Display Lines 0 to 14: XSetForeground(XD,XG,BLEEN); // colour for past commands: grey for(int i = 0; i < M; i++) { // for each visible past command line XDrawString(XD,XW,XG,18,v,showInt(Q+i,4,1),4); // show line number XDrawString(XD,XW,XG,48,v,d3[i],strlen(d3[i])); // show command v += 17; // drop down to the next line } // Display Line 15 XSetForeground(XD,XG,WHITE); // colour for most recent command: white XDrawString(XD,XW,XG,18,v,showInt(b3,4,1),4); // show line number XDrawString(XD,XW,XG,48,v,d3[M],strlen(d3[M])); // show command } /* DISPLAYS THE OUTPUT OF RECEIVER COMMAND LINES IN THE TAB 3 WINDOW AREA. Called from 1 place each in T3update() and T3show(). */ void T3showOutput() { if(tb != 3) return; XSetForeground(XD,XG,BLACK); // clear the output panel area XFillRectangle(XD,XW,XG,250,72,230,276); // NOTE: values of c3 start at 1, not 0. int v = 86, // set y-coord of first line of output Q = c3 - 15, // line number bias for shunted lines M = 15; // default number of lines to display if(c3 < 16) { // if the panel's display area is not yet full M = c3 - 1; // loop only as far as the penultimate command line Q = 1; // line number is simply the panel's line index } // Display Lines 0 to 14: XSetForeground(XD,XG,BLEEN); // colour for past commands: grey for(int i = 0; i < M; i++) { // for each visible past command line XDrawString(XD,XW,XG,254,v,showInt(Q+i,4,1),4); // show line number XDrawString(XD,XW,XG,284,v,e3[i],strlen(e3[i])); // show command v += 17; // drop down to the next line } // Display Line 15 XSetForeground(XD,XG,WHITE); // colour for most recent command: white XDrawString(XD,XW,XG,254,v,showInt(c3,4,1),4); // show line number XDrawString(XD,XW,XG,284,v,e3[M],strlen(e3[M])); // show command } /* DISPLAYS THE INPUTS OF SIGNAL STRENGTH FROM RECEIVER. Starts filling the panel from the top. Once the panel is full, shunts all lines upwards one place to make room for each new INPUT line as it arrives. Called only from 1 place each in T1nextFreq() and T0click(). switch variable [see switch statement below] | start byte of current frequency [if applicable] | | */ void T3updtInput(int sw, int f) { if(sw < 0 || sw > 4) return; if(++b3 > 9999) // If incremented line count exceeds 5 digits b3 = 1; // reset it to 1 as a safety precaution. int M = b3 - 1; // when screen not yet full /* If more than 16 commands have been posted, lose content of Line 0 and shunt the content of Lines 1 to 15 up by one line within the array e3[][]. Line 15 will thus become vacated ready for the newly posted command. */ if(b3 > 16) { M = 15; for(int i = 0; i < M; i++) for(int j = 0; j < 8; j++) d3[i][j] = d3[i+1][j]; } // INSERT THE NEWLY POSTED MRSSAGE IN LINE 15 OF THE ARRAY d3[][]. switch(sw) { // switch on message type number case 0: fseek(sf,f,SEEK_SET); // seek start byte of current frequency sprintf(d3[M],"Sig %s",showInt(fgetc(sf),3,1)); break; /* These messages are dummy samples. The in a practical implementation, the real messages from the remote node will be piped from the transcei- ver's interface driver via 'stdin'. */ case 1: sprintf(d3[M],"Ack %s %s",f3,g3); break; // PING ACKNOWLEDGED case 2: sprintf(d3[M],"OK %s %s",f3,g3); break; // XFER REQUEST GRANTED case 3: sprintf(d3[M],"Alo %s %s",f3,g3); break; // PORTS ALLOCATED case 4: sprintf(d3[M],"Bye %s %s",f3,g3); // GOODBYE } T3showInput(); // display the lines on screen /* if the STDIN button is bright [Bit 2 is set] | display the input command in the terminal window | | output a string variable | | | | put a 'new-line' [carriage-return] at the end | | | | | display 4-byte integer as char-string | | | | | | line [serial] number of command | | | | | | | display in a 5-digit format | | | | | | | | with leading zeros | | | | | | | | | the Mth command string | | | | | | | | | | */ if(a3 & 4) printf("%s %s\n",showInt(b3,5,1),d3[M]); } // CLEAR THE I/O MESSAGES FROM THE SCREEN AND ZERO THE MESSAGES ARRAY void T3clear() { XSetForeground(XD,XG,BLACK); // clear the lines panel area XFillRectangle(XD,XW,XG,17,72,466,276); for(int i = 0; i < 15; i++) // fill messages array with NULL characters for(int j = 0; j < 8; j++) d3[i][j] = 0; b3 = 0; // clear the input line number c3 = 0; // clear the output line number } /* COMPUTES THE ACTUAL COMMAND OUTPUT FOR THE TRANSCEIVER COMMAND FREQUENCY. Starts filling the panel from the top. Once the panel is full, shunts all lines upwards one place to make room for each new command line as it arrives. Called from 1 place each in T1nextFreq() & T0click() and from 2 places each in T1butClick() & T0butClick(). 0=GetFreq 1=Audio on; 2=Audio off etc. | start byte of current frequency | | */ void T3updtOutput(int sw, int f) { if(sw < 0 || sw > 6) return; if(++c3 > 9999) // If incremented line count exceeds 5 digits c3 = 1; // reset it to 1 as a safety precaution. int M = c3 - 1; // when screen not yet full /* If more than 16 commands have been posted, lose the content of Line 0 and shunt content of Lines 1 to 15 up by one line within the array e3[][]. Line 15 will thus become vacated ready for the newly posted command. */ if(c3 > 16) { M = 15; for(int i = 0; i < M; i++) for(int j = 0; j < 12; j++) e3[i][j] = e3[i+1][j]; } // INSERT THE NEWLY POSTED COMMAND IN LINE 15 OF THE ARRAY e3[][]. switch(sw) { // switch according to command type case 0: sprintf(e3[M],"GetFrq %s",showInt(f,9,1)); break; case 1: sprintf(e3[M],"Audio: OFF "); break; case 2: sprintf(e3[M],"Audio: ON "); break; /* network address of the currently selected destination node | */ case 3: sprintf(e3[M],"Ping %s %s",h3,i3); break; // ping case 4: sprintf(e3[M],"Requ %s %s",h3,i3); break; // request case 5: sprintf(e3[M],"Xfer %s %s",h3,i3); break; // transfer case 6: sprintf(e3[M],"Term %s %s",h3,i3); // terminate } T3showOutput(); // display the lines on screen /* If the STDOUT button is bright,output the command line to the terminal. Please see the corresponding code in function T3updtInput() above. */ if(a3 & 8) printf("%s %s\n",showInt(c3,5,1),e3[M]); } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE INPUT TAB. Called from 1 place each in T3showINPUT(), T3showOUTPUT(), T3showBOTH(), and T3butClick(). */ void T3Buts() { showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate the horizontal row of control buttons *A[] = {"SCAN","STOP","STDIN","STDOUT","CLEAR"," "," ","HELP"}; const int a[] = {15,15,12,9,12,15,15,15}, // horizontal offsets for word starts b[] = { 4, 4, 5,6, 5, 4, 4, 4}; // number of letters in each word int h = 17; // left edge of left-most button for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a3 & 1 << i) { // if the button is 'on' [bright] if(i ==2) // if this is the 'stdin' button XSetForeground(XD,XG,RED); // display it's lettering in red else // otherwise XSetForeground(XD,XG,WHITE); // display it's lettering in white } else XSetForeground(XD,XG,GREY); // default colour for the lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; // move across to the next button } } /* SETS UP THE HEADINGS AND BUTTONS FOR THE I/O TAB. Re-displays the current command lines for a SCAN that is still active. Called only from 1 place in T3show(). */ void T3show() { XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,17,52,467,303); XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,18,63,"LINE ---------RECIVED COMMANDS--------",38); XDrawString(XD,XW,XG,255,63,"LINE ----------COMMANDS SENT----------",38); if(b3 > 0) T3showInput(); if(c3 > 0) T3showOutput(); T3Buts(); // display the control buttons } /* ONE OF THE INPUT TAB'S CONTROL BUTTONS WAS CLICKED. Button numbers: 0:SCAN, 1:STOP, 2:STDIN, 7:HELP. a3 = Tab 3 Button States. Buttons are numbered 0 to 7 from left to right. 'y' has a '1' in the position in the lower 8 bits of b1 corresponding to the number of the clicked button as follows: 00000001 = 1 SCAN 00000010 = 2 STOP 00000100 = 4 STDIN etc. 10000000 = 128 HELP [Called from 1 place in MEH().] */ void T3butClick(int x) { switch(x) { // switch according to button number case 0: // SCAN button was clicked if(!(a3 & 1)) { // if the SCAN button is dim a3 |= 1; // brighten it a3 &= ~2; // dim the STOP button b1 |= 1; // brighten Tab 1's SCAN button b1 &= ~2; // dim Tab 1's STOP button if bright } break; // if SCAN button already bright, do nothing case 1: // STOP button was clicked if(!(a3 & 2)) { // if the STOP button is dim a3 |= 2; // brighten the STOP button a3 &= ~1; // dim the SCAN button if bright b1 |= 2; // brighten Tab 1's STOP button b1 &= ~1; // dim Tab 1's SCAN button if bright } break; // if STOP button already bright, do nothing case 2: // STDIN button was clicked if(a3 & 4) // if the STDIN button is bright a3 &= ~4; // dim it else // else a3 |= 4; // brighten it break; case 3: // STDOUT button was clicked if(a3 & 8) // if the STDOUT button is bright a3 &= ~8; // dim it else // otherwise a3 |= 8; // brighten it break; case 4: // CLEAR button was clicked 16 T3clear(); // clear all existing messages break; /* case 5: // LOG button was clicked 32 if(a3 & 32) // if the LOG button is bright a3 &= ~32; // dim it else // otherwise a3 |= 32; // brighten it break; case 6: // RECV button was clicked 64 if(a3 & 64) // if the XFER Tab's RECV button is bright a3 &= ~64; // dim it else // otherwise a3 |= 64; // brighten the I/O Tab's RECV button break; */ case 7: // HELP button was clicked helpPage("radio/com.html#io"); } T3Buts(); // re-display the buttons } // FUNCTIONS PERTAINING TO TAB 4: XFER --------------------------------------- /* DISPLAY THE TRANSFER PROTOCOL STATUS ANNOTATIONS: More than one annotation can be highlighted at the same time. Called from 1 place each in T4LSS(), T4XFR(), T4show(), T4butClick(). */ void T4status() { if(tb != 4) return; char *S[] = { // the 7 phases of the TRANSFER PROTOCOL "DESTINATION PINGED ", // PingA4345C61 out j4=0 "PING ACKNOWLEDGED ", // PackA782B9C4 in j4=1 "XFER REQUEST SENT ", // RequA782B9C4 out j4=2 "REQUEST GRANTED ", // RackA4345C61 in j4=3 "PORTS ALLOCATED ", // PallA4345C61 in j4=4 "TRANSFER INITIATED ", // XferA4345C61 out j4=5 "TRANSFER COMPLETED "}; // TermA782B9C4 out j4=6 XSetForeground(XD,XG,DARK); // clear the display area XFillRectangle(XD,XW,XG,17,180,131,142); XSetForeground(XD,XG,YELLOW); // draw the title in yellow int h = 17 + 8; XDrawString(XD,XW,XG,h,197,"-TRANSFER PROTOCOL-",19); int v = 313, // bottom line of the phase annotations y; // display colour for(int i = 0; i < 7; i++) { // for each of the 7 status phases if(i > j4) // if we are above the current phase y = GREY; // display the annotation in grey else if(i == j4) // if this is the current phase y = GREEN; // display annotation in bright green else // else we are below the current phase y = GRN; // so display in dull green XSetForeground(XD,XG,y); // set appropriate colour for annotation XDrawString(XD,XW,XG,h,v,*(S+i),19); // display the annotation v -= 16; // drop to the next line } } void T4IOmsg() { // MESSAGES SENT AND RECEIVED BY THE DATA TRANSFER SEQUENCER switch(j4) { // SWITCH ON SEQUENCER PHASE NUMBER 0 TO 6 case 0: T3updtOutput(3,0); break; // DESTINATION PINGED case 1: T3updtInput(1,0); break; // PING ACKNOWLEDGED case 2: T3updtOutput(4,0); break; // XFER REQUEST SENT case 3: T3updtInput(2,0); break; // XFER REQUEST GRANTED case 4: T3updtInput(3,0); break; // PORTS ALLOCATED case 5: T3updtOutput(5,0); break; // TRANSFER INITIATED case 6: T3updtOutput(6,0); break; // TRANSFER COMPLETED case 7: T3updtInput(4,0); // GOODBYE } } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE XFER TAB. Called from 1 place each in T4show() and T4butClick(). */ void T4Buts() { if(tb != 4) return; showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate the horizontal row of control buttons *A[] = {"DEST","VOICE","DATA","SEND","RECV","PAUSE","RESET","HELP"}; const int a[] = {15,12,15,15,15,12,12,15}, // horizontal offsets for word starts b[] = { 4, 5, 4, 4, 4, 5, 5, 4}; // number of letters in each word int h = 17, c; // left edge of left-most button for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a4 & 1 << i) { // if this button is 'on' if(i == 1) { // if this is the VOICE button if(j4 == 5) // if sequencer is at TRANSFER INITIATED c = GREEN; // make the VOICE button green else if(j4 == -1) // if the sequencer is inactive c = WHITE; // make the VOICE button white else c = YELLOW; // otherwise make it yellow } else c = WHITE; // any other 'on' button is white } else c = GREY; // if this button is off, make it grey XSetForeground(XD,XG,c); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; // move across to next button } } /* DISPLAY THE STATIC CONTENT OF THE NODES LIST. Called from only 1 place each in showTab(), MW4() & MW5(). */ void T4scroll() { if(tb != 4) return; // bail out if XFER tab not currently visible showMsg(0,BLACK); XSetForeground(XD,XG,BLACK); // clear the list area XFillRectangle(XD,XW,XG,196,74,287,278); /* if the mouse wheel was pushed downwards | number of 'nodes.idx' records before 1st visible one | | total number of records in 'nodes.idx' | | | number of visible lines on the screen | | | | scroll down the list | | | | | */ if(mw == 1 && h4 < c6 - 16) h4++; /* if the mouse wheel was pushed upwards | num of 'nodes.idx' records before 1st visible one | | first record in 'nodes.idx' | | | scroll up the list | | | | */ if(mw == 2 && h4 > 0) h4--; mw = 0; // indicates that the mouse wheel event has now been dealt with /* record number of destination node within 'nodes.idx' | number of 'nodes.idx' records before 1st visible one | | */ int x = f4 - h4, // number [0 to 15] of the visible line to be highlighted y, // record number of required node data in 'nodes.dat' v = 83, // y-coord for first node shown M = c6; // number of lines to be displayed if(c6 > 16) M = 16; // only got space for 16 lines max FILE *F = fopen("nodes.idx","r"); // open the 'nodes.idx' file FILE *G = fopen("nodes.dat","r"); // open the 'nodes.dat' file for(int i = 0; i < M; i++) { // for each line to be displayed /* file handle of 'nodes.idx' | number of records not visible before start of display | | index number for current line within display | | | multiply by 32 [record size for 'nodes.idx'] | | | | */ fseek(F,h4 + i << 5,SEEK_SET); // seek start byte of idx record /* file handle of 'nodes.dat' | REF [record number of required node data in 'nodes.dat'] | | file handle of 'nodes.idx' | | | multiply by 64 bytes per record | | | | offset of the 'frequency' field | | | | | */ fseek(G,((y = getInt(F)) << 6) + 20,SEEK_SET); // SET COLOUR FOR WHETHER OR NOT THIS LINE IS TO BE HIGHLIGHTED if(i == x) // if selected destination node XSetForeground(XD,XG,WHITE); // highlight it in bright white else // otherwise XSetForeground(XD,XG,BLEEN); // display it in munted green // DISPLAY NODE'S LIST NUMBER [REF] AND FREQUENCY int Q = 196; XDrawString(XD,XW,XG,Q,v,showInt(y,3,1),3); // Ref XDrawString(XD,XW,XG,Q+=24,v,showInt(getInt(G),9,0),9); // Frq // DISPLAY THIS NODE'S PORT NUMBER AND NET ADDRESS int k; // loop variable char S[21]; // to hold the strings of inputted characters /* file handle for 'nodes.dat' | move 4 bytes further on from | | current pointer position within 'nodes.dat' | | | */ fseek(G,+4L,SEEK_CUR); // start byte of this record's Port field // DISPLAY PORT NUMBER [IN HEX] for(k = 0; k < 4; k++) // copy each byte of Prt [Port Number in HEX] S[k] = fgetc(G); // into the character array S[] XDrawString(XD,XW,XG,Q+=60,v,S,4); // display Prt if(i == 0) { // if this is the first pass [base node] for(k = 0; k < 4; k++) // copy its HEX port number into the g3[k] = S[k]; // base node's port number global array g3[8] = 0; } else if(i == x) { // else if it is the selected node's pass for(k = 0; k < 4; k++) // copy its port number into the i3[k] = S[k]; // selected node's port number global array i3[8] = 0; } // DISPLAY NETWORK ADDRESS [IN HEX] for(k = 0; k < 8; k++) // copy each byte of Net [Network Address HEX] S[k] = fgetc(G); // into the character array S[] XDrawString(XD,XW,XG,Q+=30,v,S,8); // display Net if(i == 0) { // if this is the first pass [base node] for(k = 0; k < 8; k++) // copy its network address into the f3[k] = S[k]; // base node's network address global array f3[8] = 0; } else if(i == x) { // else if it is the selected node's pass for(k = 0; k < 8; k++) // copy its network address into the h3[k] = S[k]; // selected node's network address global array h3[8] = 0; } // DISPLAY THIS NODE'S LOCATION NAME int l = 0; // inset [in pixels] to right-justify the location name char c; // to hold current character of location name for(k = 0; k < 20; k++) { // copy the upto 20 bytes of if((c = fgetc(G)) == 0) // the location name into S[] break; // break out if end of name is encountered S[k] = c; // add current character to the location name } XDrawString(XD,XW,XG,Q+=52,v,S,k); // display the location name v += 17; // drop to next lower line } fclose(G); // close 'nodes.dat' fclose(F); // close 'nodes.idx' // IF MORE THAN 16 NODES IN THE ROUTE WE NEED TO DISPLAY A SCROLL BAR if(c6 < 17) return; // exit if 16 or less nodes in the list int h = 4352 / c6; // length of highlighted part of scroll bar v = h4 * (272-h) / (c6-16); // dull above the highlighted part XSetForeground(XD,XG,DARK); // set display colour to GREY XFillRectangle(XD,XW,XG,475,71,8,272); // show full length scroll bar XSetForeground(XD,XG,YELL); // highlight part of scroll bar XFillRectangle(XD,XW,XG,475,71+v,8,h); // in dull YELLOW } /* DISPLAYS TITLES & TEXT FOR THE SCROLLABLE LIST OF THE NODES FROM WHICH TO SELECT A DATA DESTINATION. Called only from 1 place in T4butClick(). */ void T4listNodes() { char *S = "REF FREQUENCY PORT ADDRESS ------LOCATION------"; XSetForeground(XD,XG,BLACK); // clear the title area XFillRectangle(XD,XW,XG,196,54,287,20); XSetForeground(XD,XG,YELLOW); // show column headings in yellow XDrawString(XD,XW,XG,196,63,S,48); T4scroll(); } /* FIND THE FRACNET NODE WHOSE BEARING IS CLOSEST TO THAT OF THE CURRENTLY SELECTED DESTINATION NODE. Called from only one place in T4()listClick.*/ void T4autoSelect() { FILE *F = fopen("nodes.dat","r"); // open for read-only /* file handle for 'nodes.dat' | contains the ref of the selected destination node | | times 64 gives the start byte of the node's record | | | bearing starts at byte 12 of the record | | | | */ fseek(F,(g4 << 6) + 12,SEEK_SET); // start byte of bearing field int a = getInt(F), // get destination node's bearing B = 180; // minimum bearing so far for(int i = 0; i < 5; i++) { // for each node of the selected fracnet /* file handle for 'nodes.dat' | contains the refs of the 5 nodes for each of the 5 fracnets | | number [0 to 4] of the selected fracnet | | | multiply by 4 and | | | | add it a further one to get h6 * 5 | | | | | node number within the selected fracnet | | | | | |*/ fseek(F,(d6[(h6 << 2) + h6 + i] << 6) + 12,SEEK_SET); /* | | multiply by 64 to get the start byte | of this node's record in 'nodes.dat' | | 4-byte bearing integer starts at byte 12 of the record */ int b = getInt(F); // get this node's bearing from the Base Node int c = a - b; /* difference between the bearing of the selected destin- ation node and that of the current fracnet node. */ if(c < 0) // if the difference between the bearings is negative c = -c; // reverse its sign if(c > 180) // if the result is greater than 180 degrees c = 360 - c; // subtract it from 360 to get its complement if(c < B) { // if this bearing difference be smaller than any previous B = c; // one then set it as the smallest bearing difference yet i4 = i; // and note which of the 5 fracnet nodes it pertains to } } fclose(F); // close 'nodes.dat' } /* DISPLAY THE NAMES OF THE 5 FRACNET NODES, HIGHLIGHTING THE NAME OF THE NODE THAT HAS BEEN AUTOSELECTED FOR CLOSEST BEARING TO THE SELECTED DESTINATION NODE. Called from 1 place each in T4listClick(), T4show(), T4click(). */ void T4fracnodes() { FILE *F = fopen("nodes.dat","r"); // open nodes data file for read only int v = 83, // vertical coord of first node name i,j,p,q; // loop variables, connection state colour char S[21], // array to hold the node name c; // for each node name character for(i = 0; i < 5; i++) { /* for each of the 5 nodes of the selected fracnet seek the start byte of its name field: number 0 to 4 of the currently selected fracnet | multiplied by 4 and | | added again to effectively multiply by 5 | | | number of node within selected fracnet | | | | */ p =(h6 << 2) + h6 + i; /* number of the node within the 25 fracnet nodes file handle for 'nodes.dat' | array of record numbers for all 25 nodes of the 5 fracnets | | number of node within the 25 fracnet nodes | | | times 64-byte record size | | | | plus offset of name field | | | | | */ fseek(F,(d6[p] << 6) + 40,SEEK_SET); /* connection status bits for the 25 nodes | number of node within the 25 fracnet nodes | | */ if(!(k6 & 1 << p)) q = RED; // if the node is not connected else if(i == i4) q = WHITE; // if this one is the selected node else q = BLEY; // else display its name in bluey-grey XSetForeground(XD,XG,q); // display its name in prescribed colour for(j = 0; j < 20; j++) { // for each of possible 20 chars in node name if((c = fgetc(F)) == 0) // if this one is the NULL terminator break; // break out of the for() loop S[j] = c; // otherwise add it to the node name } XDrawString(XD,XW,XG,70,v,S,j); // display the completed node name v += 18; // drop down to the next line } } /* HANDLE A CLICK MADE ON THE DESTINATION SELECTION LISTING. Highlights one of the nodes displayed in the nodes list. Called only from T4click() */ void T4listClick() { /* if mouse click was outside the horizontal limits of the list | or the DEST button is not bright [i.e. dull] | | */ if(mx < 196 && mx > 482 || !(a4 & 1)) return; // then do nothing and exit FILE *F = fopen("nodes.idx","r"); // open the index file for read-only int x, y = 70, i; // y-coord of first node data line for(i = 0; i < 16; i++) { // for each of the 16 visible data lines if(my > y && my < y+16) { // vertical limits of mouse click /* record number of destination node within 'nodes.idx' | number of the node data line within the visible range [0 to 15] | | number of the first visible node data line | | | */ f4 = i + h4; // need to 'remember' globally this record number /* file handle for 'nodes.idx' | record number of destination node within 'nodes.idx' | | times 32 to get start byte of record | | | */ fseek(F,f4 << 5,SEEK_SET); x = getInt(F); // rec num of selected node's data within 'nodes.dat' if(g4 == x) // if this node is already selected g4 = 0; // de-select it else // otherwise g4 = x; // select it T4scroll(); // re-display the list T4autoSelect(); // select the fracnet node whose bearing is closest T4fracnodes(); // re-display the members of this fracnet break; // bail out of the for() loop } y += 17; // drop down to next node data line } fclose(F); // close 'nodes.idx' } /* SHOW PERCENTAGE GRAPHIC AND DIGITS. This is the grey box that's internally 100 pixels high to show the percentage of the data that is still left to upload or the percentage of data that has so far been downloaded. */ void T4percent(int c) { /* if Tab 4 [XFER] isn't currently in view | or the percentage submitted is more than 100 | | or less than zero | | | */ if(tb != 4 || b4 > 100 || b4 < 0) return; // bail out XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,18,55,20,100); // clear the percentage graphic XFillRectangle(XD,XW,XG,16,162,24,9); // and the percentage readout if(c) return; // exit if we're merely clearing the graphic and readout XSetForeground(XD,XG,GREEN); // fill the box in bright green XFillRectangle(XD,XW,XG,18,155-b4,20,b4); // to the current percentage int a = 0, // positioning variables for the 3-character percentage readout b = 2; if(b4 < 101) { // if not yet reached 100% XSetForeground(XD,XG,WHITE); // show readout figures in white if(b4 == 100) { // if reached exactly 100% a = 3; b = 3; // set for a 4-character readout '100%' } XDrawString(XD,XW,XG,20-a,171,showInt(b4,b,1),b); // show figures XDrawString(XD,XW,XG,33+a,171,"%",1); // show % sign } /* the current percentage is zero | and the SEND buton is bright | | */ if(b4 == 0 && a4 & 8) showMsg(14,GREEN); // show message "UPLOAD COMPLETED" else /* the current percentage is 100 | and the RECV buton is bright | | */ if(b4 == 100 && a4 & 16) showMsg(18,GREEN); // show message "DOWNLOAD COMPLETED" } /* DISPLAYS THE DATA DIRECTION ARROW. Points left for download and right for upload or download. Arrow only visible during a data transfer operation. d4 = +1 arrow points right d4 = 0 arrow not visible d4 = -1 arrow points left Called from T4XFR(), T4show(), T4butClick()*/ void T4arrow() { if(tb != 4) return; XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,40,68,29,93); // clear the arrows area if(d4 == 0) return; // exit if neither uploading nor downloading int Y = 78 + i4 * 18, // y-coord of graphic within window W = Y + 1, // top of current arrow head line Z = Y - 4, // top left corner of arrow body rectangle x, y, i, p; XSetForeground(XD,XG,GREY); if(d4 == 1) { // if it's an UPLOADING ARROW [right-pointing] XFillRectangle(XD,XW,XG,41,Z,17,8); // body of arrow for(i = 0; i < 10; i++) { // for each vertical line of the arrow head x = 57 + i; // move right 'i' pixels y = 10 - i; // contract the length of the vertical line by 2 pixels XDrawLine(XD,XW,XG,x,W+y,x,Y-y); // point of the arrow } p = 67; } else { // else it's a DOWNLOADING ARROW [left-pointing] XFillRectangle(XD,XW,XG,50,Z,17,8); // body of arrow for(i = 0; i < 10; i++) { // for each vertical line of the arrow head x = 51 - i; // move left 'i' pixels y = 10 - i; // contract the length of the vertical line by 2 pixels XDrawLine(XD,XW,XG,x,W+y,x,Y-y); // point of the arrow } p = 41; } XDrawPoint(XD,XW,XG,p,Y); // the odd dot at the point of the arrow } /* DISPLAY THE MAIN FRACNETS SCREEN. Called from T4show() and T4butClick(). */ void T4showDest() { XSetForeground(XD,XG,BLACK); // clear the fracnet data display area XFillRectangle(XD,XW,XG,196,54,287,277); showMsg(0,DARK); /* if the DEST button is bright | or no destination node has yet been selected | | */ if(a4 & 1 || g4 == 0) return; // don't display destination node data FILE *F = fopen("nodes.dat","r"); // open for read-only // DISPLAY THE COLUMN TITLES AND ROW ANNOTATIONS XSetForeground(XD,XG,YELLOW); // draw node column titles in yellow XDrawString(XD,XW,XG,244,96,"------SELECTED REMOTE NODE------",32); char *T[9] = {"LATITUDE ", "LONGITUDE","DISTANCE ","BEARING ","HEIGHT ", "FREQUENCY","PORT ", "ADDRESS ", "LOCATION "}, *W[9] = {"d:m:s ","d:m:s ","metres ","degrees","metres ", "kHz ","HEX ","HEX "," "}; // DISPLAY THE FRACNET DATA int V = 99, // start height for parameter name display v = 20; // inter-line drop for(int i = 0; i < 9; i++) { XDrawString(XD,XW,XG,244,V+=v,T[i],9); // display node's parameter name XDrawString(XD,XW,XG,400,V,W[i],7); // display parameter units } V = 99; // reset to start height for parameter name display XSetForeground(XD,XG,WHITE); /* record number of node data within 'nodes.dat' | times 64 to get start byte of record | | */ fseek(F,g4 << 6,SEEK_SET); // seek start byte of record 'i' XDrawString(XD,XW,XG,330,V+=v,SecToDMS(getInt(F),1),10); // Lat XDrawString(XD,XW,XG,330,V+=v,SecToDMS(getInt(F),1),10); // Lng XDrawString(XD,XW,XG,330,V+=v,showInt(getInt(F),10,0),10); // Dst XDrawString(XD,XW,XG,330,V+=v,showInt(getInt(F),10,0),10); // Brg XDrawString(XD,XW,XG,330,V+=v,showHeight(getInt(F)),10); // Hgt XDrawString(XD,XW,XG,330,V+=v,showInt(getInt(F),10,0),10); // Frq /* file handle for 'nodes.dat' | move 4 bytes further on from | | current pointer position within 'nodes.dat' | | | */ fseek(F,+4L,SEEK_CUR); /* skip the marker field to arrive at the start byte of this record's Port field */ int k; // loop variable char S[21]; // to hold the strings of inputted characters for(k = 0; k < 4; k++) S[k] = fgetc(F); // copy each byte of Prt into S[] XDrawString(XD,XW,XG,366,V+=v,S,4); // display Prt [port number] for(k = 0; k < 8; k++) S[k] = fgetc(F); // copy each byte of Net into S[] XDrawString(XD,XW,XG,342,V+=v,S,8); // display Net [network address] int l = 0; // inset [in pixels] to right-justify the location name char c; // to hold current character of location name for(k = 0; k < 20; k++) { // copy the upto 20 bytes of if((c = fgetc(F)) == 0) // the location name int S[] break; // break out if end of name is encountered S[k] = c; } if(k < 10) l = (10 - k) * 6; // right-justify location name XDrawString(XD,XW,XG,330+l,V+=v,S,k); // display the location name fclose(F); } /* DISPLAYS THE CONTENT AND BUTTONS FOR THE XFER TAB. Called only from 1 place in showTab(). */ void T4show() { T6getNodes(); // in case the nodes data has not yet been loaded XSetForeground(XD,XG,GREY); // draw the upload/download progress box XDrawRectangle(XD,XW,XG,17,54,21,101); XDrawPoint(XD,XW,XG,38,155); T4percent(0); // display the current percentage T4arrow(); // show the current arrow state char S[] = "---FRACNET NODES---"; // block title template S[10] = h6 + 48; // add in number of selected fracnet XSetForeground(XD,XG,YELLOW); // display colour for the title XDrawString(XD,XW,XG,70,63,S,20); // display the block title XDrawString(XD,XW,XG,17,345,"MESSAGE",7); T4fracnodes(); // display fracnet names if(j4 > 5) // provided a data transfer is not still in progress j4 = -1; // reset the sequencer to its start condition T4status(); // show the current phase of a data transfer if(a4 &= ~1) // if the DEST button is bright T4showDest(); // show the details of the destination node T4Buts(); // display the control buttons } void T4reset() { // RESET THE TRANSFER SEQUENCER j4 = -1; // reset TRANSFER PROTOCOL T4showDest(); // show the details of the destination node T4status(); // clear the status indicator T4percent(1); // clear the percentage indicator T4arrow(); // clear the arrow } void T4dest() { // THE DEST BUTTON WAS CLICKED T4reset(); // reset the transfer sequencer if(a4 & 1) { // if the DEST button is bright a4 &= ~1; // make it dull T4showDest(); // show the destination node details } else { // otherwise a4 |= 1; // brighten the DEST button T4listNodes(); // display the list of nodes } } /* HANDLES THE SITUATION WHEN THE 'SEND' BUTTON HAS BEEN CLICKED Called only from one place in T4butClick(). */ void T4send() { if(a4 & 1) { // if the DEST button is bright showMsg(22,YELLOW); // message: "Need to reslease the DEST button" return; } /* if the data transfer is currently paused | when it was in the middle of downloading | | */ if(c4 & 4 && !(c4 & 1)) { showMsg(20,RED); // message "cannot resume SEND from a paused RECV" return; } /* if the SEND button is not bright | and the RECV button is also not bright | | */ if(!(a4 & 8) && !(a4 & 16)) { if(g4 == 0) // if no destination node is selected showMsg(10,YELLOW); // message: "No destination node selected" else if(i5 < 9) showMsg(30,YELLOW); // message: "No link established" else { if(!(c4 & 4)) { // if the download was not paused j4 = -1; // initiate the data transfer sequence T4status(); // clear the transfer status panel k4 = 3; // set delay: units of 3 seconds } if(a4 & 2) // if in VOICE mode showMsg(23,GREEN); // message: "Initiating voice stream protocol" else showMsg(12,GREEN); // message: "Upload in progress..." a4 |= 264; // set the TX bit and brighted the SEND button a4 &= ~112; // dull the RECV, PAUSE and CANCEL buttons if(!(c4 & 4)) // if not paused b4 = 100; // reset to 100% of file ready to be sent c4 &= ~12; // clear the paused and finished bits c4 |= 3; // set the 'upload' and 'started' bits d4 = +1; // set forarrow to point right T4arrow(); // show sending arrow [points right] } } } void T4voice() { // THE VOICE BUTTON WAS CLICKED if(a4 & 1) // if the DEST button is bright showMsg(22,YELLOW); // message: "Need to reslease the DEST button" else { T4reset(); // reset the transfer sequencer if(!(a4 & 2)) { // if the VOICE button is dull a4 |= 2; // brighten the VOICE button } a4 &= ~4; // dull the DATA button T4send(); // initiate stream connection } } void T4data() { // DATA button was clicked T4reset(); // reset the transfer sequencer if(!(a4 & 4)) { // if the DATA button is dull a4 |= 4; // brighten the DATA button a4 &= ~2; // dull the VOICE button } } /* HANDLES THE SITUATION WHEN THE 'RECV' BUTTON HAS BEEN CLICKED Called only from one place in T4butClick(). */ void T4recv() { if(a4 & 1) { // if the DEST button is bright showMsg(22,YELLOW); // message: "Need to reslease the DEST button" return; } /* if the data transfer is paused | while in the middle of uploading | | */ if(c4 & 4 && c4 & 1) { showMsg(21,RED); // messag: "cannot resume RECV from a paused SEND" return; } /* if the RECV button is dull | and the SEND button is also dull | | */ if(!(a4 & 16) && !(a4 & 8)) { if(g4 == 0) // if no destination node is selected showMsg(11,YELLOW); // message: "No destination node selected" else if(i5 < 9) // if no link-level connection is established showMsg(30,YELLOW); // message: "No link established" else { if(!(c4 & 4)) { // if the download was not paused j4 = -1; // set initiate the data transfer sequence T4status(); // clear the transfer status panel k4 = 3; // set delay: units of 3 seconds } showMsg(16,GREEN); // message: "Download in progress..." a4 |= 16; // brighten the RECV button a4 &= ~104; // dull the SEND, PAUSE and CANCEL buttons if(!(c4 & 4)) // if not paused b4 = 0; // reset to 0% of file has yet been received c4 &= ~13; // clear the upload, paused and finished bits c4 |= 2; // set the 'started' bit d4 = -1; // set forarrow to point left T4arrow(); // show receiving arrow [points left] } } } /* HANDLES THE SITUATION WHEN THE 'PAUSE' BUTTON HAS BEEN CLICKED Called only from one place in T4butClick(). */ void T4paus() { /* if the PAUSE button is bright | or the CANCEL button is bright | | */ if(a4 & 32 || a4 & 64) return; // exit withoutdoing anything if(c4 & 1) // if the TX bit is set showMsg(13,YELLOW); // message: "Upload paused" else showMsg(17,YELLOW); // message: "Download paused" a4 |= 32; // brighten the PAUSE button a4 &= ~88; // dull the SEND, RECV and CANCEL buttons c4 |= 4; // set the paused bit } /* HANDLES THE SITUATION WHEN THE 'CANCEL' BUTTON HAS BEEN CLICKED Called only from one place in T4butClick(). */ void T4canc() { if(a4 & 64) return; // exit if the CANCEL button is already bright if(a4 & 2) // if in VOICE mode showMsg(25,RED); // message: "voice stream closed" else if(c4 & 1) // if the TX bit is set showMsg(15,RED); // message: "Upload cancelled" else showMsg(19,RED); // message: "Download cancelled" a4 |= 64; // brighten the CANCEL button a4 &= ~56; // dull SEND, RECV, PAUSE buttons, unset TX & RX bits c4 = 0; // clear all transfer state bits b4 = 0; // set percentage to zero d4 = 0; // set to clear the arrow T4reset(); // reset the transfer sequencer } /* ONE OF THE XFER TAB'S CONTROL BUTTONS WAS PRESSED. Buttons are numbered 0 to 7 from left to right. [Called from 1 place in MEH().] */ void T4butClick(int x) { switch(x) { // switch according to button number case 0: T4dest(); break; // DEST button was clicked case 1: T4voice(); break; // VOICE button was clicked case 2: T4data(); break; // DATA button was clicked case 3: T4send(); break; // SEND button was clicked case 4: T4recv(); break; // RECV button was clicked case 5: T4paus(); break; // PAUSE button was clicked case 6: T4canc(); break; // CANCEL button was clicked case 7: // HELP button was clicked helpPage("radio/com.html#xfer"); } T4Buts(); // re-display the buttons } /* A CLICK HAS OCCURRED WITHIN TAB 4 OTHER THAN ON ONE OF THE BOTTOM BUTTONS Called from only one place in MEH(). */ void T4click() { if(mx > 195) // left x-limit of nodes selector list T4listClick(); // go select destination node else /* DETERMINE WHICH OF THE CURRENT FRACNET'S NODE NAMES HAS BEEN CLICKED This is to enable you to override the fracnet node decided by the destination node selector. */ if(mx > 71 && mx < 189) { // x-limits of the fracnet nodes list int y = 70; // top of the top fracnet node name for(int i = 0; i < 5; i++) { // for each of the 5 node names if(my > y && my < y + 17) { // if the click was on node name 'i' i4 = i; // set number of clicked node within fracnet T4fracnodes(); // display the node names with this one break; // highlighrted, then exit for() loop } y += 17; // drop down to next node name } } } /* INCREMENTS DOWNLOAD PERCENTAGE OR DECREMENTS UPLOAD PERCENTAGE OF THE FILE BEING TRANSFERRED. Called from T4LSS() below. This function is called whether or not we are currently in Tab 5. BIT VALUE SIGNIFICANCE IN c4 0 1 0=download 1=upload 1 2 0=idle 1=started 2 4 0=active 1=paused 3 8 0=active 1=finished */ void T4XFR() { if(e4-- > 0) // if iteration counter not yet expired return; // exit without doing anything e4 = 1; // re-prime the iteration counter to 1 second int x = 1; // incrementor/decrementor flag [set initially to increment] if(c4 & 1) // if uploading x = -1; // set 'x' to decrement the %age of the file not yet sent /* transfer is not paused | AND transfer is not finished | | */ if(!(c4 & 4) && !(c4 & 8)) { T4percent(0); // display the current state on the graphic b4 += x; // incr/decr percentage of file transferred } } /* DATA TRANSFER PROTOCOL. Manges the sequence of processes involved in up- loading a file to or downloading a file from a remote node. Called only from 1 place in main()'s timer loop. */ void T4LSS() { static int j = 9; /* IF the SEND button is is not bright [i.e. it is dim] | AND the RECV button is not bright [i.e. it is dim] | | */ if(!(a4 & 8) && !(a4 & 16)) return; // then exit without doing anything if(j4 == 5) { // if transfer sequencer is in phase 5: TRANSFER INITIATED if(a4 & 2) return; // if we're in VOICE mode, exit /* the RECV buton is bright | AND not all the data has yet been received | | | */ if(a4 & 16 && b4 < 101 || a4 & 8 && b4 > -1) T4XFR(); /* OR | | | | the SEND button is bright AND | | there's still data to be sent | THEN transfer the next block of data */ else { // ELSE THE SEQUENCER IS IN A PHASE OTHER THAN 5 j4++; // increment sequence number to next phase c4 &= ~6; // set to not started and not paused c4 |= 8; // set the SEND operation as finished d4 = 0; // arrow direction [0=no arrow] T4arrow(); // clear the data transfer arrow } } else if(k4 > 0) // ELSE IF THE PROTOCOL TIMER IS STILL RUNNING k4--; // decrement protocol timer counter else { // else the protocol timer has expired k4 = 10; // set delay: 10 seconds j4++; // increment sequence number to next phase /* if in VOICE mode AND | we've reached the TRANSFER INITIATED phase | | */ if(a4 & 2 && j4 == 5) { showMsg(24,GREEN); // "Duplex data stream established" a4 &= ~8; // dull the SEND button } } if(j4 > 6) // if the transfer is in a post-termination phase a4 &= ~24; // dull the SEND and RECV buttons [blocks this sequencer] if(j != j4) { // if the phase number has changed since last pass j = j4; // remember the new phase number T4IOmsg(); // display the I/O message in Tab 3 T4Buts(); // to change display colour of VOICE button T4status(); // display the new status } } /* If the currently active moon-bounce signal fails, reset any currently active transfer taking place in the XFER tab and any connection in the LINK tab and display appropriate messages in the repective tabs if visible at the time. Called only from main's 1-second timer secTime(). */ int sigSub(int x) { if(tb == x) // if in the XFER tab if(c7 == RED) // if the moon has gone down showMsg(27,RED); // show 'moon below horizon' message else // otherwise showMsg(29,RED); // show 'no up-channel established' message } void T4signal() { /* MOON button is selected in the LINK tab | the moon has gone down | | or moon up-channel not established | | | */ if(v5 & 4 && (c7 == RED || u7 == 0)) { v5 &= ~4; // de-select LINK tab's the vertical MOON button v5 |= 2; // select the FNET if(j4 != -1) { // if an XFER is in progress, T4reset(); // abort it sigSub(4); // display the appropriate warning message } if(m5 & 1) { // if a LINK is currently up, reset it T5reset(); // reset it sigSub(5); // display the appropriate warning message } } } // --------------------FUNCTIONS PERTAINING TO TAB 7 MOON--------------------- /* WIPE SCAN MESSAGE IN 'BAND' SUB-TAB: called from 1 place each in T7moonUp(), T7chanClick() and from 2 places each in T7bandScan(), T7chanScan(), T7vertButs(). */ void T7wipeScan() { if(a7 & 8 || a7 & 16) { // if in BAND or CHAN sub-tab, XSetForeground(XD,XG,DARK); //kill the SCAN message XFillRectangle(XD,XW,XG,160,276,323,21); } } /* DISPLAYS STATUS MESSAGE IN ALL SUB-TABS: Executed once per second. called from: T7moon() and T7bandShow() */ void T7moonUp() { if(a7 & 64) return; // don't show this message in the GEOID sub-tab static int C7 = 0; // remember the old message colour int m = 27; // set moon status message 'too low for moon-bounce c7 = RED; // set moon status message and elevation in red if(MoonEl > 0) { // but if moon elevation greater than zero c7 = GREEN; // set moon status message and elevation in GREEN m = 28; // set moon status message as 'tracking...' } q7 = 0; // set that moon's status not changed since last pass if(C7 != c7) { // if the moon's status has changed q7 = 1; // set the moon's status change flag C7 = c7; // remember the new status for subsequent passes T7wipeScan(); // wipe scan message in BAND sub-tab } XSetForeground(XD,XG,YELLOW); // Display MESSAGE annotation in yellow XDrawString(XD,XW,XG,17,345,"MESSAGE",7); showMsg(m,c7); // show the moon status message } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF TRACK TAB. Called from 1 place each in T7showTrack(), T7showBand(), T7showChan(), T7showGraph(), T7showSpare(), T7showGeoid(), T7butClick(). */ void T7showButs() { if(tb != 7) return; // bail out if TRACK tab not currently visible showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate horizontal row of control buttons *A[] = {"TRACK","GRAPH","STEER","BAND","CHAN","SPARE","GEIOD","HELP"}; const int a[] = {12,12,12,15,15,12,12,15}, // horizontal offsets for word starts b[] = { 5, 5, 5, 4, 4, 5, 5, 4}; // number of letters in each word int h = 17; // x coordinate of button block for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a7 & 1 << i) // if this button is bright XSetForeground(XD,XG,WHITE); // set colour for bright lettering else XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; // move across to next button } } /* GRAPH SUB-TAB: DISPLAYS DIAGRAMATICALLY THE ELEVATION AND AZIMUTH OF THE MOUNBOUNCE AERIAL. Called from T7butClick and T7show() */ void T7showGraph() { clearTab(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,17,70,"MOON'S CURRENT ELEVATION AND AZIMUTH",36); XSetForeground(XD,XG,GREY); // colour for axes int x = 40, y = 285, // origin of elevation graph r = 75, // radius of elevation arc d = r << 1, // diameter of elevation arc w = 50, // radius of elevation arc v = w << 1, // diameter of elevation arc X = 360, Y = 170, // origin of azimuth graph R = 90, // rdius of azimuth circle D = R << 1; // diameter of azimuth circle XDrawLine(XD,XW,XG,x,y,x,y-150); // elevation vertical axis XDrawLine(XD,XW,XG,x,y,x+150,y); // elevation horizontal axis XDrawString(XD,XW,XG,x+20,y+25,"ELEVATION",9); XDrawString(XD,XW,XG,307,310,"AZIMUTH",7); XDrawLine(XD,XW,XG,360,170-100,360,170+100); // vertical line XDrawLine(XD,XW,XG,360-100,170,360+100,170); // horizontal line XDrawArc(XD,XW,XG,X-R,Y-R,D,D,0,23030); // draw the azimuth circle XDrawString(XD,XW,XG,358, 64,"N",1); XDrawString(XD,XW,XG,249,175,"W",1); XDrawString(XD,XW,XG,467,175,"E",1); XDrawString(XD,XW,XG,358,285,"S",1); XSetForeground(XD,XG,c7); // colour for arc XDrawString(XD,XW,XG,x+78,y+25,RadToDMS(MoonEl,0),9); // show moon elevation if(c7 == GREEN) { /* if the moon is up DRAW THE MOON ELEVATION ARC | x-coordinate of the left side of the bounding rectangle | | y-coordinate of that same | | | width of the arc's bounding rectangle | | | | height of the arc's bounding rectangle | | | | | start angle relative to 3 o'clock | | | | | | */ XDrawArc(XD,XW,XG,x-w,y-w,v,v,0,round(MoonEl * DPR * 64)); /* | | | | arc angle in degrees times 64 | | | current elevation of the moon in radians | | to convert radians to degrees | convert to floating point degrees times 64 DRAW THE MOON ELEVATION LINE | x=origin of elevation line | | y-origin of elevation line | | | x-origin of the elevation arc | | | | shortened radius of the elevation arc | | | | | */ XDrawLine(XD,XW,XG,x,y,x+d*cos(MoonEl),y-d*sin(MoonEl)); /* | | y-origin of elevation arc | radius of the elevation arc */ } XDrawLine(XD,XW,XG,X,Y,X+R*sin(FwdAzi),Y-R*cos(FwdAzi)); // azimuth line XDrawString(XD,XW,XG,358,310,RadToDMS(FwdAzi,1),9); // moon azimuth T7moonUp(); // show if the moon is above or below the horizon T7showButs(); // display the control buttons for Tab 7 } /* COMPUTE AND DISPLAY THE MOON'S ASTRONOMICAL STATUS FOR THE MOON SUB-TAB AND FOR THE AZIMUTH AND ELEVATION OUTPUTS TO THE SERVOS OF THE ANTENNA STEERING PLATFORM. With appreciation of the work of the author of the website: https://www.stjarnhimlen.se/comp/ppcomp.html https://www.evona.com/articles/https-www-evona-com-articles-argument-of- periapsis-how-maths-is-responsible-for-getting-us-to-the-moon Check computed values against https://theskylive.com/moon-info */ void T7moon() { // called from T7showTrack() and secTime(). if(tb == 7 || a7 & 4) { /* do the moon orbital computations only if we are in Tab 7 or aerial STEERing is active. */ /* COMPUTE & DISPLAY THE JULIAN DAY NUMBER: This is the real [i.e. integral + fractional] number of 24-hour days measured from the most recent Julian Epoch [which is a temporal reference point of 00:00 GMT on Sat 01 January 2000. A Julian Day comprises exactly 24-hours and is, by convention, meas- ured from noon to noon: not midnight to midnight like normal days. However, for the purpose of the following astronomical computations, it is more con- venient if they run from midnight to midnight, so that is how they are treated in this function. The Julian Day number is used in this to compute the current orbital position of the Moon on the Celestial Sphere. This function is executed once every second by secTime(). */ gmt = *gmtime(&etm); // get GMT time parameters int yyyy = gmt.tm_year + 1900, // current year number mm = gmt.tm_mon + 1, // current month number dd = gmt.tm_mday, // current day of the month h = gmt.tm_hour, // completed hours today m = gmt.tm_min, // completed minutes today s = gmt.tm_sec; // completed seconds today static int DM = 0, // last time's 'day of the month' number jd = 0; // integral part of the Julian Day number /* If the 'day of the month' has changed since the previous pass, note the new current 'day of the month' number. Avoids having to re-compute the integral part of the Julian Day number at every 1-second pass. */ if(dd != DM) { DM = dd; /* COMPUTE THE INTEGRAL NUMBER OF DAYS since the start of the current Julian Epoch. Formula converts today's normal Gregorian date dd:mm:yyyy to a Julian Day Number. Divisions are truncated [any remainder is ignored]. */ jd = 367 * yyyy - 7 * ( yyyy + (mm + 9) / 12 ) / 4 - 3 * ( ( yyyy + (mm - 9) / 7 ) / 100 + 1 ) / 4 + 275 * mm / 9 + dd - 730515; } /* COMPUTE THE FRACTIONAL DAY. The datum for measuring Julian days used in these computations is from midnight to midnight. The formula below gives a positive fraction of a 24-hour day from midnight GMT to the 'time now'. This fraction is then added to the integral number of days. [integral+fractional] number of days since 00:00 GMT 01 Jan 2000 | whole days since 00:00:00 GMT 01 Jan 2000 | | GMT now in seconds since midnight | | | seconds in a day = 24 * 60 * 60 | | | | */ double JD = ((double)jd + (double)gms / 86400.0), // in 24-hour days /* COMPUTE EARTH-MOON DISTANCE IN EARTH RADII & ITS TRUE ANOMALY MEAN ANOMALY of the Moon [0 at perigee increases uniformly with time] | Rationalises the angle to lie between 0 and 2pi | | mean anomaly [angular offset from perigee] at 00:00GMT 01Jan2000 | | | its rate of increase in radians per day | | | [this figure may need fine adjustment] | | | | */ Mm = RatAng(2.0135060729 + 0.2280271437424892 * JD), /* ECCENTRIC ANOMALY OF THE MOON [IN RADIANS] [SEE WIKIPEDIA] | Moon's orbital eccentricity [a ratio] | | | */ Em = RatAng(Mm + 0.0549 * sin(Mm) * (1.0 + 0.0549 * cos(Mm))), /* Find the current x [major axis] and y [minor axis] coordinates of the Moon within its own orbital plane. x-coordinate of the Moon [perigee to apergee] in its own orbital plane | 'a' the semi-major axis of the Moon's orbit [in Earth radii] | | 'e' eccentricity [0=circle, 0-1=ellipse, 1=parabola] | | | */ X = 60.2666 * (cos(Em) - 0.0549), /* 1/10000 Earth radius = 637 metres y-coordinate of the Moon [perpendicular to x] in its own orbital plane | a * sqrt(1 - e * e) for the Moon's orbit | | */ Y = 60.175709409 * sin(Em), // ### check this line /* The Moon's true anomaly [Vm] is an angular parameter that defines its pos- ition moving along a Keplerian orbit. It is the angle between the direction of perigee and the current position of the Moon, as seen from the main focus of the ellipse (the point around which it orbits). */ Vm = atan2(Y,X), // current True Anomaly 'Vm' Rm = sqrt(X * X + Y * Y), // current Earth-Moon distance [Earth Radii] /* COMPUTE THE MOON'S 'X,Y,Z' POSITION IN CARTESIAN SPACE WHERE THE X-Y PLANE IS THE PLANE OF THE ECLIPTIC [ECLIPTIC = THE ORBIT OF THE SUN] The line joining the Moon's descending and ascending nodes is where the Moon's orbit crosses the Ecliptic Plane from North to South and from South to North respectively]. It is the line of intersection between the Ecliptic Plane and the Moon's Orbital Plane. The Moon's Argument of Perigee, 'wm' the angle between the Moon's Line of Perigee and the line joining the two nodes. compute 'wm' the Moon's ARGUMENT OF PERIGEE, which is the angle between the direction of the ascending node and the direction of the perigee. | value of 'wm' at 00h00 GMT 01 January 2000 | | average number of radians per day orbital speed | | | */ wm = RatAng(5.5512535601 + 0.00286857642388938 * JD), /* | | chops off all the Number of Julian Days elapsed completed orbits since 00h00 GMT 01 January 2000 */ A = RatAng(Vm + wm), sA = sin(A), cA = cos(A), /* 'i' is the angle of intersection between the orbital plane of the Moon and the plane of the Ecliptic [the plane of the Earth's orbit around the Sun]. It is 5.1454 degrees, which is 0.0898041713 radians. */ si = 0.08968351137, // sin(i) ci = 0.99597031471, // cos(i) sc = sA * ci, /* sin(A) * cos(i) COMPUTE THE ECLIPTIC LONGITUDE 'Nm' of the Moon's Ascending Node: the point in the Moon's orbit at which the Moon moves into the northern ecliptic hemi- sphere: i.e. where the Moon's orbit crosses the ecliptic from South to North | | longitude of the Moon's ascending Node at 00:00 GMT 01 Jan 2000 | | change in longitude per 24 hour day | | | */ Nm = RatAng(2.1838048293 - 0.000924218306302609 * JD), /* in radians | | chops off all the Number of Julian Days elapsed completed orbits since 00h00 GMT 01 January 2000 */ sN = sin(Nm), // sine of Ecliptic Longitude of the Moon's ascending node cN = cos(Nm); // cosine of Ecliptic Longitude of the Moon's ascending node X = Rm * (cN * cA - sN * sc); // Cartesian coordinates of Moon's Ascending Y = Rm * (sN * cA + cN * sc); // Node. The X & Y coordinates are in the double Z = Rm * sA * si, // plane of the Eliptic [orbit of the Sun]. /* COMPUTE THE MOON'S ECLIPTIC LATITUDE AND LONGITUDE: Note that this is not the same as normal latitude and longitude. Ecliptic latitude and long- itude are based on the Ecliptic Plane: normal latitude and longitude are based on the Equatorial Plane. */ LatEcl = atan2(Z,sqrt(X * X + Y * Y)), // Moon's ecliptic latitude LngEcl = atan2(Y,X), // Moon's ecliptic longitude /* POSITION OF THE SUN We now need to formalise some parameters pertaining to the Sun. These are ex­pressed in terms of the 'orbit' of the Sun around the Earth within the Ecliptic Plane. Ecliptic longitude of the Sun | Mean Anomaly of the Sun | | Starting point at 00h00 on 01 January 2000 | | | Angular verlocity of Sun's Mean Anomaly | | | | in Radians per Julian Day | | | | | */ Ms = 6.214192442 + 0.0172019696192896 * JD, /* | | | | | The Sun's Argument of Perihelion Julian Day number | | | | | --------------------------------- */ Ls = Ms + 4.9382415669 + 8.21936631E-7 * JD, /* | | | | Angular verlocity of Julian Day number | Sun's Perihelion in | Radians per Julian Day | Starting point at 00h00 on 01 January 2000 LUNAR PERTURBATIONS: for a positional accuracy better than about 2 deg- rees, the most important perturbations have to be taken into account. For 2 arc minute accuracy, all the following terms should be accounted for. ALL ANGLES ARE IN RADIANS. Mean Ecliptic Longitude of the Moon | Mean Anomaly of the Moon | | Moon's Argument of perigee | | | Longitude of the Moon's node | | | | */ Lm = Mm + wm + Nm, D = Lm - Ls, // Mean elongation of the Moon F = Lm - Nm, // Argument of latitude for the Moon E = D + D, // Twice the Elongation of the Moon M = Mm + Ms, // Sum of the Mean Anomalies of the Sun and Moon N = Mm - E, // Moon's Mean Anomaly less twice its Elongation G = F - E; // Moon's Argument of Latitude less twice its Elongation // Add Perterbations to the Moon's current Ecliptic Longitude [in radians] LngEcl += - 0.022235495 * sin(N) // (the Evection) + 0.011484266 * sin(E) // (the Variation) - 0.003246312 * sin(Ms) // (the Yearly Equation) - 0.001029744 * sin(Mm + Mm - E) - 0.000994838 * sin(M - E) + 0.000925025 * sin(Mm + E) + 0.000802851 * sin(E - Ms) + 0.000715585 * sin(Mm - Ms) - 0.000610865 * sin(D) // (the Parallactic Equation) - 0.000541052 * sin(M) - 0.000261799 * sin(F + G) + 0.000191986 * sin(N - E); // Add Perterbations to the Moon's current Ecliptic Latitude [in radians] LatEcl += - 0.003019420 * sin(F - E) - 0.000959931 * sin(Mm - F - E) - 0.000802851 * sin(Mm + F - E) + 0.000575959 * sin(F + E) + 0.000296706 * sin(Mm + Mm + F); // Add Perterbations to the Earth-Moon Distance [in Earth radii] Rm -= 0.58 * cos(N) + 0.46 * cos(E); /* RE-COMPUTE THE GEOCENTRIC CARTESIAN COORDINATES OF THE MOON BASED ON THE ECLIPTIC PLANE AFTER HAVING ADDED THE ABOVE PERTURBATIONS */ X = Rm * cos(LngEcl) * cos(LatEcl); // x-coord [in the Ecliptic plane] Y = Rm * sin(LngEcl) * cos(LatEcl); // y-coord [in the Ecliptic plane] Z = Rm * sin(LatEcl); // z-coord [perpendicular to it] /* COMPUTE CURRENT ANGLE BETWEEN THE ECLIPTIC & EQUATORIAL PLANES This is the same as the tilt of the Earth's axis of rotation and is gradually reducing at a very small rate. OBLIQUITY OF THE ECLIPTIC | tilt of Earth's axis of rotation at 00:00 GMT 01 Jan 2000 | | tilt reduction per 24-hour day [very small] | | | */ double o = RatAng(0.4090929594 - 6.2186081E-9 * JD), // in radians /* TRANSLATE THE GEOCENTRIC CARTESIAN COORDINATES X,Y,Z OF THE MOON [WHERE THE X & Y COORDINATES ARE IN THE ECLIPTIC PLAN] TO GEOCENTRIC CARTESIAN COORDINATES [WHERE X & y ARE IN THE EARTH'S Equatorial PLANE, WHICH IS THE SAME AS THE Equatorial PLANE OF THE CELESTIAL SPHERE].*/ SinEcl = sin(o), // sine of the obliquity of the ecliptic CosEcl = cos(o), // cosine of the obliquity of the ecliptic // the x-coord runs along where the two planes intersect so it is left as is y = Y * CosEcl - Z * SinEcl, // translate the y-coordinate z = Y * SinEcl + Z * CosEcl, // translate the z-coordinate /* COMPUTE GREENWICH MEAN SIDEREAL TIME: Greenwich Mean Sidereal Time is the number of siderial hours, sidereal minutes and sidereal seconds that the Greenwich Meridian on the Earth is East of the Zero Meridian on the Celest- ial Sphere at any given instant. The Celestial Sphere's Zero Meridian re- mains always in the same fixed direction relative to the stars. The Green- wich Meridian always rotates with the Earth. Greenwich Local Sidereal Time [GMST] = Right-Ascension of the Greenwich meridian. NOTE: 'G' given by this function agrees with the Durham University's Sidereal Time live at https://astro.dur.ac.uk/~ams/users/lst.html GREENWICH MEAN SIDERIAL TIME | remove whole turns of 2pi | | Sun's mean Celestial Longitude [Right Ascension] | | | Greenwich Mean Time [seconds past midnight] | | | | number of seconds in half a day | | | | | */ GMST = RatAng(Ls + pi * (1.0 + gms / 43200.0)), /* radians Note that a Sidereal Day is only 23h 56m 04s in terms of normal hours, minutes and seconds. So Sidereal hours, minutes and seconds are shorter than normal civil hours, minutes and seconds. /* COMPUTE THE MOON'S RIGHT-ASCENSION AND DECLINATION The Moon's SUB-POINT is the point on the Earth's surface where the Moon is currently dead overhead. The Moon's sub-point latitude is the same as its Celestial Declination. Right Ascension of the Moon | Make 'RAm' range from 0 to 2pi because Right Ascension has this range | | atan2() returns a value between -pi and +pi | | | y-coordinate of the Moon within the Equatorial Plane | | | | x-coordinate of the Moon within the Equatorial Plane | | | | | */ RAm = RatAng(atan2(y,X)), // Moon's Right Ascension [in radians] Dm = atan2(z,sqrt(X*X+y*y)), /* Moon's Declination [in radians] | | | z-coordinate of the Moon, perpendicular to the Celestial Plane# Declination of the Moon #The Celestial Plane is the Equatorial Plane of the Celestial Sphere, which coinsides with the Earth's Equatorial Plane. /* COMPUTE THE MOON'S SUB-POINT LONGITUDE Longitude of Moon's sub-point | make it into a bipolar angle | | remove complete turns of TwoPi | | | Moon's Right-Ascension | | | | Greenwich Mean Sidereal Time | | | | | */ Mlng = BipAng(RatAng(RAm - GMST)), /* COMPUTE MOON'S AZIMUTH AND DISTANCE FROM THE BASE STATION: the moon's azimuth is its bearing from the observer [i.e. the Base Station]. TEST DATA: Base Station is at my apartment */ Blat = -0.347645492, // Latitude: 19°55'07·03"S in radians Blng = -0.767623973; // Longitude: 43°58'53·81"W in radians /* NOTE: The Vincenty solution below returns distance in metres on an ellipsoidal Geoid. What the Moon calculation needs is really only the great circle angle on a purely spherical Earth. But the difference is minute and the Vincenty function below is readily available in this program. SOLUTION TO THE VINCENTY GEODETIC INVERSE PROBLEM | latitude of Base Station [in radians] | | longitude of base station [in radians] error | | | current latitude of the moon's sub-point [in radians] code | | | | current longitude of moon's sub-point [in radians] | | | | | | */ int e = VSGIP(Blat,Blng,Dm,Mlng); if(e > 0) { // if the Vincenty error non-zero showMsg(e+25,RED); // display the Vincenty error return; // no point continuing } /* Provided VSGIP() returns a zero error, the azimuth [bearing from the Base Station] of the Moon's sub-point [in radians] appears in the global variable 'FwdAzi' and the distance of the Moon's sub-point [in metres] from the Base Station appears in the global variable 'VinDst'. */ /* COMPUTE THE MOON'S LOCAL ELEVATION ANGLE. The Moon's elevation is the angle from the observer's horizontal upwards, in the vertical plane of the azimuth, to where the Moon is in the sky. */ /* See diagram: https://robmorton.website/radio/com.html#moonElev GREAT CIRCLE ANGLE BETWEEN OBSERVER AND MOON'S SUB-POINT | distance between observer and moon's sub-point [in metres] | | metres per Earth-radian | | | */ double a = VinDst / 6371000.0; // in radians /* The useful upper limit for 'a' [distance in Earth-radians of the Moon's sub-point from the Base Station] is 1.5 radians. This yields a lunar elevation above the Base Station's horizon of 03d06m32s. The following formula should return a value for MoonEl between +pi/2 and -pi/2. ELEVATION ANGLE OF THE MOON ABOVE THE HORIZON AT THE BASE STATION | Great circle angle between Base Station & Moon's sub-point | | Mean radius of Moon's orbit [in Earth Radii] | | | */ MoonEl = HalfPi - a - atan2(sin(a), Rm - cos(a)); /* Moon elevation MoonEl is for an observer at sea level. But adjustinf for observer height doesn't make any significant difference. */ // OUTPUT COMMANDS TO STEERING SERVOS if(a7 & 4) printf("Azi=%9f Ele=%9f\n",FwdAzi,MoonEl); // DISPLAY THE MOON'S ASTRONOMICAL STATUS if(tb != 7) return; // exit if MOON tab not wisible T7moonUp(); // show status message of the moon [up or down] /* if the GRAPH button is bright, update and show the elevation and azimuth graphic graphic then exit. */ if(a7 & 2) { T7showGraph(); return; } if(!(a7 & 1)) return; // exit if TRACK sub-tab not visible XSetForeground(XD,XG,BLACK); // clear the display area XFillRectangle(XD,XW,XG,137,55,65,260); XSetForeground(XD,XG,c7); // colour for moon's current elevation XDrawString(XD,XW,XG,137,310,RadToDMS(MoonEl,0),9); // show moon elevation if(c7 == GREEN) XDrawString(XD,XW,XG,136,310,"+",1); // show the sign of else XDrawString(XD,XW,XG,136,310,"-",1); // the elevation angle XSetForeground(XD,XG,WHITE); // colour for figures XDrawString(XD,XW,XG,137,70,showTime(lcs,0),9); // Local Clock Time XDrawString(XD,XW,XG,137,90,showTime(gms,0),9); // Greenwich Mean Time XDrawString(XD,XW,XG,113,110,showDouble(JD),14); // Julian Day number XDrawString(XD,XW,XG,137,130,showTime(GMST*13750.98708314,0),9); XDrawString(XD,XW,XG,137,150,showTime((int)(RAm * 43200 / pi),0),9); XDrawString(XD,XW,XG,137,170,RadToDMS(Dm,0),10); // Show Declination XDrawString(XD,XW,XG,137,190,RadToDMS(Mlng,1),10); // sub-point longitude XDrawString(XD,XW,XG,137,230,RadToDMS(Blat,0),10); // base stn. latitude XDrawString(XD,XW,XG,137,250,RadToDMS(Blng,1),10); // base stn. longitude XDrawString(XD,XW,XG,137,270,showInt(VinDst,9,0),9); // distance [metres] XDrawString(XD,XW,XG,137,290,RadToDMS(FwdAzi,1),9); // moon azimuth } // end of 'if(tb == 7 || a7 & 4) {' } // end of T7moon() /* DISPLAY THE VARIABLES FOR TRACKING ANTENNA FOR MOONBOUCE COMMUNICATION Called from T7butClick() and T7show(). */ void T7showTrack() { clearTab(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,210,70,"MOON-BOUNCE: ASTRONOMICAL STATUS",32); int v = 70, p = 20,h = 208; XSetForeground(XD,XG,BLEEN); // names colour XDrawString(XD,XW,XG,17,v ,"LOCAL CLOCK TIME",16); XDrawString(XD,XW,XG,17,v+=p,"GREEWICH MEAN TIME",18); XDrawString(XD,XW,XG,17,v+=p,"JULIAN DAY NUMBER",17); XDrawString(XD,XW,XG,h,v,"24-hour days measured from 00:00 01 Jan 2000",44); XDrawString(XD,XW,XG,17,v+=p,"MEAN SIDEREAL TIME",18); XDrawString(XD,XW,XG,h,v,"Greenwich Local Sidereal Time [short seconds]",45); XDrawString(XD,XW,XG,17,v+=p,"MOON RIGHT-ASCEN.",17); XDrawString(XD,XW,XG,h,v,"Moon's current Celestial Right-Ascension HMS",44); XDrawString(XD,XW,XG,17,v+=p,"MOON DECLINATION",16); XDrawString(XD,XW,XG,h,v ,"Same as Latitude of the Moon's sub-point",40); XDrawString(XD,XW,XG,17,v+=p,"MOON LONGITUDE",14); XDrawString(XD,XW,XG,h,v ,"Longitude of the Moon's sub-point",33); XSetForeground(XD,XG,YELLOW); // title colour v += p; FILE *F = fopen("nodes.dat","r"); NodLat = getInt(F); // latitude of base node NodLng = getInt(F); // longitude of base node fclose(F); XSetForeground(XD,XG,BLEEN); // names colour XDrawString(XD,XW,XG,17,v+=p,"BASE LATITUDE",13); XDrawString(XD,XW,XG,h,v,"Latitude of the Base Station",28); XDrawString(XD,XW,XG,17,v+=p,"BASE LONGITUDE",14); XDrawString(XD,XW,XG,h,v,"Longitude of the Base Station",29); XDrawString(XD,XW,XG,17,v+=p,"BASE DISTANCE",13); XDrawString(XD,XW,XG,h,v,"Metres from Base Station to Moon's sub-point",44); XDrawString(XD,XW,XG,17,v+=p,"MOON'S AZIMUTH",14); XDrawString(XD,XW,XG,h,v,"Bearing of the Moon from the Base Station",41); XDrawString(XD,XW,XG,17,v+=p,"MOON'S ELEVATION",16); XDrawString(XD,XW,XG,h,v,"Upward angle from the horizontal to the Moon",44); T7setGeoid(); // initialise the geoid T7moon(); // show the initial moon tracker figures T7showButs(); // display the control buttons for Tab 7 } /* CLEARS ONE OF THE TWO STACKS OF VERTICAL BUTTONS THE BAND SUB-TAB AND THE SINGLE STACK OF VERTICAL BUTTONS IN THE CHAN SUB-TAB Called from: T7moduButs() and T7bandButs(). */ void T7clearVert(int X) { // horiz start of left side of button stack int h = 114; // set 'h' to top of first button XSetForeground(XD,XG,DARK); // shade for buttons' background for(int i = 0; i < 7; i++) { // shade the background area for each button XFillRectangle(XD,XW,XG,X,h,53,21); h += 27; // move across to the next button } } /* DISPLAY THE MODULATION-TYPE BUTTONS IN THE BAND SUB-TAB Called from T7showBand() and T7vertButs(). */ void T7moduButs() { T7clearVert(20); char // annotate the vertical row of control buttons *A[] = {"CW","SSB","JT65","JT65A","JT65B","Q65","RESET"}; int a[] = {21,18,15,12,12,18,12}, // horizontal offsets for word starts b[] = { 2, 3, 4, 5, 5, 3, 5}, // number of letters in each word h = 129; // x coordinate of button block for(int i = 0; i < 7; i++) { // for each of the 8 buttons: if(m7 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else // otherwise XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,20+a[i],h,A[i],b[i]); h += 27; // move across to the next button } } /* DISPLAY THE RIGHT-MOST VERTICAL STACK OF 'BAND SELECTOR' BUTTONS IN THE BAND SUB-TAB. Called from: T7bandSrch(), T7showBand(), T7vertButs().*/ void T7bandButs() { T7clearVert(87); char // annotate the vertical row of control buttons *A[] = {"2metre","70cm","23cm","13cm","5cm","3cm","SCAN"}; int a[] = { 9,15,15,15,18,18,15}, // horizontal offsets for word starts b[] = { 6, 4, 4, 4, 3, 3, 4}, // number of letters in each word h = 129; // h coordinate of button block for(int i = 0; i < 7; i++) { // for each of the 8 buttons: if(b7 & 1 << i) // if this button is bright [on] XSetForeground(XD,XG,WHITE); // set colour for bright lettering else // otherwise XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,87+a[i],h,A[i],b[i]); h += 27; // move across to the next button } } /* DISPLAY THE TWO SIDE-BY-SIDE CONTROL BUTTONS IN THE 'CHAN' SUB-TAB Called by: T7chanSrch(), T7showChan(), T7chanClick(). */ void T7chanButs() { if(tb != 7) return; // bail out if MOON tab not currently visible int h = 20; // set 'h' to left side of first button const char // annotate horizontal row of control buttons *A[] = {"RESET","CALL"}; const int a[] = {12,15}, // horizontal offsets for word starts b[] = { 5, 4}; // number of letters in each word for(int i = 0; i < 2; i++) { // for each of the 2 buttons: XSetForeground(XD,XG,DARK); // set colour for button background XFillRectangle(XD,XW,XG,h,276,53,21); if(w7 & 1 << i) // if this button is 'on' XSetForeground(XD,XG,WHITE); // set colour for bright lettering else XSetForeground(XD,XG,GREY); // set colour for dull lettering XDrawString(XD,XW,XG,h + a[i],291,A[i],b[i]); h += 59; // move across to next button } } /* DISPLAY THE APPROPRIATE MESSAGE AT THE TIME IN THE MESSAGE FIELD OF THE BAND SUB-TAB. Called from: T7bandSrch(), T7showBand(), T7vertButs(). */ void T7bandScan() { int b[] = { // SIMULATED QUIET DOWN-CHANNEL FREQUENCIES 144655, // for 2-metre band 437240, // for 70 cm band 1244240, // for 23 cm band 2362250, // for 13 cm band 5657285, // for 5 cm band 10024437 // for 3 cm band }; if(b7 & 64) { // if the SCAN button is on T7wipeScan(); XSetForeground(XD,XG,YELLOW); // message colour XDrawString(XD,XW,XG,165,291,"SEEKING BEST FREE CHANNEL...",28); } else { s7 = 2; // set that scan is completed T7wipeScan(); if(c7 == GREEN) { // if a quiet receiving channel found for(int i = 0; i < 6; i++) // acquire its frequency if(b7 & 1 << i) { f7 = b[i]; break; } XSetForeground(XD,XG,c7); // new message colour XDrawString(XD,XW,XG,165,291,"Best free DOWN-LINK channel found:",34); XDrawString(XD,XW,XG,371,291,showInt(f7,8,0),8); // show frequency XDrawString(XD,XW,XG,426,291,"kHz",3); } else { f7 = 0; u7 = 0; XSetForeground(XD,XG,c7); // new message colour XDrawString(XD,XW,XG,165,291,"NO PATH POSSIBLE: SEE BELOW.",28); } } } /* SHOW THE PROGRESS MESSAGE FOR THE ACQUISITION OF AN UP-CHANNEL FREQUENCY Called from: T7chanSrch(), T7showChan(), T7chanClick(). */ void T7chanScan() { if(!(a7 & 16)) return; // don't display if not in CHAN sub-tab if(w7 & 2) { // if timer is running T7wipeScan(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,165,291,"REQUESTING UP-CHANNEL FREQUENCY...",34); } else { T7wipeScan(); if(c7 == GREEN) { // if moon id above the horizon XSetForeground(XD,XG,GREEN); // new message colour XDrawString(XD,XW,XG,165,291,"UP-CHANNEL FREQUENCY RECEIVED",29); XDrawString(XD,XW,XG,17,260,showInt(u7,8,0),8); } else { f7 = 0; u7 = 0; XSetForeground(XD,XG,c7); // new message colour XDrawString(XD,XW,XG,165,291,"NO SIGNAL: SEE BELOW.",21); } } } /* TIMER FOR SIMULATING SEARCH FOR BEST FREE CHANNEL Called from: T7vertButs() and secTime(). */ void T7bandSrch() { if(b7 & 64) // if the scan button is on if(t7 > 0) // if the timer has not expired t7--; // decrement the timer else { // otherwise b7 &= ~64; // set the SCAN button to 'off' T7bandButs(); // re-display the vertical BAND button states T7bandScan(); // show the seeking clear channel message } } /* TIMER FOR SIMULATING WAIT FOR UP-CHANNEL FREQUENCY Called from T7chanClick() and secTime(). */ void T7chanSrch() { if(w7 & 2) // if the CALL button is on if(x7 > 0) // if the reply wait timer not finished x7--; // decrement it else { // otherwise v7 = 1; // set 'up-channel frequency acquired' flag w7 &= ~2; // set the CALL button off T7chanButs(); // re-display the CALL button state T7chanScan(); // show the waiting for up-channel frquency } } /* DISPLAY THE GENERAL CONTENT OF THE 'BAND' SUB-TAB Called from: T7butClick() and T7show(). */ void T7showBand() { char *s[] = { "If the moon is up, and high enough to support a ", "moon-bounce link, then proceed as follows: ", "1) Select the transmission mode you want by clicking", " the appropriate MODULATION button in left column.", "2) Select the receiving BAND you want by clicking ", " the appropriate band button in the right column. ", "3) Click the SCAN button to initiate scanning for ", " the best [quietest] channel for the 'down' link. ", "If and when a clear frequency is shown below, go to ", "the LINK tab and initiate the link connection. ", }; int x = 70, t[] = {0,5,0,5,0,5,0,5,0,0}; clearTab(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,17, 70,"MOON-BOUNCE RECEIVER",20); XDrawString(XD,XW,XG,17,100,"MODULATION --BAND--",20); XSetForeground(XD,XG,BLEEN); // text colour for(int i = 0; i < 10; i++) { XDrawString(XD,XW,XG,165,x,s[i],52); x += 19 + t[i]; } T7moduButs(); // re-display vertical column of MODULATION-type buttons T7bandButs(); // re-display the vertical column of BAND buttons if(s7 > 0) // is scan has been completed T7bandScan(); // display the 'best fequency found' message T7moonUp(); // show message stating current status of the moon T7showButs(); // display the control buttons for Tab 7 } /* DISPLAY THE GENERAL CONTENT OF THE 'CHAN' SUB-TAB: call and ask for up- channel, inform down-channel frequency, accept and acknowledge up-channel frequency. Called from: T7chanClick(), T7butClick(), T7show(). */ void T7showChan() { int cf[] = { // CALLING FREQUENCIES [in kilohertz] 145300, // 2 metre 438000, // 70 cm 1296100, // 23 cm 2361500, // 13 cm 5656600, // 5 cm 10025000 // 3 cm }, uf[] = { // ALLOCATABLE UP-CHANNEL FREQUENCIES FOR EACH BAND 145400, // kHz 437200, 1244300, 2362000, 5657000, 10024333 }, n[] = {12,10,10,10,9,9}, // lengths of the following annotations h = 115; // initial height [y-coord] for calling frequencies char *s[] = { // CALLING FREQUENCY ANNOTATIONS "kHz 2 metre", "kHz 70 cm", "kHz 23 cm", "kHz 13 cm", "kHz 5 cm", "kHz 3 cm" }, *t[] = { // EXPLANATORY TEXT FOR THE 'CHAN' TAB "If the DOWN-FREQuency is zero, first go to the BAND", "sub-tab and acquire a clear down-channel frequency.", "Then click on the CALL button below. This causes a ", "request to be sent, on the highlighted calling fre-", "quency in the list on the left, to the remote node ", "for an up-channel frequency on which it can receive", "transmissions from here. If an up-channel frequency", "is not forthcoming on the first attempt, click on ", "the RESET button and click the CALL button again. " }; clearTab(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,17,70,"MOON-BOUNCE UP-CHANNEL REQUEST TAB",34); XSetForeground(XD,XG,YELL); // title colour XDrawString(XD,XW,XG,17,95,"CALLING FREQUENCIES",19); for(int i = 0; i < 6; i++) { XSetForeground(XD,XG,GREY); // title colour if(b7 & 1 << i) { u7 = uf[i]; XSetForeground(XD,XG,WHITE); // title colour } XDrawString(XD,XW,XG,17,h,showInt(cf[i],8,0),8); XDrawString(XD,XW,XG,70,h,s[i],n[i]); h += 20; } XSetForeground(XD,XG,YELL); // title colour XDrawString(XD,XW,XG,17+54,240,"kHz DOWN-FREQ",13); XDrawString(XD,XW,XG,17+54,260,"kHz UP-FREQ",11); XSetForeground(XD,XG,GREEN); // title colour XDrawString(XD,XW,XG,17,240,showInt(f7,8,0),8); XSetForeground(XD,XG,BLEEN); // title colour h = 95; for(int i = 0; i < 9; i++) { XDrawString(XD,XW,XG,165,h,t[i],51); h += 20; } if(v7 > 0) T7chanScan(); T7chanButs(); T7showButs(); // display the control buttons for Tab 7 } void T7showSpare() { // SPARE SUB-TAB: called from T7butClick and T7show() clearTab(); XSetForeground(XD,XG,YELLOW); // title colour XDrawString(XD,XW,XG,17,70,"MOON-BOUNCE SPARE",17); T7showButs(); // display the control buttons for Tab 7 } /* DISPLAYS THE CONTENT OF THE GEOID TAB. Highlights the currently selected selection mode and geoid. Called from only 1 place each in T7click(), T7auto(), T7butClick() and T7show(). */ void T7showGeoid() { if(tb != 7) return; // don't display if we're not in Tab 7 clearTab(); char *S1[] = { "Geoid selection can be MANUAL","or AUTOMATIC. The selection", "mode currently effective is","the highlighted [bright] one.", "You can change selection mode","by clicking on MANUAL or AUTO-", "MATIC. The GEOID currently in","use by the Vincenty distance", "& bearing computations is the","one highlighted on the right.", "When in MANUAL selection mode,","a different geoid can be sel-", "ected by clickling on its name." }, *S2[] = { // Geoid Select Menu "GRS80/WGS84 (NAD83)","Clark 1866 (NAD27)","Airy 1858","Airy Modified", "Australian National", "Bessel 1841", "Clark 1880", "Everest 1830", "Everest Modified","Fisher 1960","Fisher 1968","Hough 1956", "International (Hayford)","Krassovsky 1938","NWL-9D (WGS 66)", "South American 1969","Soviet Geod System 1985","WGS 72" }; int L1[] = {29,27,27,29,29,30,29,28,29,29,30,29,31}, L2[] = {19,18,9,13,19,11,10,12,16,11,11,10,23,15,16,19,23,6}, c = GREY, d = GREY; if(Gauto == 1) // if in AUTOMATIC mode d = WHITE; // highlight the word AUTOMATIC else // else, must be in MANUAL mode c = WHITE; // so highlight the word MANUAL XSetForeground(XD,XG,c); XDrawString(XD,XW,XG,17,83,"MANUAL",6); XSetForeground(XD,XG,d); XDrawString(XD,XW,XG,17,101,"AUTOMATIC",9); // DISPLAY THE INSTRUCTION TEXT IN GREY XSetForeground(XD,XG,BLEY); int v = 129; // vertical coord of first line of instructions text for(int i = 0; i < 13; i++) { XDrawString(XD,XW,XG,17,v,*(S1 + i),*(L1 + i)); v += 18; } // DISPLAY THE LIST OF GEOIDS, HIGHLIGHTING THE SELECTED ONE v = 63; // vertical coord of first line of geoids list for(int i = 0; i < 18; i++) { if(i == Geoid) XSetForeground(XD,XG,WHITE); else XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,255,v,*(S2 + i),*(L2 + i)); v += 17; } // SHOW THE TWO LIST [MENU] TITLES XSetForeground(XD,XG,YELLOW); XDrawString(XD,XW,XG,145,63,"AVAILABLE GEOIDS:",17); XDrawString(XD,XW,XG,17,63,"GEOID SELECTION",15); T7showButs(); } /* HANDLES CLICKS TO THE BAND SUB-TAB'S VERTICAL BUTTONS. Called from only one place in T7bandColumn(). */ void T7vertButs(int i) { if(i < 7) T7wipeScan(); // wipe the SCAN message area switch(i) { // CHECK IF THE CLICK WAS ON ONE OF THE VERTICAL BAND BUTTONS case 0: if(!(b7 & 1)) { // if the 2metre button is off b7 |= 1; // switch on 2metre button b7 &= ~62; // switch off all other BAND buttons } break; case 1: if(!(b7 & 2)) { // if the 70cm button is off b7 |= 2; // switch it on b7 &= ~61; // switch off all other BAND buttons } break; case 2: if(!(b7 & 4)) { // if the 23 button is off b7 |= 4; // switch it on b7 &= ~59; // switch off all other BAND buttons } break; case 3: if(!(b7 & 8)) { // if the 13cm button is off b7 |= 8; // switch it on b7 &= ~55; // switch off all other BAND buttons } break; case 4: if(!(b7 & 16)) { // if the 5cm button is off b7 |= 16; // switch it on b7 &= ~47; // switch off all other BAND buttons } break; case 5: if(!(b7 & 32)) { // if the 3cm button is off b7 &= ~31; // switch it on } break; case 6: if(b7 & 64) // if the SCAN button is on b7 &= ~64; // switch it off else { // otherwise b7 |= 64; // switch it on t7 = 9; // set channel search timer to 9 seconds } s7 = 1; // set 'scan in progress' T7bandScan(); // show the 'seeking clear channel' message break; // IF THE CLICK WAS ON ONE OF THE VERTICAL MODULATION-TYPE BUTTONS: case 7: // CW if(!(m7 & 1)) { // if the CW button is off m7 |= 1; // switch it on m7 &= ~62; // switch off all other buttons } break; case 8: // SSB if(!(m7 & 2)) { // if the SSB button is off m7 |= 2; // switch it on m7 &= ~61; // switch off all other buttons } break; case 9: // JT65 if(!(m7 & 4)) { // if the JT65 button is off m7 |= 4; // switch it on m7 &= ~59; // switch off all other buttons } break; case 10: // JT65A if(!(m7 & 8)) { // if the JT65A button is off m7 |= 8; // switch it on m7 &= ~55; // switch off all other buttons } break; case 11: // JT65B if(!(m7 & 16)) { // if the JT65B button is off m7 |= 16; // switch it on m7 &= ~47; // switch off all other buttons } break; case 12: // Q65 if(!(m7 & 32)) { // if the Q65 button is off m7 |= 32; // switch it on m7 &= ~31; // switch off all other buttons } break; case 13: // RESET b7 |= 64; // switch off SCAN button s7 = 0; // scanning inactive t7 = 0; // with the SCAN timer set to zero f7 = 0; // zero the down-channel frequency T7bandSrch(); // scan timer T7wipeScan(); break; } T7bandButs(); // re-displat the states of the band buttons T7moduButs(); // re-displat the states of the modulation buttons } /* DETERMINES WHICH BUTTON WAS CLICKED IN THE PRE-DETERMINED COLUMN Called only from T7bandClick() below. */ void T7bandColumn(int q) { int y = 113; // y-coord of top of top button for(int i = 0; i < 7; i++) { // find in which button the click occurred if(my > y && my < y+21) { // if it occurred within the 'i'th button T7vertButs(i + q); // go process the vertical button click break; // and break out of the for() loop } y += 27; // drop down to try the next button } } /* MOUSE CLICK OCCURRED ON ONE OF THE BUTTONS IN ONE OF THE VERTICAL COLUMNS OF BUTTONS IN BAND SUB-TAB: determines in which of the two vertical col- umns of buttons the click occurred. Called only from T7vertClick(). */ void T7bandClick() { if(mx > 16 && mx < 70) { // horiz limits of vertical BAND buttons if(my > 113 && my < 298) // vertical limits of BAND buttons T7bandColumn(7); // find which BAND button was clicked } else if(mx > 82 && mx < 136) { // horiz limits of vertical BAND buttons if(my > 113 && my < 298) // vertical limits of MODE buttons T7bandColumn(0); // find which MODE button was clicked } } /* A CLICK OCCURRED ON ONE OF THE TWO BUTTONS IN THE 'CHAN' SUB-TAB Called only from T7vertClick(). */ void T7chanClick() { if(my < 276 || my > 296) // outside vertical limits of buttons return; // so exit if(mx > 19 && mx < 73) { // if within horiz bounds of RESET button v7 = 0; // reset 'up-channel frequency acquired' flag x7 = 0; // set up-channel freq acquisition timer to zero u7 = 0; // set the u-channel frequency to zero T7chanSrch(); // kill the CALL timer T7wipeScan(); // wipe the adjacent message T7showChan(); w7 &= ~2; // dim the CALL button } else if(mx > 86 && mx < 140) { // if within horiz bounds of CALL button if(f7 > 0) { // if a down-channel frequency has been acquired w7 |= 2; // brighten the CALL button x7 = 8; // set up-channel frequency request timer T7chanScan(); // show the waiting for up-channel frquency } else { XSetForeground(XD,XG,RED); // new message colour XDrawString(XD,XW,XG,165,291,"NO DOWN-CHANNEL FREQUENCY",25); } } T7chanButs(); // re-display the button states } /* MOUSE CLICK HANDLER FOR TAB 7. Deals with clicks within vertical buttons displayS in the BAND and CHAN sub-tabs. Called only from: T7click() .*/ void T7vertClick() { if(a7 & 8) // if Tab 7's BAND button is illuminated T7bandClick(); // click occurred in the BAND display if(a7 & 16) // if Tab 7's CHAN button is illuminated T7chanClick(); // click occurred in the CHAN display } /* THE MOUSE WAS CLICKED WITHIN THE MOON TAB. Called from 2 places in the Mouse Events Handler MEH(). */ void T7click() { if(a7 & 8 || a7 & 16) { // if mouse clicked in MOON sub-tab BAND or CHAN T7vertClick(); // check vertical buttons in BAND sub-tab return; // and exit } else if(!(a7 & 64)) return; // exit if not GEOID sub-tab int h = 51, // top of first click area n = -1; // number of clicked name if(mx > 144 && mx < 198 && my > 85 && my < 107) // HELP buton clicked helpPage("navigation/nav_full.html#geoid" ); else if(mx > 253 && mx < 483) { // if within x-limits of Geoid names for(int i = 0; i < 18; i++) { // for each geoid name if(my > h && my < h + 15) { // if click was on this name n = i; break; // exit with i = geoid index number } h += 17; // drop down to next line } if(n != -1) { // click was on a geoid name Geoid = n; // set number of newly selected geoid T7setGeoid(); // set up the geoid parameters } } else if(mx > 14 && mx < 75) { // if within x-limits of mode selector if(my > 71 && my < 86) // if click was on MANUAL Gauto = 0; // set to manual else if(my > 89 && my < 104) // if click was on AUTOMATIC Gauto = 1; // set to automatic } T7showGeoid(); // re-display the Geoid Tab content } /* Proposed function to AUTOMATICALLY SELECT THE BEST GEOID to use according to the base node's current position on the planet. This current implement- ation is very rudimentary. The applicable zones for each geoid need to be properly researched and constructed. */ void T7auto() { // not currently called from anywhere if(Gauto == 0) return; // return if geoid selection is set to manual int x = 0; // for the geiod index number if(NodLat < 0) { // If base node is in the southern hemisphere: /* if the base node's longitude is between 30°W and 150°W use the South American geoid else use the Australian geoid */ if(NodLng < -0.523598776 && NodLng < -2.617993878) x = 4; else x = 15; } else { // Else base node is in the northern hemisphere: /* if the base node's longitude is between 30°W and 150°W, use the North American geoid else if its longitude is between 45°E and 170°E use the Soviet geoid. */ if(NodLng < -0.523598776 && NodLng < -2.967059728) x = 0; else if(NodLng > 0.785398163 && NodLng < 0.296705973) x = 16; } if(Geoid != x) { // if the selected geoided has been automatically changed Geoid = x; // remember which geoid is selected for next time through T7setGeoid(); // set up the parameters of the new geoid T7showGeoid(); // re-display the geiod selector menu } } /* HANDLES A CLICK MADE ON ONE OF THE MOON TAB'S BOTTOM BUTTONS Called from only 1 place in T7show() and MEH(). */ void T7butClick(int x) { switch(x) { case 0: if(!(a7 & 1)) { // if the TRACK button is dim a7 &= ~0xFB; // kill all buttons except STEER a7 |= 1; // brighten the TRACK button T7showTrack(); // display the moon tracking data } break; case 1: if(!(a7 & 2)) { // if the GRAPH button is dim a7 &= ~0xFB; // kill all buttons except STEER a7 |= 2; // brighten it T7showGraph(); // display the moon tracking data } break; case 2: if(a7 & 4) // if the STEER button is bright a7 &= ~4; // dim it else // else, if it is dim a7 |= 4; // brighten it break; case 3: if(!(a7 & 8)) { // if the BAND button is dim a7 &= ~0xFB; // kill all buttons except STEER a7 |= 8; // brighten it T7showBand(); // display the moon tracking data } break; case 4: if(!(a7 & 16)) { // if the CHAN button is dim a7 &= ~0xFB; // kill all buttons except STEER a7 |= 16; // brighten it T7showChan(); // display the moon tracking data } break; case 5: if(!(a7 & 32)) { // if the SPARE button is on a7 &= ~0xFB; // kill all buttons except STEER a7 |= 32; // brighten it T7showSpare(); // display the moon tracking data } break; case 6: if(!(a7 & 64)) { // if the GEOID button is dim a7 &= ~0xFB; // kill all buttons except STEER a7 |= 64; // brighten the GEOID button T7showGeoid(); // show the TRACK Tab's content } break; case 7: // HELP button was clicked if(!(a7 & 128)) { // if the HELP button is dim a7 |= 128; // brighten the HELP button helpPage("radio/com.html#admin"); } } T7showButs(); // re-display the buttons } /* DISPLAYS THE APPROPRIATE MOON SUB-TAB IN THE EVENT OF A SUB-TAB SELECTION OR GENERAL RE-EXPOSURE UPON RETURN FROM MINIMIZED STATE OR FROM ANOTHER DESKTOP. Called only from one place in showTab(). */ void T7show() { if(a7 & 1) T7showTrack(); else if(a7 & 2) T7showGraph(); else if(a7 & 8) T7showBand(); else if(a7 & 16) T7showChan(); else if(a7 & 32) T7showSpare(); else if(a7 & 64) T7showGeoid(); else T7butClick(0); } // FUNCTIONS PERTAINING TO TAB 2: HAMS---------------------------------------- /* SORTS 'hams.idx' INTO ALPHABETICAL ORDER OF CALL SIGN. Called only from 1 place in T2getHams(). This function is based on the Hoare Quick-Sort. */ void T2idxSort( int L, // lowest occupied element of the array to be sorted int H, // highest occupied element of the array to be sorted FILE *F) { // file handle for 'hams.dat' int l = L, // set moving low to LOW end of partition h = H; // set moving high to HIGH end of partition if(H > L) { // if partition contains anything // get content of the mid record of 'hams.idx' unsigned long M = getLong(L + H >> 1,4,4,F); while(l <= h) { // loop through file until the indices cross /* While distance held in lowest element of Hdst[] < distance held in midway element of Hdst[], push lower sort boundary up by one element. */ while(l < H && getLong(l,4,4,F) < M) l++; /* While distance held in highest element of Hdst[] > distance held in midway element of Hdst[], pull upper sort boundary down by 1 element. */ while(h > L && getLong(h,4,4,F) > M) h--; if(l <= h) { // if low index <= high index, swap contents of /* number of the moving 'low' record | multiply by 32 [the number of bytes per record | | number of the moving 'high' record | | multiply by 32 [the number of bytes per record | | | record size = 32 bytes | | | | file handle for 'hams.idx' | | | | | */ sortSwap(l<<4,h<<4,16,F); // high & low elements of 'hams.idx' l++; // push lower sort boundary up by one element h--; // pull upper sort boundary down by one element } } if(L < h) T2idxSort(L,h,F); // sort lower partition if(l < H) T2idxSort(l,H,F); // sort upper partition } } void T2getHams() { // LOAD THE NUMBER OF HAMS ON-FILE if(e2) return; // exit if hams data already loaded FILE *F = fopen("hams.dat","r"); // open 'hams.dat' file for writing fseek(F,8,SEEK_SET); // start byte of record 0's 'Dst' field c2 = getInt(F); // set the number of hams on-file fclose(F); // close 'hams.dat' } /* SORTS 'hams.dat' INTO ALPHABETIC ORDER OF NAME. Called only from one place in T2regen(). This function is based on the Hoare Quick-Sort. */ void T2datSort( int L, // lowest occupied element of the array to be sorted int H, // highest occupied element of the array to be sorted FILE *F) { // file handle for 'hams.dat' int l = L, // set moving low to LOW end of partition h = H; // set moving high to HIGH end of partition if(H > L) { // if partition contains anything unsigned long M = getLong(L + H >> 1,37,6,F); // get content of its mid element while(l <= h) { // loop through file until the indices cross /* While distance held in lowest element of Hdst[] < distance held in midway element of Hdst[], push lower sort boundary up by one element. */ while(l < H && getLong(l,37,6,F) < M) l++; /* While distance held in highest element of Hdst[] > distance held in midway element of Hdst[], pull upper sort boundary down by 1 element. */ while(h > L && getLong(h,37,6,F) > M) h--; if(l <= h) { // if low index <= high index, swap contents of /* number of the moving 'low' record | multiply by 64 [the number of bytes per record | | number of the moving 'high' record | | multiply by 64 [the number of bytes per record | | | record size = 64 bytes | | | | file handle for 'hams.idx' | | | | | */ sortSwap(l<<6,h<<6,64,F); // high & low elements of 'hams.dat' l++; // push lower sort boundary up by one element h--; // pull upper sort boundary down by one element } } if(L < h) T2datSort(L,h,F); // sort lower partition if(l < H) T2datSort(l,H,F); // sort upper partition } } /* DISPLAY AND SCROLL THE HAMS LIST. Called from only 1 place each in T2show(), MW4() and MW5(). */ void T2scroll() { XSetForeground(XD,XG,BLACK); // clear the hams list display area XFillRectangle(XD,XW,XG,17,73,484,270); if(mw == 1 && f2 < c2-16) // if Mouse Wheel 1 f2++; // scroll down the list else if(mw == 2 && f2 > 0) // if Mouse Wheel 2 f2--; // scroll back up list mw = 0; // Mouse wheel event now dealt with int b = 0, // highlighting [bright] flag v = 83, // y-coord for first ham shown M = c2; // number of lines to be displayed if(c2 > 16) // only got space for 16 lines max M = 16; char c; // to hold characters beuing transferred between arrays FILE *F, *G; // file handles for the two files F = fopen("hams.dat","r"); // open the 'hams.dat' file if(a2 & 8) // if displaying in alphabetical order of call sign G = fopen("hams.idx","r"); // open the 'hams.idx' file for(int i = 0; i < M; i++) { // for each line to be displayed int x; if(a2 & 4) // if displaying in alphabetical order of name x = f2 + i; // number of required record within 'hams.dat' else { /* displaying in alphabetical order of call sign seek start byte of the required index record within 'hams.idx' | file handle for 'hams.idx' | | number of records prior to the first visible one | | | number of the 'hams.idx' record within visible range | | | | multiply by 16 [bytes per record] | | | | | from the start of 'hams.idx' | | | | | | */ fseek(G,(f2 + i) << 4,SEEK_SET); x = getInt(G); // number of required record within 'hams.dat' } int r = x << 6; // start byte in 'hams.dat' of this ham's data // SET COLOUR FOR WHETHER OR NOT THIS LINE IS TO BE HIGHLIGHTED fseek(F,r + 24,SEEK_SET); // seek start byte of marker if((b = getInt(F)) == 1) // if this is a selected ham XSetForeground(XD,XG,WHITE); // highlight it in bright white else // otherwise XSetForeground(XD,XG,BLEEN); // display it in grey // DISPLAY HAM'S LIST NUMBER, NAME, LATITUDE, LONGITUDE AND HEIGHT int Q = 17; XDrawString(XD,XW,XG,Q,v,showInt(x,3,1),3); fseek(F,r,SEEK_SET); // start byte of this record within 'hams.dat' XDrawString(XD,XW,XG,Q+=20,v,SecToDMS(getInt(F),0),10); // Lat XDrawString(XD,XW,XG,Q+=70,v,SecToDMS(getInt(F),1),10); // Lng fseek(F,+8L,SEEK_CUR); // start byte of xref 'hams.dat' XDrawString(XD,XW,XG,Q+=66,v,showInt(getInt(F),6,1),6); // XREF XDrawString(XD,XW,XG,Q+=54,v,showInt(getInt(F),7,0),7); // Frq fseek(F,+4L,SEEK_CUR); // skip the marker integer // DISPLAY THIS HAM'S CALL SIGN int k; char S[21]; for(k = 0; k < 9; k++) S[k] = fgetc(F); // copy each byte of Net into S[] XDrawString(XD,XW,XG,Q+=48,v,S,9); // display Net [network address] if(b) // if this is the selected ham for(k = 0; k < 9; k++) { // preserve the call sign in global array g2[] if((c = S[k]) == ' ') // substituting spaces for underscores c = '_'; // and place it within the text editor g2[k + 6] = c; // command line for the NOTES button } // DISPLAY THIS HAM'S LOCATION NAME int l = 0; // inset [in pixels] to right-justify the location name for(k = 0; k < 20; k++) { // copy the upto 20 bytes of if((c = fgetc(F)) == 0) // the location name int S[] break; // break out if end of name is encountered S[k] = c; // add the character to the location name } XDrawString(XD,XW,XG,Q+=60,v,S,k); // display the location name v += 17; // drop to next lower line } if(a2 & 8) // if displaying in alphabetical order of call sign fclose(G); // close 'hams.idx' fclose(F); // close 'hams.dat' // IF MORE THAN 16 HAMS IN THE ROUTE WE NEED TO DISPLAY A SCROLL BAR if(c2 < 17) return; // exit if no more than '16 lines to dispolay int h = 4352 / c2; // height of highlighted scroll bar v = f2 * (272-h) / (c2-16); // dull above the highlighted part XSetForeground(XD,XG,DARK); // show full length scroll bar in grey XFillRectangle(XD,XW,XG,475,71,8,272); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,475,71+v,8,h); } /* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF HAMS TAB Called from 1 place each in T2show() and T2butClick(). */ void T2Buts() { if(tb != 2) return; // bail out if STNS tab not currently visible showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate horizontal row of control buttons *A[] = {"REGEN","LISTEN","NAME","C'SIGN","NOTES","","","HELP"}; const int a[] = {12,9,15,9,12,0,0,15}, // horizontal offsets for word starts b[] = {5, 6, 4,6, 5,0,0, 4}; // number of letters in each word int h = 17,c; // x coordinate of button block for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a2 & 1 << i) c = WHITE; // colour for highlighted button else c = GREY; // colour for dim button [default] XSetForeground(XD,XG,c); // colour for the lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59;; // move across to next button } } /* DISPLAY THE TITLES OF THE HAMS LIST. Called from only 1 place each in showTab(). */ void T2show() { if(tb != 2) return; // bail out if NETS tab not currently visible XSetForeground(XD,XG,BLACK); // clear the hams list display area XFillRectangle(XD,XW,XG,17,38,484,320); int Q = 17; // y-coord of column headings char *S = "REF LATITUDE LONGITUDE -XREF- FREQUENCY " "CALL SIGN ----------NAME----------"; XSetForeground(XD,XG,YELLOW); // show column headings in yellow XDrawString(XD,XW,XG,Q,63,S,77); e2 = 0; T2getHams(); T2scroll(); T2Buts(); // display the tab's buttons } /* LOAD THE HAMS INFORMATION LINE-BY-LINE FROM THE HAMS FILE 'hams.txt'. STORE IT IN 'hams.dat' TOGETHER WITH DISTANCE AND BEARING INFORMATION. Format for file 'hams.dat': Each 64-byte record contains: OFFSET STORAGE CONTENT 0 NNNN latitude 4 NNNN longitude 8 NNNN distance [in rec 0 is number of recs in file] 12 NNNN bearing 16 NNNN mktr reference 20 NNNN frequency 24 NNNN marker 28 AAAAAAAAA call sign 37 AAAAAAAAAAAAAAAAAAAA name 52 SSSSSSSSSSSS 12 spare bytes Called from only one place in T2butClick(). */ void T2regen() { FILE *F = fopen("hams.txt","r"); // open the 'hams.txt' file for reading showMsg(8,WHITE); // show "Regenerating hams data" message if(F == NULL) { // if can't open waypoints data file printf("Couldn't find 'hams.txt'."); return; } // BYPASS THE FILE'S HEADING COMMENTS int n = 0, c, f = 0, i, j, m = 0; while(!feof(F)) { // while we haven't yet reached the end of the file c = fgetc(F); // get the next character from the file 'hams.txt' m++; // increment the char count to next char to be read if(f == 1) { // if we are inside a comment line if(c == '\n') { // if we have reached the end of the comment line f = 0; // reset the 'in comment' flag n = 0; // reset the line's character count } } else { // otherwise we must be outside a comment line if(c == '#') // must be at the beginning of a comment line f = 1; // so set the 'in comment' flag and loop back else break; // break out of the header bypass loop } } fseek(F,m-1,SEEK_SET); // back-track to character just read FILE *G = fopen("hams.dat","w"); // open 'hams.dat' file for writing // READ IN THE HAMS DATA AND WRITE IT TO 'hams.dat' int P = 0; // file pointer for 'hams.dat' file double X, Y; // lat and lng of the base ham for(c2 = 0; c2 < 200; c2++) { // For all possible hams on file if(feof(F)) break; // terminate loop at end of file fseek(G,P,SEEK_SET); /* set 'hams.dat' file pointer to start of 1st/next 64-byte record Input the latitude, longitude [in integral seconds of arc] and height [in metres] as strings from the 'hams.txt' file, convert them all to integers and store all 3 in the 'hams.dat' file. */ int x = getLatLngHgt(7,F), // latitude [seconds of arc] y = getLatLngHgt(8,F); // longitude [seconds of arc] putInt(x,G); // save latitude to 'hams.dat' putInt(y,G); // save longitude to 'hams.dat' if(c6 == 0) { // if 1st ham in the file X = x * RPS; // base ham's latitude in radians Y = y * RPS; // base ham's longitude in radians putInt(0,G); // base ham has zero distance putInt(0,G); // base ham has zero bearing } else { // for all other hams: /* Compute the distance and bearing of the current ham from the base ham then store them as rounded integers in the hams structure. */ int e = VSGIP(X,Y,x*RPS,y*RPS); // compute distance and bearing if(e > 0) // if an error occurs showMsg(e+25,RED); // display the Vincenty error putInt((int)rint(VinDst),G); // put ham's distance in 'hams.dat' putInt((int)rint(FwdAzi*DPR),G); // put ham's bearing in 'hams.dat' } putInt(getLatLngHgt(6,F),G); // store height in 'hams.dat' file putInt(getLatLngHgt(8,F),G); // store frequency in 'hams.dat' file putInt(0,G); // store marker as zero [un-marked] fgetc(F); // skip the space character for(i = 0; i < 9; i++) // For each of 9 call sign chars fputc(fgetc(F),G); // store char to 'hams.dat' fgetc(F); // skip the space character for(i = 0; i < 22; i++) { // For each of upto 20 possible chars if((c = fgetc(F)) == '\n') // if it's a new-line character, add a break; // break out of loop if it's a 'CR' fputc(c,G); // store char to 'hams.dat' } if(i < 21) // if there were less than 20 characters in the location fputc(0,G); // name, put in a terminating NULL character. P += 64; // advance 'hams.dat' file pointer to start of next record } fclose(G); // close the 'hams.dat' file. fclose(F); // close the 'hams.txt' file. e2 = 1; // indicates that hams have been loaded f2 = 0; // zero the scroll display bias c2--; // total number of hams [gets over-incremented in loop] /* Open 'hams.dat' again, this time in updating mode. Set its file pointer to the start of the field that holds the number of records in the file and store the number of records in this field. */ F = fopen("hams.dat","r+"); T2datSort(1,c2-1,F); // sort 'hams.dat' into order of ascending call sign fseek(F,8,SEEK_SET); // the 'number of records in the file' field starts putInt(c2,F); // at Byte 8 of the first record of the file fclose(F); // close 'hams.dat' e2 = 0; // clear the 'hams already loaded' flag // GENERATE & SORT HAMS INDEX FILE 'hams.idx' FROM DATA IN 'hams.dat' int g = 28; // start byte of call sign field of 1st record of 'hams.dat' f = 0; // start byte of 1st record of 'hams.idx' G = fopen("hams.dat","r"), // open for read-only F = fopen("hams.idx","w"); // open for create and write for(i = 0; i < c2; i++) { // for each record on-file: fseek(F,f,SEEK_SET); // start byte of this output record putInt(i,F); // store ham's record number in index file fseek(G,g,SEEK_SET); // start byte of input record's name field for(j = 0; j < 9; j++) { // For each of upto 20 possible chars if((c = fgetc(G)) == 0) // if it's a NULL terminator break; // break out of loop fputc(c,F); // store char to 'hams.idx' } if(j < 9) // if there were less than 9 characters in the location fputc(0,F); // name, put in a terminating NULL character. g += 64; // advance to the next input record f += 16; // advance to the next output record } fclose(G); // close 'hams.dat' fclose(F); // close 'hams.idx' F = fopen("hams.idx","r+"); // open for reading and modifying T2idxSort(1,c2-1,F); // sort 'hams.idx' into alpabetical order of name fclose(F); // close 'hams.idx' T2show(); // re-display the hams list } void T2butClick(int i) { // called from only 1 place in MEH() switch(i) { case 0: if(!(a2 & 1)) { // if the REGEN button is dim a2 |= 1; // brighten the REGEN button T2regen(); // regenerate the database a2 &= ~1; // dim the REGEN button } break; case 1: if(a2 & 2) { // if the LISTEN button is bright a2 &= ~2; // dim it T3updtOutput(1,0); // send 'audio off' message } else { a2 |= 2; // brighten it T3updtOutput(0,d2); // send 'getfrq' message T3updtOutput(2,0); // send 'audio on' message } break; case 2: if(!(a2 & 4)) { // if the NAME button is dim a2 |= 4; // brighten it a2 &= ~8; // dim the C'SIGN button T2scroll(); // re-display the 'hams list' } break; case 3: if(!(a2 & 8)) { // if the C'SIGN button is dim a2 |= 8; // brighten it a2 &= ~4; // dim the NAME button T2scroll(); // re-display the 'hams list' } break; case 4: if(a2 & 16) // if the NOTES button is bright a2 &= ~16; // dim it else { // else, if it's dim a2 |= 16; // brighten it system(g2); // open a file for notes on this ham } break; case 7: // HELP button was clicked helpPage("radio/com.html#hams"); } T2Buts(); // re-display the buttons } /* HANDLE A CLICK MADE ON THE HAMS LISTING. Highlights [selects] one ham displayed in the hams list. Called only from MEH() */ void T2click() { if(mx < 17 && mx > 482) // if outside horizontal limits of list return; // return without doing anything FILE *F, *G; // file handles for the two files F = fopen("hams.dat","r+"); // open the 'hams.dat' file for updating int i; // loop variable for(i = 0; i < c2; i++) { // CLEAR ALL CURRENTLY MARKED HAMS /* number of the ham data line within the visible range | number of the first visible ham data line | | multiply by 64 bytes per ham record | | | add offset of marker field within record | | | | */ fseek(F,(i + f2 << 6) + 24,SEEK_SET); if(getInt(F)) { // if this ham has been marked fseek(F,-4L,SEEK_CUR); // back-up 4 bytes [getInt() advances 4] putInt(0,F); // and clear his marker byte } } if(a2 & 8) // if displaying in alphabetical order of call sign G = fopen("hams.idx","r"); // open the 'hams.idx' file int y = 70, // y-coord of first ham data line z = 0; // 1 = ham identified // IDENTIFY THE LINE NUMBER, WITHIN THE VISIBLE LIST, THAT HAS BEEN CLICKED for(i = 0; i < 16; i++) { // for each of the 16 visible data lines if(my > y && my < y+16) { // vertical limits of mouse click z = 1; // mark ham as identified break; // bail out of for() loop } y += 17; // drop to next ham data line } // IF SUCCESSFULLY IDENTIFIED, GET HIS FREQUENCY AND MARK HIS RECORD if(z) { int x; // for number of required record within 'hams.dat' if(a2 & 4) // if displaying in alphabetical order of name x = f2 + i; // number of required record within 'hams.dat' else { /* displaying in alphabetical order of call sign seek start byte of the required index record within 'hams.idx' | file handle for 'hams.idx' | | number of records prior to the first visible one | | | number of the 'hams.idx' record within visible range | | | | multiply by 16 [bytes per record] | | | | | from the start of 'hams.idx' | | | | | | */ fseek(G,(f2 + i) << 4,SEEK_SET); x = getInt(G); // number of required record within 'hams.dat' } /* number of required record within 'hams.dat' | multiply by 64 bytes per ham record | | add offset of frequency field within record | | | */ fseek(F,(x << 6) + 20,SEEK_SET); d2 = getInt(F); // get this ham's listening frequency putInt(1,F); // set the marker byte if(a2 & 8) // if selecting from list in alphabetical fclose(G); // order of call sign, close 'hams.idx' fclose(F); // close the 'hams.dat' file a2 &= ~2; // dull the LISTEN button T2scroll(); // re-display the hams as marked } } // FUNCTIONS PERTAINING TO TAB 1: SCAN---------------------------------------- /* DISPLAY THE FREQUENCIES PERTAINING TO A GIVEN BROADCASTER 'c1'. Called from 1 place each in T1nextFreq() and T1scroll(). */ void T1showFreqs() { XSetForeground(XD,XG,DARK); // clear the frequencies panel XFillRectangle(XD,XW,XG,182,237,301,100); if(b1 & 1) { // if scanning is currently active while(g1 >= k1 + 48) // while current operating freq is off panel k1 += 6; // to the right, scroll 1 column rightwards. while(g1 < k1) // while current operating freq of off panel k1 -= 6; // to the left, scroll 1 column leftwards. } /* SEEK START BYTE WITHIN 'stns.dat' OF FIRST FREQUENCY TO BE DISPLAYED: file handle of 'stns.dat' | start record | | plus the display offset | | | times 4 [each frequency record is a 4-byte int] | | | | */ fseek(ff,h1 + k1 << 2,SEEK_SET); int h = 152, // pixel position of the first column v = 251, // pixel position for writing on the first row N = 48, // number of frequencies for a full panel x = i1 - k1; // number of freqs beyond the first to be displayed if(x < 48) // if this is less than a full panel, N = x; // then use it for(int i = 0; i < N; i++) { // for each frequency if(i % 6 == 0) { // if 'i' divides by the number of rows in this column h += 36; // advance to the next column v = 80 + 171; // and start at the top again. } if(g1 - k1 == i) // highlight currently operative frequency in white XSetForeground(XD,XG,WHITE); else // display all the others in grey XSetForeground(XD,XG,GREY); /* x-coord of current frequency | y-coord of current frequency | | make it 5 characters long | | | with leading spaces | | | | display length | | | | | */ XDrawString(XD,XW,XG,h,v,showInt(getInt(ff),5,0),5); /* | | display it as an integer get the current frequency converted to characters from the 'stns.dat' file */ v += 16; // drop down to the next line } // SCROLL BAR DISPLAY XSetForeground(XD,XG,DARK); // show a full length scroll bar in grey XFillRectangle(XD,XW,XG,182,343,301,8); if(i1 < 49) return; // exit if broadcaster uses 48 frequencies or less int y = i1 / 6; // find the number of complete columns of 6 freqs x = i1 % 6; // if there are some frequencies left over if(x > 0) y++; // add an extra [incomplete] column /* The width of the yellow bar is computed as follows. The width of the visible part of the list is 8 * 36 = 288. The width of the panel is 301. The width of the panel times the total width of the list is 301 * 288 = 86688. This must be divided by the width of the whole list [visible and non-visible]. The width of the list in pixels is y * 36. Thus, the width of the yellow bar is 301 * 288 / (y * 36) = 2408 / y. */ h = 2408 / y; // compute width of yellow bar /* The displacement of the yellow bar from the left edge of the panel is the difference between the panel width and the width of the yellow bar [301 - h] times the number of columns that are currently invisible to the left of the panel [k1 / 6] divided by the total number of columns minus the maximum possible number of invisible columns [y - 8] that can be moved to the left of the panel. */ v = 182 + (301 - h) * k1 / ((y << 2) + (y << 1) - 48); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,v,343,h,8); } /* FIND THE FIRST/NEXT FREQUENCY FOR THE CURRENT BROADCASTER. The List Number of the currently selected broadcaster is in c1. The switch value x can have the following values: -1: decrement to next lower frequency, 0: don't increment or decrement, +1: increment to the next higher frequency. Called from 1 place each in T1scroll(), T1show() & main() and from 2 places each in T1click() & T1butClick(). */ void T1nextFreq(int x) { if(x != 0) { g1 += x; // incr/decr the current frequency index number if(g1 >= i1) // If we've passed the last of the currently selected g1 = 0; // broadcaster's frequencies, loop back to the 1st. if(g1 < 0) // If we've gone below the first one, loop back to g1 = i1 - 1; // this broadcaster's last frequency. } /* file handle of 'stns.dat' | the number of this broadcaster's first frequency record | | + number of the current frequency within his frequency list | | | times 4 because each frequency is a 4-byte integer | | | | measured from the beginning of the file | | | | | */ fseek(ff,h1 + g1 << 2,SEEK_SET); f1 = getInt(ff); // get the currently operatinal frequency T3updtOutput(2,f1); // post the new frequency as an output command T3updtInput(0,f1); // get the received signal strength if(tb != 1) return; // don't display if not in the SCAN Tab. // DISPLAY THE BROADCAST FREQUENCY XSetForeground(XD,XG,BLACK); XFillRectangle(XD,XW,XG,248,58,34,15); XSetForeground(XD,XG,WHITE); XDrawString(XD,XW,XG,250,70,showInt(f1,5,0),5); // DISPLAY THE BROADCAST BAND NAME XSetForeground(XD,XG,BLACK); // clear the band name display area XFillRectangle(XD,XW,XG,393,58,90,15); int I = f0[0], // number of broadcast bands i, // index number of the current band bs, // band start frequency in kHz be; // band end in kHz for(i = 0; i < I; i++) { // for each of the broadcast bands bs = bn[i].os, // get its start frequency be = bs + bn[i].ow; // get its end frequency if(f1 > bs && f1 < be) // if current station freq lies between them break; // break loop: 'i' is the band index number } XSetForeground(XD,XG,WHITE); // display the band name in white XDrawString(XD,XW,XG,393,70,bn[i].N,strlen(bn[i].N)); graticule(); // display the scanner's frequency indicator graticule // DISPLAY THE BANDSCOPE SIGNALS TRACE fseek(sf,f1 - 150,SEEK_SET); // seek start byte of this graph's scope data int y = 200; // base y-coord of graph // for each of the 300 signal plots within the current graph range: for(int j = 0; j < 300; j++) { int c = fgetc(sf), // get first/next 8-bit integer from scope.dat x = 182 + j; // x-coord of this signal within graph if(j > 147 && j < 153) // if this is the station of interest XSetForeground(XD,XG,YELLOW); // show trace in bright yellow else // otherwise XSetForeground(XD,XG,GRN); // show trace in dark green XDrawLine(XD,XW,XG,x,y - c,x,y); // display its amplitude on the graph } /* MARK THE BOUNDS OF THE OFFICIAL BAND WITH A YELLOW RECTANGLE ['i' is still the band's index number] */ int xc = 332, // x-coord of centre point of graticule xb = xc - (f1 - bs), // x-coord of start of official band xe = xc + (be - f1), // x-coord of end of official band xm = 482; // x-coord of end [right edge] of graticule if(xb < 182) // if x-coord of official band start is left of the beginning xb = 182; // of the graticule, set it to the beginning of the graticule. if(xe > xm) // if x-coord of official band end is right of the end of xe = xm; // the graticule, set it to the end of the graticule. int xw = xe - xb; // width of the official band XSetForeground(XD,XG,YELL); // display band rectangle in a darker yellow XDrawRectangle(XD,XW,XG,xb,80,xw,120); T1showFreqs(); // re-display this broadcaster's frequencies } void T1scan() { // called only from main() timing loop if(b1 & 1) // if frequency scanning is active if(a1 > 0) // if the iteration counter has not yet expired a1--; // decrement it else { // otherwise T1nextFreq(1); // go to next frequency for current broadcaster} a1 = 2; // and re-prime the iteration counter for 2 seconds } } /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF SCAN TAB Called from 1 place each in T1show(), T1click() and T1butClick(). */ void T1Buts() { if(tb != 1) return; // bail out if SCAN tab not currently visible showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate the horizontal row of control buttons *A[] = {"SCAN","STOP","NEXT","PREV","LISTEN"," "," ","HELP"}; const int a[] = {15,15,15,15,9,0,0,15}, // horiz offsets for word starts b[] = { 4, 4, 4, 4,6,0,0, 4}; // number of letters in each word int h = 17; // x-coord of left side offirst button for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(b1 & 1 << i) // if the button is 'on' XSetForeground(XD,XG,WHITE); // display it's lettering in white else XSetForeground(XD,XG,GREY); // default colour for the lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; // move across to the next button } } /* DISPLAY THE SCROLLABLE LIST OF BROADCASTERS. Called from 1 place each in T1scroll(), T1show() and T1click(). */ void T1showBroadcasters() { XSetForeground(XD,XG,DARK); XFillRectangle(XD,XW,XG,140,58,26,15); // clear broadcaster number XFillRectangle(XD,XW,XG,17,78,133,274); // clear broadcasters names area XSetForeground(XD,XG,WHITE); // display broadcaster number XDrawString(XD,XW,XG,144,70,showInt(c1,3,1),3); char S[32]; // to hold a broadcaster's name int v = 92, // y-coord for first broadcaster shown M = d1, i; // total number of broadcasters on file if(d1 > 16) // only got space for 16 lines maximum M = 16; for(i = 0; i < M; i++) { // for each line to be displayed int x = e1 + i, // broadcaster number for current line r = (x + 1) << 5, // start byte of broadcaster's name c, // current character got from file j = 0; // character count of current broadcaster name /* file handle for 'broadcasters.dat' | broadcaster number for current display line [record] | | plus 1 record 0 is used for number of records in the file | | | times 32 [32 bytes per record] | | | | */ fseek(bf,(x + 1) << 5,SEEK_SET); // seek start byte of broadcaster's name while((c = fgetc(bf)) != '\0') // read-in the name of the S[j++] = c; // current broadcaster if(c1 == x) { // highlight Current Broadcaster in white XSetForeground(XD,XG,WHITE); } else // and the others in grey XSetForeground(XD,XG,GREY); if(j > 20) // display broadcaster's name j = 20; XDrawString(XD,XW,XG,17+6,v,S,j); v += 17; // drop to the next lower line } if(d1 < 16) { // If there are more than 16 broadcasters, int // we need to display a scroll bar. h = 4352 / d1; // height of highlighted scroll bar v = e1 * (272 - h) // Height of the dark part of the scroll / (d1 - 16); // bar above the highlighted part. XSetForeground(XD,XG,DARK); // show full length scroll bar in grey XFillRectangle(XD,XW,XG,157,80,8,272); XSetForeground(XD,XG,YELL); // show highlighted part of scroll bar XFillRectangle(XD,XW,XG,157,80+v,8,h); } /* file handle for 'stns.idx' [stations index file] | current broadcaster's record number | | start at record 1 [record 0 is for total number of records] | | | times 8 [2 x 4-byte integers per record] | | | | measured from the beginning of the file | | | | | */ fseek(fi,c1 + 1 << 3,SEEK_SET); h1 = getInt(fi); /* Record number, within 'stns.dat' file, of the- currently selected broadcaster's first frequency */ i1 = getInt(fi); /* Number of frequencies on which the currently selected broadcaster transmits */ } /* SCROLL EITHER THE LIST OF BROADCASTERS OR A BROADCASTER'S LIST OF FREQUENCIES. Called by T1click(), MW4() and MW5(). */ void T1scroll() { if(mw > 0) { if(mx > 16 && mx < 150 // Provided the mouse pointer is with- && my > 77 && my < 352) { // in the Broadcaster Names panel: if(mw == 1 && e1 < d1-16) // if Mouse Wheel 1 e1++; // scroll down the list else if(mw == 2 && e1 > 0) // if Mouse Wheel 2 e1--; // scroll back up list T1showBroadcasters(); // display visible part of broadcasters list } if(mx > 181 && mx < 483 // Provided the mouse pointer is with- && my > 236 && my < 337) { // in the frequencies panel: if(mw == 1 && k1 < i1-48) // if Mouse Wheel 1 k1 += 6; // scroll down the list else if(mw == 2 && k1 > 0) // if Mouse Wheel 2 k1 -= 6; // scroll back up list T1showFreqs(); // display current broadcaster's frequencies } mw = 0; // Mouse wheel event now dealt with } } /* DISPLAY THE STATIC CONTENT OF THE SCAN TAB. Called only from 1 place in showTab(). */ void T1show() { XSetForeground(XD,XG,YELL); // menu titles in yellow XDrawString(XD,XW,XG,182,70,"FREQUENCY: kHz",20); XDrawString(XD,XW,XG,357,70,"BAND:",5); XDrawString(XD,XW,XG,23,70,"NAME OF BROADCASTER",19); // DISPLAY THE LIST OF FREQUENCIES FOR CURRENT BROADCASTER d0 = 0; // select 'broadcast bands' loadBands(); // load them into struct 'bn' XSetForeground(XD,XG,YELL); // display frequency scale in yellow XDrawLine(XD,XW,XG,182,210,482,210); XDrawLine(XD,XW,XG,182,211,182,216); XDrawLine(XD,XW,XG,232,211,232,220); XDrawLine(XD,XW,XG,282,211,282,216); XDrawLine(XD,XW,XG,332,205,332,220); XDrawLine(XD,XW,XG,382,211,382,216); XDrawLine(XD,XW,XG,432,210,432,220); XDrawLine(XD,XW,XG,482,210,482,216); int v = 80 + 152; XDrawString(XD,XW,XG,171,v,"-150",4); XDrawString(XD,XW,XG,221,v,"-100",4); XDrawString(XD,XW,XG,274,v,"-50",3); XDrawString(XD,XW,XG,330,v,"0",1); XDrawString(XD,XW,XG,374,v,"+50",3); XDrawString(XD,XW,XG,421,v,"+100",4); XDrawString(XD,XW,XG,471,v,"+150",4); T1showBroadcasters(); // display the list of broadcasters T1nextFreq(0); // display frequencies with first one highlighted T1Buts(); // display the control buttons for the SCAN Tab } /* MOUSE CLICKED ON A NON-BUTTON PART OF THE SCAN TAB. Called only from 1 place in MEH(). */ void T1click() { // IF THE CLICK WAS WITHIN THE BROADCASTERS NAMES PANEL if(mx > 16 && mx < 150 && my > 77 && my < 352) { int j = 96, // base of the first visible broadcaster name i; // For each of the 16 currently visible broadcaster names: for(i = 0; i < 16; i++) { if(my < j) // if the mouse is above this broadcaster name break; // break out of loop with 'j' = name's base y-coord j += 17; // drop down to the base of the next broadcaster name } if(i < 16) { // provided a valid broadcaster name was clicked c1 = i // broadcaster number [0 to 255] = 'i' + + e1; // number of top displayed line in list of broadcasters mw = 0; // set to 'not a mouse-wheel initiated operation' T1scroll(); // re-display the list with clicked name highlighted } k1 = 0; // number of the first frequency to be displayed g1 = 0; /* index number indicating which of the currently selected broadcaster's frequencies is currently operational. */ T1showBroadcasters(); T1nextFreq(0); // 0 = display current frequency 'f1' on graticule } // A FREQUENCY IN THE CURRENT BROADCASTER'S FREQUENCY LIST WAS CLICKED else if(mx > 181 && mx < 483 && my > 236 && my < 337) { if(b1 & 1) { // if currently in scanning mode b1 &= ~1; // stop scanning [dim the SCAN button] b1 |= 2; // brighten the STOP button T1Buts(); // re-display the button states } int i, // column number j, // row number x = 0, // column bias y = 0; // row bias for(i = 0; i < 8; i++) { // for each of the 8 columns if(mx < 220 + x) // i = column number break; // exit on clicked column x += 36; // advance to next column right } for(j = 0; j < 6; j++) { // for each of the 6 rows if(my < 255 + y) // i = row number break; // exit on clicked row y += 16; // advance to next row down } /* CALCULATE 'g1' [THE CLICKED FREQUENCY'S LIST NUMBER WITHIN THE CURRENTLY SELECTED BROADCASTER'S FREQUENCIES LIST] number of hidden frequencies [that is: 6 times the number of hidden columns to the left of the frequencies panel] | | i * 6 = number of frequencies in the visible | columns to the left of the clicked frequency | | | ________|________ */ g1 = k1 + (i << 2) + (i << 1) + j; /* | | column number [0 to 7] number of frequencies in clicked fre- of current frequency quency's column upto & including itself FIND THE START BYTE [WITHIN 'stns.dat'] OF THE CLICKED FREQUENCY. h1 + g1 = the start record [within 'stns.dat'] of the clicked frequency. file handle for 'stns.dat' | start record [in 'stns.dat'] of current broadcasrer's frqs | | list number of current frq for selected broadcaster | | | times 4 [frq record is a 4-byte integer | | | | */ fseek(ff,(h1 + g1) << 2,SEEK_SET); // seek start byte of clicked frq f1 = getInt(ff); // get the clicked frequency from 'stns.dat' T1nextFreq(0); // 0 = display current frequency 'f1' on graticule } } /* ONE OF THE SCAN TAB'S CONTROL BUTTONS WAS PRESSED. Button numbers: 0:SCAN, 1:STOP, 2:NEXT, 3:PREV, 4:LISTEN, 5:XXXX, 6:XXXX, 7:HELP. 'b1' contains the Tab 1 Button States. Buttons are numbered 0 to 7 from left to right. 'y' has a '1' in the position in the lower 8 bits of 'b1' corresponding to the number of the clicked button as follows: 00000001 = 1 SCAN 00000010 = 2 STOP 00000100 = 4 NEXT 00001000 = 8 PREV 00010000 = 16 LISTEN 00100000 = 16 XXXX 01000000 = 16 XXXX 10000000 = 128 HELP [Called from 1 place only in MEH()] */ void T1butClick(int x) { switch(x) { // switch according to button number case 0: // SCAN button was clicked if(!(b1 & 1)) { // if the SCAN button is dull b1 |= 1; // brighten the SCAN button b1 &= ~2; // dim the STOP button if bright a3 |= 1; // brighten Tab 3's SCAN button a3 &= ~2; // dim Tab 3's STOP button if bright } break; // if SCAN button already bright, do nothing case 1: // STOP button was clicked if(!(b1 & 2)) { // if the STOP button is dull b1 |= 2; // brighten the STOP button b1 &= ~1; // dim the SCAN button if bright a3 |= 2; // brighten Tab 3's STOP button a3 &= ~1; // dim Tab 3's SCAN button if bright } break; // if STOP button already bright, do nothing case 2: // NEXT button was clicked T1nextFreq(+1); // access and display the NEXT frequency in the list break; // button does not become bright when clicked case 3: // PREV button was clicked T1nextFreq(-1); // access and display PREVious frequency in the list break; // button does not become bright when clicked case 4: // LISTEN button was clicked if(b1 & 8) { // if the LISTEN button is bright b1 &= ~8; // make it dull a0 &= ~8; // make the SCOPE Tab's LISTEN button also dull T3updtOutput(0,0); // emit 'stdout' command "audio off" } else { // otherwise: b1 |= 8; // brighten it a0 |= 8; // make the SCOPE Tab's LISTEN button also bright T3updtOutput(1,0); // emit 'stdout' command "audio on" } break; case 7: // HELP button was clicked helpPage("radio/com.html#scan"); } // end of 'switch' T1Buts(); // re-display the buttons } // FUNCTIONS PERTAINING TO TAB 0: SCOPE--------------------------------------- /* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE SCOPE TAB. Called from 1 place each in T0show() and T0butClick(). */ void T0Buts() { showButs(); // show 8 blank buttons across the bottom of the tab area const char // annotate horizontal row of control buttons *A[] = {"","","","","LISTEN","","","HELP"}; const int a[] = {0,0,0,0,9,0,0,15}, // horiz offsets for word starts b[] = {0,0,0,0,6,0,0, 4}; // number of letters in each word int h = 17; // left edge of left-most button for(int i = 0; i < 8; i++) { // for each of the 8 buttons: if(a0 & 1 << i) // if button is 'on' XSetForeground(XD,XG,WHITE); // colour for the highlighted button else XSetForeground(XD,XG,GREY); // default colour for the lettering XDrawString(XD,XW,XG,h + a[i],377,A[i],b[i]); h += 59; } } /* DISPLAY THE BAND NAMES, FREQUENCY SCALE, BAND GRATICULE, BANDSCOPE TRACE SIGNALS AND OFFICIAL BAND BOUNDARIES. Called from 1 place each in T0BandTypes() and T0click(). */ void T0BandNames() { XSetForeground(XD, XG, DARK); // clear the band names panel XFillRectangle(XD,XW,XG,17,243,466,110); int v = 260, // base height of lettering of the first row of band names h = 23, // left margin for first column of band names N = f0[d0]; // number of Broadcast bands [defaults = Broadcast] for(int i = 0; i < N; i++) { // SHOW BAND NAMES if(i == 6 || i == 12 || i == 18 || i == 24) { // 6 bands per column h += 91; // advance rightwards to next column of band names v = 260; // re-align to the top of the new column } if(e0 == i) // if this is the selected band XSetForeground(XD,XG,WHITE); // highlight it else XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,h,v,bn[i].N,strlen(bn[i].N)); // display band name v += 17; // drop down to next row of current column } c0 = bn[e0].gs; // the start frequency of the selected band // DISPLAY THE TUNING SCALE FOR THE CURRENTLY SELECTED BAND XSetForeground(XD, XG, BLACK); // clear the frequency scale area XFillRectangle(XD,XW,XG,167,219,330,15); int f = bn[e0].gs; // start-freq of graticule for this band h = 182; // horizontal position of first annotation XSetForeground(XD,XG,YELL); // colour for frequency scale numbering for(int i = 0; i < 7; i++) { // for each of the 7 annotations int l = 1; // number of characters in frequency if(f > 9) l = 2; if(f > 99) l = 3; // determine the number of numeric if(f > 999) l = 4; // characters in current annotation if(f > 9999) l = 5; int d = (((l << 2) + (l << 1)) >> 1) - 1; // back-bias for annotation XDrawString(XD,XW,XG,h - d,232,showInt(f,l,0),l); // display annotation f += 50; // increment the frequency for the next annotation h += 50; // increment the horizontal offset for the next annotation } graticule(); // display the frequency indicator graticule // DISPLAY THE BANDSCOPE SIGNALS TRACE fseek(sf,bn[e0].gs,SEEK_SET); // start byte of this graph's scope data int y = 200; // base y-coordinate of graph XSetForeground(XD,XG,GRN); // show trace in darkgreen // for each of the 300 signal plots within the current graph range: for(int i = 0; i < 300; i++) { int c = fgetc(sf), // get first/next 8-bit integer from file x = 182 + i; // x-coord of this signal within graph XDrawLine(XD,XW,XG,x,y-c,x,y); // display its amplitude on the graph h0[i] = c; // save it in the signal amplitudes array } // MARK THE BOUNDS OF THE OFFICIAL BAND WITH A YELLOW RECTANGLE XSetForeground(XD,XG,YELL); // display in a darker yellow /* x-coord of start of graticule | + start frequency of official band | | - start frequency of graticule | ____|____ ____|____ */ XDrawRectangle(XD,XW,XG,182+bn[e0].os-bn[e0].gs,80,bn[e0].ow,120); /* | | | y-coord of start of graticule | | extent of official band in kHz | */ } // height of graticule /* DISPLAY THE 5 BAND TYPES, HIGHLIGHTING THE CURRENTLY SELECTED ONE. Called from 1 place each in T0show() and T0click(). */ void T0BandTypes() { const char *S[5] = {"Broadcast","Amateur","Aircraft","Marine","Commercial"}; const int N[5] = {9,7,8,6,10}; // length of each band-type name int v = 98; // y-coord of base of first name for(int i = 0; i < 5; i++) { // for each band type if(d0 == i) // if this is the currently selected one XSetForeground(XD,XG,WHITE); // display it in bright white else // otherwise display it in grey XSetForeground(XD,XG,GREY); XDrawString(XD,XW,XG,23,v,S[i],N[i]); v += 17; // drop to the next line down } T0BandNames(); // display band names for currently-selected band type } /* DISPLAY THE STATIC CONTENT OF THE SCOPE TAB. Called only from 1 place in showTab(). */ void T0show() { XSetForeground(XD,XG,DARK); // clear the band types panel XFillRectangle(XD,XW,XG,17,80,90,120); XSetForeground(XD,XG,YELL); // display menu titles in yellow XDrawString(XD,XW,XG,182,70,"LISTENING ON: kHz",23); XDrawString(XD,XW,XG,357,70,"MOUSE FREQ: kHz",21); XDrawString(XD,XW,XG,23,70,"TYPES OF BAND",13); XDrawString(XD,XW,XG,23,233,"NAMES OF BANDS",14); XSetForeground(XD,XG,YELL); // display frequency scale in dull yellow XDrawLine(XD,XW,XG,182,210,482,210); XDrawLine(XD,XW,XG,182,211,182,220); XDrawLine(XD,XW,XG,232,211,232,216); XDrawLine(XD,XW,XG,282,211,282,220); XDrawLine(XD,XW,XG,332,211,332,216); XDrawLine(XD,XW,XG,382,211,382,220); XDrawLine(XD,XW,XG,432,210,432,216); XDrawLine(XD,XW,XG,482,210,482,220); XSetForeground(XD,XG,YELLOW); // letter 'S' in bright yellow XDrawString(XD,XW,XG,156,90,"S",1); XSetForeground(XD,XG,GREY); // display the S-meter box in grey XDrawRectangle(XD,XW,XG,132,80,15,120); int x = 115; // display the S-meter graduations for(int i = 0; i < 10; i++) { XDrawLine(XD,XW,XG,154,x-5,149,x-5); x += 10; } XSetForeground(XD,XG,YELL); // display the S-meter numbering x = 115; for(int i = 0; i < 10; i++) { XDrawString(XD,XW,XG,156,x,showInt(9-i,1,0),1); x += 10; } loadBands(); // load the band names for currently selected band type T0BandTypes(); // display the band names for currently selected band type T0Buts(); // display the control buttons for the SCOPE Tab } /* MOUSE CLICKED ON A NON-BUTTON PART OF THE SCOPE TAB. Called only from 1 place in MEH(). */ void T0click() { if(tb != 0) return; // bail out if the SCOPE tab isn't currently visible int y = 102; // vertical back-stop coordinate // IF THE CLICK WAS WITHIN THE BAND TYPES AREA if(my > 68 && my < 170 && mx > 20 && mx < 100) { d0 = 0; // default to Broadcast Bands for(int i = 0; i < 5; i++) { // for each of the 5 band types if(my < y) { // catch the selected Band Type d0 = i; // set the clicked Band Type number break; // exit the loop } y += 17; // drop down to the next line [Band Type name] } loadBands(); // load the Band Names for the selected Band Type T0BandTypes(); // highlight the selected band type // ELSE IF THE CLICK WAS WITHIN THE BAND NAMES AREA } else if(my > 250 && my < 350) { e0 = 0; // default to the first band of the selected type y = 264; // names in first row are higher than this int x = 108, // names in first column are to the left of this N = f0[d0]; // total number of bands of this type for(int i = 0; i < N; i++) { // for each of the band names of this type /* if we've reached the start of the 2nd column | or the 3rd column | | or the 4th column | | | or the 5th column | | | | */ if(i == 6 || i == 12 || i == 18 || i == 24) { x += 91; // add the column width to the right limit of the column y = 264; // and reset to the lower limit of the first row } /* if mouse click is above the lower limit of the current row, | and is also | | to the left of the right limit of the current column | | | */ if(my < y && mx < x) { e0 = i; // note the index number of the clicked Name break; // and break out of the for() loop } y += 17; // drop down to the next row } T0BandNames(); // highlight the selected band name // ELSE IF THE CLICK WAS INSIDE THE GRATICULE AREA } else if(my > 79 && my < 201 && mx > 181 && mx < 483) { g0 = c0 + mx - 182; // 'Listening On' frequency off-set in kilohertz XSetForeground(XD,XG,BLACK); // clear the frequency read-out XFillRectangle(XD,XW,XG,266,58,30,15); XSetForeground(XD,XG,WHITE); // display frequency off-set in white XDrawString(XD,XW,XG,266,70,showInt(g0,5,0),5); T3updtInput(0,g0); // post a GetFrq command T3updtOutput(2,g0); // post a GetFrq command } } // if the click were anywhere else on the SCOPE Tab, do nothing here. /* ONE OF THE SCOPE TAB'S CONTROL BUTTONS WAS PRESSED. Button numbers: 4:LISTEN and 7:HELP are the only buttons currently enabled here. Called from 1 place in MEH(). */ void T0butClick(int x) { int y = 1 << x; // set bit in 'b1' corresponding to clicked button switch(x) { // switch according to button number case 4: // LISTEN button was clicked if(a0 & y) { // if the LISTEN button is bright a0 &= ~y; // make it dull b1 &= ~y; // make the SCAN Tab's LISTEN button also dull T3updtOutput(0,0); // emit 'stdout' command "audio off" } else { // otherwise: a0 |= y; // brighten it b1 |= y; // make the SCAN Tab's LISTEN button also bright T3updtOutput(1,0); // emit 'stdout' command "audio on" } break; case 7: helpPage("radio/com.html#scope"); } T0Buts(); // re-display the buttons } /* MONITOR THE POSITION OF THE MOUSE POINTER WITHIN TAB 0'S FREQUENCY GRATICULE AND DISPLAY THE FREQUENCY INDICATED THEREBY AND THE SIGNAL STRENGTH IN THE S-METER PANEL. Called only from 1 place in main(). */ void T0mouseFreq() { XSetForeground(XD,XG,BLACK); // clear the frequency read-out XFillRectangle(XD,XW,XG,430,58,30,15); // wipe the inner part of the S-meter XFillRectangle(XD,XW,XG,133,81,14,119); int x = 181, // 1 less than x-origin of tuning graticule y = 78, // 2 less than y-origin of tuning graticule z = mx - 182, // position of mouse beyond start of graticule s = h0[z]; // get signal strength at current mouse position // PROVIDED THE MOUSE IS WITHIN THE GRATICULE AREA: if(my > y && my < y + 123 && mx > x && mx < x + 301 && z < 300 && s < 121) { b0 = c0 + z; // band start frequency + mouse off-set in kilohertz XSetForeground(XD,XG,WHITE); // display freq off-set in white XDrawString(XD,XW,XG,430,70,showInt(b0,5,0),5); XSetForeground(XD,XG,GREEN); // display the signal strength in green XFillRectangle(XD,XW,XG,133,201-s,14,s); } } // TAB DISPLAY DISPATCHER----------------------------------------------------- /* DISPLAY ALL THE TITLES, BUTTONS AND DEFAULT IN-FILL FOR THE CURRENT TAB. The current tab is specified by the numeric value of the global variable 'tb': 0=SCOPE Tab, 1=SCAN Tab, 2=HAMS Tab, 3=I/O Tab, 4=XFER Tab, 5=LINK Tab, 6=NETS Tab, 7=TRACK Tab Called from only 1 place each in main() and MEH(). SCOPE 5 12 0 The original order in which the SCAN 4 15 1 tab sub-programs were developed. HAMS 4 15 2 I/O 3 18 3 XFER 4 15 4 LINK 4 15 5 NETS 4 15 6 MOON 4 15 7 SCOPE 5 12 0 The more logical order for them to SCAN 4 15 1 appear in the finished program. HAMS 4 15 2 NETS 4 15 6 MOON 4 15 7 XFER 4 15 4 LINK 4 15 5 I/O 3 18 3 */ void showTab() { const char *BA[8] = {"SCOPE","SCAN","HAMS","NETS","MOON","XFER","LINK","I/O"}; const int NC[8] = { 5, 4, 4, 4, 4, 4, 4, 3}, // number of chars in each annotation NB[8] = {12,15,15,15,15,15,15,18}, // x-bias for each annotation TB[8] = { 0, 1, 2, 6, 7, 4, 5, 3}; // tab re-ordering XSetForeground(XD,XG,DARK); // shade for button background int h = 17, i; // set y-coord to left margin for(i = 0; i < 8; i++) { // draw each button XFillRectangle(XD,XW,XG,h,17,53,21); h += 59; // move across to the next button } h = 17; // reset to the left margin for(i = 0; i < 8; i++) { // annotate each button if(Tb == i) // if this is the currently selected tab XSetForeground(XD,XG,WHITE); // annotate it in bright white lettering else // otherwise XSetForeground(XD,XG,GREY); // annotate it in grey lettering XDrawString(XD,XW,XG,h + *(NB+i),32,*(BA+i),*(NC+i)); h += 59; // move across to the next button } XSetForeground(XD,XG,BLACK); // clear Tab area XFillRectangle(XD,XW,XG,17,38,484,383); tb = TB[Tb]; // re-order the tabs to the original development order switch(tb) { // switch according to tab number case 0: T0show(); break; // paint the SCOPE tab case 1: T1show(); break; // paint the SCAN tab case 2: T2show(); break; // paint the HAMS tab case 3: T3show(); break; // paint the I/O tab case 4: T4show(); break; // paint the XFER tab case 5: T5show(); break; // paint the LINK tab case 6: T6show(); break; // paint the NETS tab case 7: T7show(); // paint the MOON tab } } // MOUSE EVENT HANDLERS------------------------------------------------------- /* MOUSE EVENT HANDLER. Handles clicks of the left mouse button [Button 1]. The x and y coordinates of the click-point within the application window are already in global variables mx and my. Called only from one place in main()'s event loop. */ void MEH() { int x = 1, i; // default click area is the open Tab area if(my > 16 && my < 38) // if click was on one of the Tab buttons x = 0; // indicates that a Tab button was clicked else if(my > 361 && my < 383) // if click was on one of the Bottom buttons x = 2; // indicates a control button was clicked int y = 0; for(i = 0; i < 8; i++) { // for each button if(mx > 16 + y && mx < 70 + y) // if click was within a button break; // break out of loop y += 59; // move across to the next button } switch(x) { // switch according to type of item clicked case 0: // A TAB BUTTON WAS CLICKED Tb = i; // set Tab Number showTab(); // display the Tab's content break; case 1: // THE CLICK OCCURRED IN SOME OTHER PART OF THE TAB AREA switch(tb) { case 0: T0click(); break; // clicked in another part of SCOPE Tab case 1: T1click(); break; // clicked in another part of SCAN Tab case 2: T2click(); break; // clicked in another part of HAMS Tab case 4: T4click(); break; // clicked in another part of XFER Tab case 5: T5click(); break; // clicked in another part of LINK Tab case 6: T6click(); break; // clicked in another part of NETS Tab case 7: T7click(); // clicked in another part of TRACK Tab } break; case 2: // A CONTROL BUTTON WAS CLICKED switch(tb) { // 'i' is button number left to right: 0 to 7 case 0: T0butClick(i); break; // SCOPE Tab's button click handler case 1: T1butClick(i); break; // SCAN Tab's button click handler case 2: T2butClick(i); break; // HAMS Tab's button click handler case 3: T3butClick(i); break; // I/O Tab's button click handler case 4: T4butClick(i); break; // XFER Tab's button click handler case 5: T5butClick(i); break; // LINK Tab's button click handler case 6: T6butClick(i); break; // NETS Tab's button click handler case 7: T7butClick(i); // TRACK Tab's button click handler } } } void MW4() { // MOUSE WHEEL DOWN. Called only from 1 place in main() switch(tb) { case 1: mw = 2; T1scroll(); break; case 2: mw = 2; T2scroll(); break; case 4: mw = 2; T4scroll(); break; case 6: mw = 2; T6scroll(); } } void MW5() { // MOUSE WHEEL UP. Called only from 1 place in main() switch(tb) { case 1: mw = 1; T1scroll(); break; case 2: mw = 1; T2scroll(); break; case 4: mw = 1; T4scroll(); break; case 6: mw = 1; T6scroll(); } } /* THE 1-SECOND TIMER SERVICE */ void secTime() { static int S = 0; // second number from previous pass etm = time(NULL); // GMT in milliseconds since 00h00 UDT 01Jan1970 gms = etm % 86400; // GMT as seconds since midnight lmt = *localtime(&etm); // get the local time parameters dn = lmt.tm_yday; // current day number of year [starts at day 0] int s = lmt.tm_sec; // current second number [clock time] lcs = (lmt.tm_hour * 60 + lmt.tm_min) * 60 + s; // seconds since midnight if(S == s) return; // current second not yet expired S = s; // note new second number for next pass static int D = 0; // day number from previous pass if(D != dn) // if day num changed D = dn; // note the day number for the next pass T1scan(); // step to next frequency for current broadcaster in Tab 1 T4signal(); // check that down-channel signal is still present T4LSS(); // update Tab 4's data transfer sequencer T7moon(); // update the moon tracker T7bandSrch(); // clear channel search timer T7chanSrch(); // clear the up-channel reply timer } // MAIN FUNCTION FROM WHICH ALL THE ABOVE APPLICATION FUNCTIONS ARE CALLED int main(int argc, char *argv[]) { // ACCEPT POSSIBLE COMMAND-LINE ARGUMENTS if(argc > 2) { // command line argument usage: printf("%s\n","Use: com"); // run 'com.c' without input or output printf("%s\n","or: com -o"); // send numeric commands to 'stdout' printf("%s\n","or: com -io"); // also accept input from stdin return 1; // exit because too many args entered } else if(argc > 1) { if(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0) { printf("%s\n","Use: com"); // normal -help output printf("%s\n","or: com -o"); printf("%s\n","or: com -io"); return 2; } // enable only output '-o' or both input and output '-io' else if(strcmp(argv[1], "-o") == 0) a3 |= 8; // brighten Tab 3's [I/O tab] 'stdout' button else if(strcmp(argv[1], "-io") == 0) a3 |= 12; // brighten Tab 3's [I/O tab] 'stdin' and 'stdout' buttons } XEvent e; // reference to any event from the created window int s, // number of the current default screen mouseB; // number of the mouse button that was pressed 0, 1, 2, 3, 4 /* Check that you can actually open the X-display by getting a non-NULL reference to a display. Else send an error message to the terminal. */ XD = XOpenDisplay(NULL); if(XD == NULL) { fprintf(stderr,"Cannot open display\n"); exit(1); } s = DefaultScreen(XD); // get the number of the default screen XG = DefaultGC(XD,s); // set the graphics context to use XW = XCreateSimpleWindow( // create an application window XD, // within the default display RootWindow(XD,s), // within the root window [desktop] 10,10, // position of top right corner 500,400, // width and height of the window 1, // border width in pixels BlackPixel(XD,s), // colour of window's border BlackPixel(XD,s) // colour of window's background ); // set the window title XStoreName(XD,XW,"COMMUNICATIONS COMMAND & CONTROL by R J Morton"); /* Make input possible from the keyboard, window controls and mouse then display the new window on the screen. Not all these bit masks are needed so comment out those that are not needed. */ XSelectInput(XD,XW,ExposureMask | ButtonPressMask | PointerMotionMask // | KeyPressMask | KeyReleaseMask // | ButtonReleaseMask | StructureNotifyMask ); XMapWindow(XD,XW); // map the window to the display XFlush(XD); // flush the display events buffer /* Define a unique indentifier WM_DELETE_WINDOW (referred to as an atom) that will invoke the X11 protocol that closes windows upon the display 'XD' we have just created. [At least I think this is what it means] */ Atom WM_DELETE_WINDOW = XInternAtom(XD,"WM_DELETE_WINDOW",False); /* Make 'delete window' events from this program's window visible as a ClientMessage event. */ XSetWMProtocols(XD,XW,&WM_DELETE_WINDOW,1); // OPEN ALL DATA FILES IN READ-ONLY MODE bf = fopen("broadcasters.dat","r"); char S[4]; for(int i = 0; i < 3; i++) // get the number of broadcasters currently S[i] = fgetc(bf); // on file from the 'broadcasters.dat' file S[3] = '\0'; // terminate string with a null character d1 = atoi(S); // the number of broadcasters as an integer sf = fopen("scope.dat","r"); ft = fopen("bands.dat","r"); fi = fopen("stns.idx","r"); ff = fopen("stns.dat","r"); /* The event-handling [or 'run'] loop [a permanent loop] broken only by the 'break' function in response to an external event. */ while (1) { if(XPending(XD) > 0) { // Provided there are events on the event queue, XNextEvent(XD,&e); // go fetch the first event. /* If it is the 'window ready' event, set the foreground [drawing] colour to white [hex value FFFFFF] and draw the static words and selector lists on the screen. */ if(e.type == Expose) // if an 'expose' event has occurred showTab(); // draw all the titles and menus in the window /* if we are in Tab 0 [SCOPE] | AND the mouse has moved | | | */ else if(tb == 0 && e.type == MotionNotify) { mx = e.xmotion.x; // set current x-coord of mouse my = e.xmotion.y; // set current y-coord of mouse T0mouseFreq(); // display the frequency at the mouse pointer } /* Whenever the mouse is clicked, get its window coordiates. If it was clicked within the area of one of the selector lists, re-draw that list with the clicked item highlishted. */ else if(e.type == ButtonPress) if(e.xbutton.button == Button1) { // if left mouse button clicked mx = e.xbutton.x; // set x-coord of click my = e.xbutton.y; // set y-coord of click MEH(); // call Mouse Event Handler } if(e.xbutton.button == Button4) { // mouse wheel up-button clicked mx = e.xbutton.x; // set x-coord of click my = e.xbutton.y; // set y-coord of click MW4(); // mouse wheel scroll up } if(e.xbutton.button == Button5) { // mouse wheel down-button clicked mx = e.xbutton.x; // set x-coord of click my = e.xbutton.y; // set y-coord of click MW5(); // mouse wheel scroll down } } /* Else if any key has been pressed, or a box control has been clicked, break out of the permanent while() loop, close the window and exit. Should also check here for other client message types; however, as the only protocol registered above is WM_DELETE_WINDOW, it is safe for this program. */ else if (e.type == KeyPress || e.type == ClientMessage) break; /* No external event was captured, so if the Link Connect Sequencer, the Data Transfer Sequencer or Frequency Scanning mode is active, then update the relevant process according to its appropriate time interval. */ else { secTime(); // 1-second & 50 microsecond timer services function T5LSS(); // cycle Tab 5's carrier signal [Lissajous figure] if(i5 > 4 && // if LINK sequencer in 'far carrier acquired' i5 < 10) { // to 'bit frame stabilised' phases inclusive usleep(100); // sleep for only 100 microseconds } else // otherwise usleep(50000); // sleep for 50 milliseconds } } // end of outer permanent while() loop fclose(bf); // close the broadcasters names file fclose(sf); // close the scope trace file scope.dat fclose(ft); // close the band data file fclose(fi); // close the broadcast stations index data file fclose(ff); // close the broadcast stations data file XCloseDisplay(XD); // clear this window from the X11 display 'XD' return 0; // return that everything went OK } // end of main()